cwvreg.py
branchstable
changeset 8463 a964c40adbe3
parent 8437 c9ab72f0645d
child 8537 e30d0a7f0087
equal deleted inserted replaced
8461:8af7c6d86efb 8463:a964c40adbe3
     1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     1 # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     3 #
     3 #
     4 # This file is part of CubicWeb.
     4 # This file is part of CubicWeb.
     5 #
     5 #
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
    14 # details.
    14 # details.
    15 #
    15 #
    16 # You should have received a copy of the GNU Lesser General Public License along
    16 # You should have received a copy of the GNU Lesser General Public License along
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    18 """.. VRegistry:
    18 """.. RegistryStore:
    19 
    19 
    20 The `VRegistry`
    20 The `RegistryStore`
    21 ---------------
    21 -------------------
    22 
    22 
    23 The `VRegistry` can be seen as a two-level dictionary. It contains
    23 The `RegistryStore` can be seen as a two-level dictionary. It contains
    24 all dynamically loaded objects (subclasses of :ref:`appobject`) to
    24 all dynamically loaded objects (subclasses of :ref:`appobject`) to
    25 build a |cubicweb| application. Basically:
    25 build a |cubicweb| application. Basically:
    26 
    26 
    27 * the first level key returns a *registry*. This key corresponds to the
    27 * the first level key returns a *registry*. This key corresponds to the
    28   `__registry__` attribute of application object classes
    28   `__registry__` attribute of application object classes
    32   attribute of application object classes.
    32   attribute of application object classes.
    33 
    33 
    34 A *registry* holds a specific kind of application objects. There is
    34 A *registry* holds a specific kind of application objects. There is
    35 for instance a registry for entity classes, another for views, etc...
    35 for instance a registry for entity classes, another for views, etc...
    36 
    36 
    37 The `VRegistry` has two main responsibilities:
    37 The `RegistryStore` has two main responsibilities:
    38 
    38 
    39 - being the access point to all registries
    39 - being the access point to all registries
    40 
    40 
    41 - handling the registration process at startup time, and during automatic
    41 - handling the registration process at startup time, and during automatic
    42   reloading in debug mode.
    42   reloading in debug mode.
    74 
    74 
    75 API for objects registration
    75 API for objects registration
    76 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    76 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    77 
    77 
    78 Here are the registration methods that you can use in the `registration_callback`
    78 Here are the registration methods that you can use in the `registration_callback`
    79 to register your objects to the `VRegistry` instance given as argument (usually
    79 to register your objects to the `RegistryStore` instance given as argument (usually
    80 named `vreg`):
    80 named `vreg`):
    81 
    81 
    82 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_all
    82 .. automethod:: cubicweb.cwvreg.CWRegistryStore.register_all
    83 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_and_replace
    83 .. automethod:: cubicweb.cwvreg.CWRegistryStore.register_and_replace
    84 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register
    84 .. automethod:: cubicweb.cwvreg.CWRegistryStore.register
    85 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.unregister
    85 .. automethod:: cubicweb.cwvreg.CWRegistryStore.unregister
    86 
    86 
    87 Examples:
    87 Examples:
    88 
    88 
    89 .. sourcecode:: python
    89 .. sourcecode:: python
    90 
    90 
   191 """
   191 """
   192 
   192 
   193 __docformat__ = "restructuredtext en"
   193 __docformat__ = "restructuredtext en"
   194 _ = unicode
   194 _ = unicode
   195 
   195 
       
   196 import sys
       
   197 from os.path import join, dirname, realpath
   196 from warnings import warn
   198 from warnings import warn
   197 from datetime import datetime, date, time, timedelta
   199 from datetime import datetime, date, time, timedelta
   198 
   200 
   199 from logilab.common.decorators import cached, clear_cache
   201 from logilab.common.decorators import cached, clear_cache
   200 from logilab.common.deprecation import deprecated, class_deprecated
   202 from logilab.common.deprecation import deprecated, class_deprecated
   201 from logilab.common.modutils import cleanup_sys_modules
   203 from logilab.common.modutils import cleanup_sys_modules
       
   204 from logilab.common.registry import (
       
   205     RegistryStore, Registry, classid,
       
   206     ObjectNotFound, NoSelectableObject, RegistryNotFound)
   202 
   207 
   203 from rql import RQLHelper
   208 from rql import RQLHelper
   204 from yams.constraints import BASE_CONVERTERS
   209 from yams.constraints import BASE_CONVERTERS
   205 
   210 
   206 from cubicweb import (ETYPE_NAME_MAP, Binary, UnknownProperty, UnknownEid,
   211 from cubicweb import (CW_SOFTWARE_ROOT, ETYPE_NAME_MAP, CW_EVENT_MANAGER,
   207                       ObjectNotFound, NoSelectableObject, RegistryNotFound,
   212                       Binary, UnknownProperty, UnknownEid)
   208                       CW_EVENT_MANAGER)
       
   209 from cubicweb.vregistry import VRegistry, Registry, class_regid, classid
       
   210 from cubicweb.rtags import RTAGS
   213 from cubicweb.rtags import RTAGS
       
   214 from cubicweb.predicates import (implements, appobject_selectable,
       
   215                                  _reset_is_instance_cache)
   211 
   216 
   212 def clear_rtag_objects():
   217 def clear_rtag_objects():
   213     for rtag in RTAGS:
   218     for rtag in RTAGS:
   214         rtag.clear()
   219         rtag.clear()
   215 
   220 
   216 def use_interfaces(obj):
   221 def use_interfaces(obj):
   217     """return interfaces used by the given object by searching for implements
   222     """return interfaces required by the given object by searching for
   218     selectors
   223     `implements` predicate
   219     """
   224     """
   220     from cubicweb.selectors import implements
       
   221     impl = obj.__select__.search_selector(implements)
   225     impl = obj.__select__.search_selector(implements)
   222     if impl:
   226     if impl:
   223         return sorted(impl.expected_ifaces)
   227         return sorted(impl.expected_ifaces)
   224     return ()
   228     return ()
   225 
   229 
   226 def require_appobject(obj):
   230 def require_appobject(obj):
   227     """return interfaces used by the given object by searching for implements
   231     """return appobjects required by the given object by searching for
   228     selectors
   232     `appobject_selectable` predicate
   229     """
   233     """
   230     from cubicweb.selectors import appobject_selectable
       
   231     impl = obj.__select__.search_selector(appobject_selectable)
   234     impl = obj.__select__.search_selector(appobject_selectable)
   232     if impl:
   235     if impl:
   233         return (impl.registry, impl.regids)
   236         return (impl.registry, impl.regids)
   234     return None
   237     return None
   235 
   238 
   251         return sorted([x for x in self.possible_objects(*args, **kwargs)
   254         return sorted([x for x in self.possible_objects(*args, **kwargs)
   252                        if x.cw_propval('visible')],
   255                        if x.cw_propval('visible')],
   253                       key=lambda x: x.cw_propval('order'))
   256                       key=lambda x: x.cw_propval('order'))
   254 
   257 
   255 
   258 
   256 VRegistry.REGISTRY_FACTORY[None] = CWRegistry
   259 def related_appobject(obj, appobjectattr='__appobject__'):
       
   260     """ adapts any object to a potential appobject bound to it
       
   261     through the __appobject__ attribute
       
   262     """
       
   263     return getattr(obj, appobjectattr, obj)
   257 
   264 
   258 
   265 
   259 class ETypeRegistry(CWRegistry):
   266 class ETypeRegistry(CWRegistry):
   260 
   267 
   261     def clear_caches(self):
   268     def clear_caches(self):
   262         clear_cache(self, 'etype_class')
   269         clear_cache(self, 'etype_class')
   263         clear_cache(self, 'parent_classes')
   270         clear_cache(self, 'parent_classes')
   264         from cubicweb import selectors
   271         _reset_is_instance_cache(self.vreg)
   265         selectors._reset_is_instance_cache(self.vreg)
       
   266 
   272 
   267     def initialization_completed(self):
   273     def initialization_completed(self):
   268         """on registration completed, clear etype_class internal cache
   274         """on registration completed, clear etype_class internal cache
   269         """
   275         """
   270         super(ETypeRegistry, self).initialization_completed()
   276         super(ETypeRegistry, self).initialization_completed()
   271         # clear etype cache if you don't want to run into deep weirdness
   277         # clear etype cache if you don't want to run into deep weirdness
   272         self.clear_caches()
   278         self.clear_caches()
   273 
   279 
   274     def register(self, obj, **kwargs):
   280     def register(self, obj, **kwargs):
   275         oid = kwargs.get('oid') or class_regid(obj)
   281         obj = related_appobject(obj)
       
   282         oid = kwargs.get('oid') or obj.__regid__
   276         if oid != 'Any' and not oid in self.schema:
   283         if oid != 'Any' and not oid in self.schema:
   277             self.error('don\'t register %s, %s type not defined in the '
   284             self.error('don\'t register %s, %s type not defined in the '
   278                        'schema', obj, oid)
   285                        'schema', obj, oid)
   279             return
   286             return
   280         kwargs['clear'] = True
   287         kwargs['clear'] = True
   352         for ttype in targettypes:
   359         for ttype in targettypes:
   353             etypecls = self.etype_class(ttype)
   360             etypecls = self.etype_class(ttype)
   354             fetchattrs_list.append(set(etypecls.fetch_attrs))
   361             fetchattrs_list.append(set(etypecls.fetch_attrs))
   355         return reduce(set.intersection, fetchattrs_list)
   362         return reduce(set.intersection, fetchattrs_list)
   356 
   363 
   357 VRegistry.REGISTRY_FACTORY['etypes'] = ETypeRegistry
       
   358 
       
   359 
   364 
   360 class ViewsRegistry(CWRegistry):
   365 class ViewsRegistry(CWRegistry):
   361 
   366 
   362     def main_template(self, req, oid='main-template', rset=None, **kwargs):
   367     def main_template(self, req, oid='main-template', rset=None, **kwargs):
   363         """display query by calling the given template (default to main),
   368         """display query by calling the given template (default to main),
   387                     yield view
   392                     yield view
   388             except Exception:
   393             except Exception:
   389                 self.exception('error while trying to select %s view for %s',
   394                 self.exception('error while trying to select %s view for %s',
   390                                vid, rset)
   395                                vid, rset)
   391 
   396 
   392 VRegistry.REGISTRY_FACTORY['views'] = ViewsRegistry
       
   393 
       
   394 
   397 
   395 class ActionsRegistry(CWRegistry):
   398 class ActionsRegistry(CWRegistry):
   396     def poss_visible_objects(self, *args, **kwargs):
   399     def poss_visible_objects(self, *args, **kwargs):
   397         """return an ordered list of possible actions"""
   400         """return an ordered list of possible actions"""
   398         return sorted(self.possible_objects(*args, **kwargs),
   401         return sorted(self.possible_objects(*args, **kwargs),
   405             actions = rset.possible_actions(**kwargs) # cached implementation
   408             actions = rset.possible_actions(**kwargs) # cached implementation
   406         result = {}
   409         result = {}
   407         for action in actions:
   410         for action in actions:
   408             result.setdefault(action.category, []).append(action)
   411             result.setdefault(action.category, []).append(action)
   409         return result
   412         return result
   410 
       
   411 VRegistry.REGISTRY_FACTORY['actions'] = ActionsRegistry
       
   412 
   413 
   413 
   414 
   414 class CtxComponentsRegistry(CWRegistry):
   415 class CtxComponentsRegistry(CWRegistry):
   415     def poss_visible_objects(self, *args, **kwargs):
   416     def poss_visible_objects(self, *args, **kwargs):
   416         """return an ordered list of possible components"""
   417         """return an ordered list of possible components"""
   443         # XXX set context for bw compat (should now be taken by comp.render())
   444         # XXX set context for bw compat (should now be taken by comp.render())
   444         for component in thisctxcomps:
   445         for component in thisctxcomps:
   445             component.cw_extra_kwargs['context'] = context
   446             component.cw_extra_kwargs['context'] = context
   446         return thisctxcomps
   447         return thisctxcomps
   447 
   448 
   448 VRegistry.REGISTRY_FACTORY['ctxcomponents'] = CtxComponentsRegistry
       
   449 
       
   450 
   449 
   451 class BwCompatCWRegistry(object):
   450 class BwCompatCWRegistry(object):
   452     def __init__(self, vreg, oldreg, redirecttoreg):
   451     def __init__(self, vreg, oldreg, redirecttoreg):
   453         self.vreg = vreg
   452         self.vreg = vreg
   454         self.oldreg = oldreg
   453         self.oldreg = oldreg
   460         return getattr(self.vreg[self.redirecto], attr)
   459         return getattr(self.vreg[self.redirecto], attr)
   461 
   460 
   462     def clear(self): pass
   461     def clear(self): pass
   463     def initialization_completed(self): pass
   462     def initialization_completed(self): pass
   464 
   463 
   465 class CubicWebVRegistry(VRegistry):
   464 
       
   465 class CWRegistryStore(RegistryStore):
   466     """Central registry for the cubicweb instance, extending the generic
   466     """Central registry for the cubicweb instance, extending the generic
   467     VRegistry with some cubicweb specific stuff.
   467     RegistryStore with some cubicweb specific stuff.
   468 
   468 
   469     This is one of the central object in cubicweb instance, coupling
   469     This is one of the central object in cubicweb instance, coupling
   470     dynamically loaded objects with the schema and the configuration objects.
   470     dynamically loaded objects with the schema and the configuration objects.
   471 
   471 
   472     It specializes the VRegistry by adding some convenience methods to access to
   472     It specializes the RegistryStore by adding some convenience methods to access to
   473     stored objects. Currently we have the following registries of objects known
   473     stored objects. Currently we have the following registries of objects known
   474     by the web instance (library may use some others additional registries):
   474     by the web instance (library may use some others additional registries):
   475 
   475 
   476     * 'etypes', entity type classes
   476     * 'etypes', entity type classes
   477 
   477 
   489     * 'formrenderers', rendering forms to html
   489     * 'formrenderers', rendering forms to html
   490 
   490 
   491     * 'controllers', primary objects to handle request publishing, directly
   491     * 'controllers', primary objects to handle request publishing, directly
   492       plugged into the application
   492       plugged into the application
   493     """
   493     """
       
   494 
       
   495     REGISTRY_FACTORY = {None: CWRegistry,
       
   496                         'etypes': ETypeRegistry,
       
   497                         'views': ViewsRegistry,
       
   498                         'actions': ActionsRegistry,
       
   499                         'ctxcomponents': CtxComponentsRegistry,
       
   500                         }
   494 
   501 
   495     def __init__(self, config, initlog=True):
   502     def __init__(self, config, initlog=True):
   496         if initlog:
   503         if initlog:
   497             # first init log service
   504             # first init log service
   498             config.init_log()
   505             config.init_log()
   499         super(CubicWebVRegistry, self).__init__(config)
   506         super(CWRegistryStore, self).__init__(config.debugmode)
       
   507         self.config = config
       
   508         # need to clean sys.path this to avoid import confusion pb (i.e.  having
       
   509         # the same module loaded as 'cubicweb.web.views' subpackage and as
       
   510         # views' or 'web.views' subpackage. This is mainly for testing purpose,
       
   511         # we should'nt need this in production environment
       
   512         for webdir in (join(dirname(realpath(__file__)), 'web'),
       
   513                        join(dirname(__file__), 'web')):
       
   514             if webdir in sys.path:
       
   515                 sys.path.remove(webdir)
       
   516         if CW_SOFTWARE_ROOT in sys.path:
       
   517             sys.path.remove(CW_SOFTWARE_ROOT)
   500         self.schema = None
   518         self.schema = None
   501         self.initialized = False
   519         self.initialized = False
   502         # XXX give force_reload (or refactor [re]loading...)
   520         # XXX give force_reload (or refactor [re]loading...)
   503         if self.config.mode != 'test':
   521         if self.config.mode != 'test':
   504             # don't clear rtags during test, this may cause breakage with
   522             # don't clear rtags during test, this may cause breakage with
   513         except RegistryNotFound:
   531         except RegistryNotFound:
   514             self[regid] = self.registry_class(regid)(self)
   532             self[regid] = self.registry_class(regid)(self)
   515             return self[regid]
   533             return self[regid]
   516 
   534 
   517     def items(self):
   535     def items(self):
   518         return [item for item in super(CubicWebVRegistry, self).items()
   536         return [item for item in super(CWRegistryStore, self).items()
   519                 if not item[0] in ('propertydefs', 'propertyvalues')]
   537                 if not item[0] in ('propertydefs', 'propertyvalues')]
   520     def iteritems(self):
   538     def iteritems(self):
   521         return (item for item in super(CubicWebVRegistry, self).iteritems()
   539         return (item for item in super(CWRegistryStore, self).iteritems()
   522                 if not item[0] in ('propertydefs', 'propertyvalues'))
   540                 if not item[0] in ('propertydefs', 'propertyvalues'))
   523 
   541 
   524     def values(self):
   542     def values(self):
   525         return [value for key, value in self.items()]
   543         return [value for key, value in self.items()]
   526     def itervalues(self):
   544     def itervalues(self):
   527         return (value for key, value in self.items())
   545         return (value for key, value in self.items())
   528 
   546 
       
   547     def load_module(self, module):
       
   548         """ variation from the base implementation:
       
   549         apply related_appobject to the automatically registered objects
       
   550         """
       
   551         self.info('loading %s from %s', module.__name__, module.__file__)
       
   552         if hasattr(module, 'registration_callback'):
       
   553             module.registration_callback(self)
       
   554             return
       
   555         for objname, obj in vars(module).iteritems():
       
   556             if objname.startswith('_'):
       
   557                 continue
       
   558             self._load_ancestors_then_object(module.__name__,
       
   559                                              related_appobject(obj))
       
   560 
   529     def reset(self):
   561     def reset(self):
   530         CW_EVENT_MANAGER.emit('before-registry-reset', self)
   562         CW_EVENT_MANAGER.emit('before-registry-reset', self)
   531         super(CubicWebVRegistry, self).reset()
   563         super(CWRegistryStore, self).reset()
   532         self._needs_iface = {}
   564         self._needs_iface = {}
   533         self._needs_appobject = {}
   565         self._needs_appobject = {}
   534         # two special registries, propertydefs which care all the property
   566         # two special registries, propertydefs which care all the property
   535         # definitions, and propertyvals which contains values for those
   567         # definitions, and propertyvals which contains values for those
   536         # properties
   568         # properties
   538             self['propertydefs'] = {}
   570             self['propertydefs'] = {}
   539             self['propertyvalues'] = self.eprop_values = {}
   571             self['propertyvalues'] = self.eprop_values = {}
   540             for key, propdef in self.config.cwproperty_definitions():
   572             for key, propdef in self.config.cwproperty_definitions():
   541                 self.register_property(key, **propdef)
   573                 self.register_property(key, **propdef)
   542         CW_EVENT_MANAGER.emit('after-registry-reset', self)
   574         CW_EVENT_MANAGER.emit('after-registry-reset', self)
       
   575 
       
   576     def register_all(self, objects, modname, butclasses=()):
       
   577         butclasses = set(related_appobject(obj)
       
   578                          for obj in butclasses)
       
   579         objects = [related_appobject(obj) for obj in objects]
       
   580         super(CWRegistryStore, self).register_all(objects, modname, butclasses)
       
   581 
       
   582     def register_and_replace(self, obj, replaced):
       
   583         obj = related_appobject(obj)
       
   584         replaced = related_appobject(replaced)
       
   585         super(CWRegistryStore, self).register_and_replace(obj, replaced)
   543 
   586 
   544     def set_schema(self, schema):
   587     def set_schema(self, schema):
   545         """set instance'schema and load application objects"""
   588         """set instance'schema and load application objects"""
   546         self._set_schema(schema)
   589         self._set_schema(schema)
   547         # now we can load application's web objects
   590         # now we can load application's web objects
   595     def register_if_interface_found(self, obj, ifaces, **kwargs):
   638     def register_if_interface_found(self, obj, ifaces, **kwargs):
   596         """register `obj` but remove it if no entity class implements one of
   639         """register `obj` but remove it if no entity class implements one of
   597         the given `ifaces` interfaces at the end of the registration process.
   640         the given `ifaces` interfaces at the end of the registration process.
   598 
   641 
   599         Extra keyword arguments are given to the
   642         Extra keyword arguments are given to the
   600         :meth:`~cubicweb.cwvreg.CubicWebVRegistry.register` function.
   643         :meth:`~cubicweb.cwvreg.CWRegistryStore.register` function.
   601         """
   644         """
   602         self.register(obj, **kwargs)
   645         self.register(obj, **kwargs)
   603         if not isinstance(ifaces,  (tuple, list)):
   646         if not isinstance(ifaces,  (tuple, list)):
   604             self._needs_iface[obj] = (ifaces,)
   647             self._needs_iface[obj] = (ifaces,)
   605         else:
   648         else:
   611         `obj.__regid__` if not specified.
   654         `obj.__regid__` if not specified.
   612 
   655 
   613         If `clear` is true, all objects with the same identifier will be
   656         If `clear` is true, all objects with the same identifier will be
   614         previously unregistered.
   657         previously unregistered.
   615         """
   658         """
   616         super(CubicWebVRegistry, self).register(obj, *args, **kwargs)
   659         obj = related_appobject(obj)
       
   660         super(CWRegistryStore, self).register(obj, *args, **kwargs)
   617         # XXX bw compat
   661         # XXX bw compat
   618         ifaces = use_interfaces(obj)
   662         ifaces = use_interfaces(obj)
   619         if ifaces:
   663         if ifaces:
   620             if not obj.__name__.endswith('Adapter') and \
   664             if not obj.__name__.endswith('Adapter') and \
   621                    any(iface for iface in ifaces if not isinstance(iface, basestring)):
   665                    any(iface for iface in ifaces if not isinstance(iface, basestring)):
   628             self._needs_appobject[obj] = depends_on
   672             self._needs_appobject[obj] = depends_on
   629 
   673 
   630     def register_objects(self, path):
   674     def register_objects(self, path):
   631         """overriden to give cubicweb's extrapath (eg cubes package's __path__)
   675         """overriden to give cubicweb's extrapath (eg cubes package's __path__)
   632         """
   676         """
   633         super(CubicWebVRegistry, self).register_objects(
   677         super(CWRegistryStore, self).register_objects(
   634             path, self.config.extrapath)
   678             path, self.config.extrapath)
   635 
   679 
   636     def initialization_completed(self):
   680     def initialization_completed(self):
   637         """cw specific code once vreg initialization is completed:
   681         """cw specific code once vreg initialization is completed:
   638 
   682 
   683                         break
   727                         break
   684                 else:
   728                 else:
   685                     self.debug('unregister %s (no %s object in registry %s)',
   729                     self.debug('unregister %s (no %s object in registry %s)',
   686                                classid(obj), ' or '.join(regids), regname)
   730                                classid(obj), ' or '.join(regids), regname)
   687                     self.unregister(obj)
   731                     self.unregister(obj)
   688         super(CubicWebVRegistry, self).initialization_completed()
   732         super(CWRegistryStore, self).initialization_completed()
   689         for rtag in RTAGS:
   733         for rtag in RTAGS:
   690             # don't check rtags if we don't want to cleanup_interface_sobjects
   734             # don't check rtags if we don't want to cleanup_interface_sobjects
   691             rtag.init(self.schema, check=self.config.cleanup_interface_sobjects)
   735             rtag.init(self.schema, check=self.config.cleanup_interface_sobjects)
   692 
   736 
   693     # rql parsing utilities ####################################################
   737     # rql parsing utilities ####################################################