vregistry.py
changeset 2650 18aec79ec3a3
parent 2589 92f2bc945261
child 2655 48cd71bdb5cd
equal deleted inserted replaced
2649:5d4a943695d1 2650:18aec79ec3a3
    25 import types
    25 import types
    26 from os import listdir, stat
    26 from os import listdir, stat
    27 from os.path import dirname, join, realpath, split, isdir, exists
    27 from os.path import dirname, join, realpath, split, isdir, exists
    28 from logging import getLogger
    28 from logging import getLogger
    29 from warnings import warn
    29 from warnings import warn
       
    30 
       
    31 from logilab.common.deprecation import deprecated
    30 
    32 
    31 from cubicweb import CW_SOFTWARE_ROOT, set_log_methods
    33 from cubicweb import CW_SOFTWARE_ROOT, set_log_methods
    32 from cubicweb import RegistryNotFound, ObjectNotFound, NoSelectableObject
    34 from cubicweb import RegistryNotFound, ObjectNotFound, NoSelectableObject
    33 
    35 
    34 
    36 
   123                 else:
   125                 else:
   124                     select = AndSelector(*selectors)
   126                     select = AndSelector(*selectors)
   125                 cls.__select__ = select
   127                 cls.__select__ = select
   126 
   128 
   127 
   129 
   128 class VRegistry(object):
   130 class Registry(dict):
   129     """class responsible to register, propose and select the various
   131 
   130     elements used to build the web interface. Currently, we have templates,
   132     def __init__(self, config):
   131     views, actions and components.
   133         super(Registry, self).__init__()
   132     """
       
   133 
       
   134     def __init__(self, config):#, cache_size=1000):
       
   135         self.config = config
   134         self.config = config
   136         # dictionnary of registry (themself dictionnary) by name
   135 
   137         self._registries = {}
   136     def __getitem__(self, name):
   138         self._lastmodifs = {}
       
   139 
       
   140     def reset(self):
       
   141         self._registries = {}
       
   142         self._lastmodifs = {}
       
   143 
       
   144     def __getitem__(self, key):
       
   145         return self._registries[key]
       
   146 
       
   147     def get(self, key, default=None):
       
   148         return self._registries.get(key, default)
       
   149 
       
   150     def items(self):
       
   151         return self._registries.items()
       
   152 
       
   153     def values(self):
       
   154         return self._registries.values()
       
   155 
       
   156     def __contains__(self, key):
       
   157         return key in self._registries
       
   158 
       
   159     def registry(self, name):
       
   160         """return the registry (dictionary of class objects) associated to
   137         """return the registry (dictionary of class objects) associated to
   161         this name
   138         this name
   162         """
   139         """
   163         try:
   140         try:
   164             return self._registries[name]
   141             return super(Registry, self).__getitem__(name)
   165         except KeyError:
   142         except KeyError:
   166             raise RegistryNotFound(name), None, sys.exc_info()[-1]
   143             raise ObjectNotFound(name), None, sys.exc_info()[-1]
   167 
   144 
   168     def registry_objects(self, name, oid=None):
   145     def register(self, obj, oid=None, clear=False):
   169         """returns objects registered with the given oid in the given registry.
   146         """base method to add an object in the registry"""
   170         If no oid is given, return all objects in this registry
   147         assert not '__abstract__' in obj.__dict__
   171         """
   148         oid = oid or obj.id
   172         registry = self.registry(name)
   149         assert oid
   173         if oid is not None:
   150         if clear:
   174             try:
   151             vobjects = self[oid] =  []
   175                 return registry[oid]
   152         else:
   176             except KeyError:
   153             vobjects = self.setdefault(oid, [])
   177                 raise ObjectNotFound(oid), None, sys.exc_info()[-1]
   154         # registered() is technically a classmethod but is not declared
       
   155         # as such because we need to compose registered in some cases
       
   156         vobject = obj.registered.im_func(obj, self)
       
   157         assert not vobject in vobjects, \
       
   158                'object %s is already registered' % vobject
       
   159         assert callable(vobject.__select__), vobject
       
   160         vobjects.append(vobject)
       
   161 
       
   162     def register_and_replace(self, obj, replaced):
       
   163         # XXXFIXME this is a duplication of unregister()
       
   164         # remove register_and_replace in favor of unregister + register
       
   165         # or simplify by calling unregister then register here
       
   166         if hasattr(replaced, 'classid'):
       
   167             replaced = replaced.classid()
       
   168         registered_objs = self.get(obj.id, ())
       
   169         for index, registered in enumerate(registered_objs):
       
   170             if registered.classid() == replaced:
       
   171                 del registered_objs[index]
       
   172                 break
       
   173         else:
       
   174             self.warning('trying to replace an unregistered view %s by %s',
       
   175                          replaced, obj)
       
   176         self.register(obj)
       
   177 
       
   178     def unregister(self, obj):
       
   179         oid = obj.classid()
       
   180         for registered in self.get(obj.id, ()):
       
   181             # use classid() to compare classes because vreg will probably
       
   182             # have its own version of the class, loaded through execfile
       
   183             if registered.classid() == oid:
       
   184                 # XXX automatic reloading management
       
   185                 self[obj.id].remove(registered)
       
   186                 break
       
   187         else:
       
   188             self.warning('can\'t remove %s, no id %s in the registry',
       
   189                          oid, obj.id)
       
   190 
       
   191     def all_objects(self):
       
   192         """return a list containing all objects in this registry.
       
   193         """
   178         result = []
   194         result = []
   179         for objs in registry.values():
   195         for objs in self.values():
   180             result += objs
   196             result += objs
   181         return result
   197         return result
   182 
   198 
   183     # dynamic selection methods ################################################
   199     # dynamic selection methods ################################################
   184 
   200 
   185     def object_by_id(self, registry, oid, *args, **kwargs):
   201     def object_by_id(self, oid, *args, **kwargs):
   186         """return object in <registry>.<oid>
   202         """return object with the given oid. Only one object is expected to be
       
   203         found.
   187 
   204 
   188         raise `ObjectNotFound` if not object with id <oid> in <registry>
   205         raise `ObjectNotFound` if not object with id <oid> in <registry>
   189         raise `AssertionError` if there is more than one object there
   206         raise `AssertionError` if there is more than one object there
   190         """
   207         """
   191         objects = self.registry_objects(registry, oid)
   208         objects = self[oid]
   192         assert len(objects) == 1, objects
   209         assert len(objects) == 1, objects
   193         return objects[0].selected(*args, **kwargs)
   210         return objects[0].selected(*args, **kwargs)
   194 
   211 
   195     def select(self, registry, oid, *args, **kwargs):
   212     def select(self, oid, *args, **kwargs):
   196         """return the most specific object in <registry>.<oid> according to
   213         """return the most specific object among those with the given oid
   197         the given context
   214         according to the given context.
   198 
   215 
   199         raise `ObjectNotFound` if not object with id <oid> in <registry>
   216         raise `ObjectNotFound` if not object with id <oid> in <registry>
   200         raise `NoSelectableObject` if not object apply
   217         raise `NoSelectableObject` if not object apply
   201         """
   218         """
   202         return self.select_best(self.registry_objects(registry, oid),
   219         return self.select_best(self[oid], *args, **kwargs)
   203                                 *args, **kwargs)
   220 
   204 
   221     def select_object(self, oid, *args, **kwargs):
   205     def select_object(self, registry, oid, *args, **kwargs):
   222         """return the most specific object among those with the given oid
   206         """return the most specific object in <registry>.<oid> according to
   223         according to the given context, or None if no object applies.
   207         the given context, or None if no object apply
   224         """
   208         """
   225         try:
   209         try:
   226             return self.select(oid, *args, **kwargs)
   210             return self.select(registry, oid, *args, **kwargs)
       
   211         except (NoSelectableObject, ObjectNotFound):
   227         except (NoSelectableObject, ObjectNotFound):
   212             return None
   228             return None
   213 
   229 
   214     def possible_objects(self, registry, *args, **kwargs):
   230     def possible_objects(self, *args, **kwargs):
   215         """return an iterator on possible objects in <registry> for the given
   231         """return an iterator on possible objects in this registry for the given
   216         context
   232         context
   217         """
   233         """
   218         for vobjects in self.registry(registry).itervalues():
   234         for vobjects in self.itervalues():
   219             try:
   235             try:
   220                 yield self.select_best(vobjects, *args, **kwargs)
   236                 yield self.select_best(vobjects, *args, **kwargs)
   221             except NoSelectableObject:
   237             except NoSelectableObject:
   222                 continue
   238                 continue
   223 
   239 
   250                                 % (args, kwargs.keys(),
   266                                 % (args, kwargs.keys(),
   251                                    [repr(v) for v in winners]))
   267                                    [repr(v) for v in winners]))
   252         # return the result of the .selected method of the vobject
   268         # return the result of the .selected method of the vobject
   253         return winners[0].selected(*args, **kwargs)
   269         return winners[0].selected(*args, **kwargs)
   254 
   270 
       
   271 
       
   272 class VRegistry(dict):
       
   273     """class responsible to register, propose and select the various
       
   274     elements used to build the web interface. Currently, we have templates,
       
   275     views, actions and components.
       
   276     """
       
   277 
       
   278     def __init__(self, config):
       
   279         super(VRegistry, self).__init__()
       
   280         self.config = config
       
   281 
       
   282     def reset(self):
       
   283         self.clear()
       
   284         self._lastmodifs = {}
       
   285 
       
   286     def __getitem__(self, name):
       
   287         """return the registry (dictionary of class objects) associated to
       
   288         this name
       
   289         """
       
   290         try:
       
   291             return super(VRegistry, self).__getitem__(name)
       
   292         except KeyError:
       
   293             raise RegistryNotFound(name), None, sys.exc_info()[-1]
       
   294 
       
   295     # dynamic selection methods ################################################
       
   296 
       
   297     @deprecated('use vreg[registry].object_by_id(oid, *args, **kwargs)')
       
   298     def object_by_id(self, registry, oid, *args, **kwargs):
       
   299         """return object in <registry>.<oid>
       
   300 
       
   301         raise `ObjectNotFound` if not object with id <oid> in <registry>
       
   302         raise `AssertionError` if there is more than one object there
       
   303         """
       
   304         return self[registry].object_by_id(oid)
       
   305 
       
   306     @deprecated('use vreg[registry].select(oid, *args, **kwargs)')
       
   307     def select(self, registry, oid, *args, **kwargs):
       
   308         """return the most specific object in <registry>.<oid> according to
       
   309         the given context
       
   310 
       
   311         raise `ObjectNotFound` if not object with id <oid> in <registry>
       
   312         raise `NoSelectableObject` if not object apply
       
   313         """
       
   314         return self[registry].select(oid, *args, **kwargs)
       
   315 
       
   316     @deprecated('use vreg[registry].select_object(oid, *args, **kwargs)')
       
   317     def select_object(self, registry, oid, *args, **kwargs):
       
   318         """return the most specific object in <registry>.<oid> according to
       
   319         the given context, or None if no object apply
       
   320         """
       
   321         return self[registry].select_object(oid, *args, **kwargs)
       
   322 
       
   323     @deprecated('use vreg[registry].possible_objects(*args, **kwargs)')
       
   324     def possible_objects(self, registry, *args, **kwargs):
       
   325         """return an iterator on possible objects in <registry> for the given
       
   326         context
       
   327         """
       
   328         return self[registry].possible_objects(*args, **kwargs)
       
   329 
   255     # methods for explicit (un)registration ###################################
   330     # methods for explicit (un)registration ###################################
       
   331 
       
   332     # default class, when no specific class set
       
   333     REGISTRY_FACTORY = {None: Registry}
       
   334 
       
   335     def registry_class(self, regid):
       
   336         try:
       
   337             return self.REGISTRY_FACTORY[regid]
       
   338         except KeyError:
       
   339             return self.REGISTRY_FACTORY[None]
       
   340 
       
   341     def setdefault(self, regid):
       
   342         try:
       
   343             return self[regid]
       
   344         except KeyError:
       
   345             self[regid] = self.registry_class(regid)(self.config)
       
   346             return self[regid]
   256 
   347 
   257 #     def clear(self, key):
   348 #     def clear(self, key):
   258 #         regname, oid = key.split('.')
   349 #         regname, oid = key.split('.')
   259 #         self[regname].pop(oid, None)
   350 #         self[regname].pop(oid, None)
   260 
   351 
   271 
   362 
   272     def register(self, obj, registryname=None, oid=None, clear=False):
   363     def register(self, obj, registryname=None, oid=None, clear=False):
   273         """base method to add an object in the registry"""
   364         """base method to add an object in the registry"""
   274         assert not '__abstract__' in obj.__dict__
   365         assert not '__abstract__' in obj.__dict__
   275         registryname = registryname or obj.__registry__
   366         registryname = registryname or obj.__registry__
   276         oid = oid or obj.id
   367         registry = self.setdefault(registryname)
   277         assert oid
   368         registry.register(obj, oid=oid, clear=clear)
   278         registry = self._registries.setdefault(registryname, {})
   369         try:
   279         if clear:
   370             vname = obj.__name__
   280             vobjects = registry[oid] =  []
       
   281         else:
       
   282             vobjects = registry.setdefault(oid, [])
       
   283         # registered() is technically a classmethod but is not declared
       
   284         # as such because we need to compose registered in some cases
       
   285         vobject = obj.registered.im_func(obj, self)
       
   286         assert not vobject in vobjects, \
       
   287                'object %s is already registered' % vobject
       
   288         assert callable(vobject.__select__), vobject
       
   289         vobjects.append(vobject)
       
   290         try:
       
   291             vname = vobject.__name__
       
   292         except AttributeError:
   371         except AttributeError:
   293             vname = vobject.__class__.__name__
   372             vname = obj.__class__.__name__
   294         self.debug('registered vobject %s in registry %s with id %s',
   373         self.debug('registered vobject %s in registry %s with id %s',
   295                    vname, registryname, oid)
   374                    vname, registryname, oid)
   296         # automatic reloading management
   375         # automatic reloading management
   297         self._loadedmods[obj.__module__]['%s.%s' % (obj.__module__, oid)] = obj
   376         self._loadedmods[obj.__module__]['%s.%s' % (obj.__module__, oid)] = obj
   298 
   377 
   299     def unregister(self, obj, registryname=None):
   378     def unregister(self, obj, registryname=None):
   300         registryname = registryname or obj.__registry__
   379         self[registryname or obj.__registry__].unregister(obj)
   301         registry = self.registry(registryname)
       
   302         removed_id = obj.classid()
       
   303         for registered in registry.get(obj.id, ()):
       
   304             # use classid() to compare classes because vreg will probably
       
   305             # have its own version of the class, loaded through execfile
       
   306             if registered.classid() == removed_id:
       
   307                 # XXX automatic reloading management
       
   308                 registry[obj.id].remove(registered)
       
   309                 break
       
   310         else:
       
   311             self.warning('can\'t remove %s, no id %s in the %s registry',
       
   312                          removed_id, obj.id, registryname)
       
   313 
   380 
   314     def register_and_replace(self, obj, replaced, registryname=None):
   381     def register_and_replace(self, obj, replaced, registryname=None):
   315         # XXXFIXME this is a duplication of unregister()
   382         self[registryname or obj.__registry__].register_and_replace(obj, replaced)
   316         # remove register_and_replace in favor of unregister + register
   383 
   317         # or simplify by calling unregister then register here
   384     # initialization methods ###################################################
   318         if hasattr(replaced, 'classid'):
       
   319             replaced = replaced.classid()
       
   320         registryname = registryname or obj.__registry__
       
   321         registry = self.registry(registryname)
       
   322         registered_objs = registry.get(obj.id, ())
       
   323         for index, registered in enumerate(registered_objs):
       
   324             if registered.classid() == replaced:
       
   325                 del registry[obj.id][index]
       
   326                 break
       
   327         else:
       
   328             self.warning('trying to replace an unregistered view %s by %s',
       
   329                          replaced, obj)
       
   330         self.register(obj, registryname=registryname)
       
   331 
       
   332     # intialization methods ###################################################
       
   333 
   385 
   334     def init_registration(self, path, extrapath=None):
   386     def init_registration(self, path, extrapath=None):
   335         # compute list of all modules that have to be loaded
   387         # compute list of all modules that have to be loaded
   336         self._toloadmods, filemods = _toload_info(path, extrapath)
   388         self._toloadmods, filemods = _toload_info(path, extrapath)
   337         self._loadedmods = {}
   389         self._loadedmods = {}
   519                         self.debug('updating %s.%s base classes',
   571                         self.debug('updating %s.%s base classes',
   520                                   obj.__module__, obj.__name__)
   572                                   obj.__module__, obj.__name__)
   521                         obj.__bases__ = newbases
   573                         obj.__bases__ = newbases
   522 
   574 
   523 # init logging
   575 # init logging
   524 set_log_methods(VObject, getLogger('cubicweb'))
   576 set_log_methods(VObject, getLogger('cubicweb.appobject'))
   525 set_log_methods(VRegistry, getLogger('cubicweb.registry'))
   577 set_log_methods(VRegistry, getLogger('cubicweb.vreg'))
       
   578 set_log_methods(Registry, getLogger('cubicweb.registry'))
   526 
   579 
   527 
   580 
   528 # selector base classes and operations ########################################
   581 # selector base classes and operations ########################################
   529 
   582 
   530 class Selector(object):
   583 class Selector(object):