vregistry.py
changeset 2675 f84ba1a66abb
parent 2662 87aed8cb7ff6
child 2689 44f041222d0f
equal deleted inserted replaced
2674:ff6114c2c416 2675:f84ba1a66abb
     3   together. The vregistry handles registration of dynamically loaded
     3   together. The vregistry handles registration of dynamically loaded
     4   objects and provides a convenient api to access those objects
     4   objects and provides a convenient api to access those objects
     5   according to a context
     5   according to a context
     6 
     6 
     7 * to interact with the vregistry, objects should inherit from the
     7 * to interact with the vregistry, objects should inherit from the
     8   VObject abstract class
     8   AppObject abstract class
     9 
     9 
    10 * the selection procedure has been generalized by delegating to a
    10 * the selection procedure has been generalized by delegating to a
    11   selector, which is responsible to score the vobject according to the
    11   selector, which is responsible to score the appobject according to the
    12   current state (req, rset, row, col). At the end of the selection, if
    12   current state (req, rset, row, col). At the end of the selection, if
    13   a vobject class has been found, an instance of this class is
    13   a appobject class has been found, an instance of this class is
    14   returned. The selector is instantiated at vobject registration
    14   returned. The selector is instantiated at appobject registration
    15 
    15 
    16 
    16 
    17 :organization: Logilab
    17 :organization: Logilab
    18 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
    18 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
    19 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
    19 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
    20 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
    20 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
    21 """
    21 """
    22 __docformat__ = "restructuredtext en"
    22 __docformat__ = "restructuredtext en"
    23 
    23 
    24 import sys
    24 import sys
    25 import types
       
    26 from os import listdir, stat
    25 from os import listdir, stat
    27 from os.path import dirname, join, realpath, split, isdir, exists
    26 from os.path import dirname, join, realpath, split, isdir, exists
    28 from logging import getLogger
    27 from logging import getLogger
    29 from warnings import warn
    28 from warnings import warn
    30 
    29 
    31 from cubicweb import CW_SOFTWARE_ROOT, set_log_methods
    30 from logilab.common.deprecation import deprecated, class_moved
       
    31 from logilab.common.logging_ext import set_log_methods
       
    32 
       
    33 from cubicweb import CW_SOFTWARE_ROOT
    32 from cubicweb import (RegistryNotFound, ObjectNotFound, NoSelectableObject,
    34 from cubicweb import (RegistryNotFound, ObjectNotFound, NoSelectableObject,
    33                       RegistryOutOfDate)
    35                       RegistryOutOfDate)
       
    36 from cubicweb.appobject import AppObject
    34 
    37 
    35 # XXX depending on cubicweb.web is ugly, we should deal with uicfg
    38 # XXX depending on cubicweb.web is ugly, we should deal with uicfg
    36 #     reset with a good old event / callback system
    39 #     reset with a good old event / callback system
    37 try:
    40 try:
    38     from cubicweb.web import uicfg
    41     from cubicweb.web import uicfg
    56             _toload[0][modname] = fileordir
    59             _toload[0][modname] = fileordir
    57             _toload[1].append((fileordir, modname))
    60             _toload[1].append((fileordir, modname))
    58     return _toload
    61     return _toload
    59 
    62 
    60 
    63 
    61 class VObject(object):
    64 class Registry(dict):
    62     """visual object, use to be handled somehow by the visual components
    65 
    63     registry.
    66     def __init__(self, config):
    64 
    67         super(Registry, self).__init__()
    65     The following attributes should be set on concret vobject subclasses:
       
    66 
       
    67     :__registry__:
       
    68       name of the registry for this object (string like 'views',
       
    69       'templates'...)
       
    70     :id:
       
    71       object's identifier in the registry (string like 'main',
       
    72       'primary', 'folder_box')
       
    73     :__select__:
       
    74       class'selector
       
    75 
       
    76     Moreover, the `__abstract__` attribute may be set to True to indicate
       
    77     that a vobject is abstract and should not be registered
       
    78     """
       
    79     # necessary attributes to interact with the registry
       
    80     id = None
       
    81     __registry__ = None
       
    82     __select__ = None
       
    83 
       
    84     @classmethod
       
    85     def registered(cls, registry):
       
    86         """called by the registry when the vobject has been registered.
       
    87 
       
    88         It must return the  object that will be actually registered (this
       
    89         may be the right hook to create an instance for example). By
       
    90         default the vobject is returned without any transformation.
       
    91         """
       
    92         cls.build___select__()
       
    93         return cls
       
    94 
       
    95     @classmethod
       
    96     def selected(cls, *args, **kwargs):
       
    97         """called by the registry when the vobject has been selected.
       
    98 
       
    99         It must return the  object that will be actually returned by the
       
   100         .select method (this may be the right hook to create an
       
   101         instance for example). By default the selected object is
       
   102         returned without any transformation.
       
   103         """
       
   104         return cls
       
   105 
       
   106     @classmethod
       
   107     def classid(cls):
       
   108         """returns a unique identifier for the vobject"""
       
   109         return '%s.%s' % (cls.__module__, cls.__name__)
       
   110 
       
   111     # XXX bw compat code
       
   112     @classmethod
       
   113     def build___select__(cls):
       
   114         for klass in cls.mro():
       
   115             if klass.__name__ == 'AppRsetObject':
       
   116                 continue # the bw compat __selector__ is there
       
   117             klassdict = klass.__dict__
       
   118             if ('__select__' in klassdict and '__selectors__' in klassdict
       
   119                 and '__selgenerated__' not in klassdict):
       
   120                 raise TypeError("__select__ and __selectors__ can't be used together on class %s" % cls)
       
   121             if '__selectors__' in klassdict and '__selgenerated__' not in klassdict:
       
   122                 cls.__selgenerated__ = True
       
   123                 # case where __selectors__ is defined locally (but __select__
       
   124                 # is in a parent class)
       
   125                 selectors = klassdict['__selectors__']
       
   126                 if len(selectors) == 1:
       
   127                     # micro optimization: don't bother with AndSelector if there's
       
   128                     # only one selector
       
   129                     select = _instantiate_selector(selectors[0])
       
   130                 else:
       
   131                     select = AndSelector(*selectors)
       
   132                 cls.__select__ = select
       
   133 
       
   134 
       
   135 class VRegistry(object):
       
   136     """class responsible to register, propose and select the various
       
   137     elements used to build the web interface. Currently, we have templates,
       
   138     views, actions and components.
       
   139     """
       
   140 
       
   141     def __init__(self, config):#, cache_size=1000):
       
   142         self.config = config
    68         self.config = config
   143         # dictionnary of registry (themself dictionnary) by name
    69 
   144         self._registries = {}
    70     def __getitem__(self, name):
   145         self._lastmodifs = {}
       
   146 
       
   147     def reset(self):
       
   148         self._registries = {}
       
   149         self._lastmodifs = {}
       
   150         if uicfg is not None:
       
   151             reload(uicfg)
       
   152 
       
   153     def __getitem__(self, key):
       
   154         return self._registries[key]
       
   155 
       
   156     def get(self, key, default=None):
       
   157         return self._registries.get(key, default)
       
   158 
       
   159     def items(self):
       
   160         return self._registries.items()
       
   161 
       
   162     def values(self):
       
   163         return self._registries.values()
       
   164 
       
   165     def __contains__(self, key):
       
   166         return key in self._registries
       
   167 
       
   168     def registry(self, name):
       
   169         """return the registry (dictionary of class objects) associated to
    71         """return the registry (dictionary of class objects) associated to
   170         this name
    72         this name
   171         """
    73         """
   172         try:
    74         try:
   173             return self._registries[name]
    75             return super(Registry, self).__getitem__(name)
   174         except KeyError:
    76         except KeyError:
   175             raise RegistryNotFound(name), None, sys.exc_info()[-1]
    77             raise ObjectNotFound(name), None, sys.exc_info()[-1]
   176 
    78 
   177     def registry_objects(self, name, oid=None):
    79     def register(self, obj, oid=None, clear=False):
   178         """returns objects registered with the given oid in the given registry.
    80         """base method to add an object in the registry"""
   179         If no oid is given, return all objects in this registry
    81         assert not '__abstract__' in obj.__dict__
   180         """
    82         oid = oid or obj.id
   181         registry = self.registry(name)
    83         assert oid
   182         if oid is not None:
    84         if clear:
   183             try:
    85             appobjects = self[oid] =  []
   184                 return registry[oid]
    86         else:
   185             except KeyError:
    87             appobjects = self.setdefault(oid, [])
   186                 raise ObjectNotFound(oid), None, sys.exc_info()[-1]
    88         # registered() is technically a classmethod but is not declared
       
    89         # as such because we need to compose registered in some cases
       
    90         appobject = obj.registered.im_func(obj, self)
       
    91         assert not appobject in appobjects, \
       
    92                'object %s is already registered' % appobject
       
    93         assert callable(appobject.__select__), appobject
       
    94         appobjects.append(appobject)
       
    95 
       
    96     def register_and_replace(self, obj, replaced):
       
    97         # XXXFIXME this is a duplication of unregister()
       
    98         # remove register_and_replace in favor of unregister + register
       
    99         # or simplify by calling unregister then register here
       
   100         if hasattr(replaced, 'classid'):
       
   101             replaced = replaced.classid()
       
   102         registered_objs = self.get(obj.id, ())
       
   103         for index, registered in enumerate(registered_objs):
       
   104             if registered.classid() == replaced:
       
   105                 del registered_objs[index]
       
   106                 break
       
   107         else:
       
   108             self.warning('trying to replace an unregistered view %s by %s',
       
   109                          replaced, obj)
       
   110         self.register(obj)
       
   111 
       
   112     def unregister(self, obj):
       
   113         oid = obj.classid()
       
   114         for registered in self.get(obj.id, ()):
       
   115             # use classid() to compare classes because vreg will probably
       
   116             # have its own version of the class, loaded through execfile
       
   117             if registered.classid() == oid:
       
   118                 # XXX automatic reloading management
       
   119                 self[obj.id].remove(registered)
       
   120                 break
       
   121         else:
       
   122             self.warning('can\'t remove %s, no id %s in the registry',
       
   123                          oid, obj.id)
       
   124 
       
   125     def all_objects(self):
       
   126         """return a list containing all objects in this registry.
       
   127         """
   187         result = []
   128         result = []
   188         for objs in registry.values():
   129         for objs in self.values():
   189             result += objs
   130             result += objs
   190         return result
   131         return result
   191 
   132 
   192     # dynamic selection methods ################################################
   133     # dynamic selection methods ################################################
   193 
   134 
   194     def object_by_id(self, registry, oid, *args, **kwargs):
   135     def object_by_id(self, oid, *args, **kwargs):
   195         """return object in <registry>.<oid>
   136         """return object with the given oid. Only one object is expected to be
       
   137         found.
   196 
   138 
   197         raise `ObjectNotFound` if not object with id <oid> in <registry>
   139         raise `ObjectNotFound` if not object with id <oid> in <registry>
   198         raise `AssertionError` if there is more than one object there
   140         raise `AssertionError` if there is more than one object there
   199         """
   141         """
   200         objects = self.registry_objects(registry, oid)
   142         objects = self[oid]
   201         assert len(objects) == 1, objects
   143         assert len(objects) == 1, objects
   202         return objects[0].selected(*args, **kwargs)
   144         return objects[0](*args, **kwargs)
   203 
   145 
   204     def select(self, registry, oid, *args, **kwargs):
   146     def select(self, oid, *args, **kwargs):
   205         """return the most specific object in <registry>.<oid> according to
   147         """return the most specific object among those with the given oid
   206         the given context
   148         according to the given context.
   207 
   149 
   208         raise `ObjectNotFound` if not object with id <oid> in <registry>
   150         raise `ObjectNotFound` if not object with id <oid> in <registry>
   209         raise `NoSelectableObject` if not object apply
   151         raise `NoSelectableObject` if not object apply
   210         """
   152         """
   211         return self.select_best(self.registry_objects(registry, oid),
   153         return self.select_best(self[oid], *args, **kwargs)
   212                                 *args, **kwargs)
   154 
   213 
   155     def select_object(self, oid, *args, **kwargs):
   214     def select_object(self, registry, oid, *args, **kwargs):
   156         """return the most specific object among those with the given oid
   215         """return the most specific object in <registry>.<oid> according to
   157         according to the given context, or None if no object applies.
   216         the given context, or None if no object apply
   158         """
   217         """
   159         try:
   218         try:
   160             return self.select(oid, *args, **kwargs)
   219             return self.select(registry, oid, *args, **kwargs)
       
   220         except (NoSelectableObject, ObjectNotFound):
   161         except (NoSelectableObject, ObjectNotFound):
   221             return None
   162             return None
   222 
   163 
   223     def possible_objects(self, registry, *args, **kwargs):
   164     def possible_objects(self, *args, **kwargs):
   224         """return an iterator on possible objects in <registry> for the given
   165         """return an iterator on possible objects in this registry for the given
   225         context
   166         context
   226         """
   167         """
   227         for vobjects in self.registry(registry).itervalues():
   168         for appobjects in self.itervalues():
   228             try:
   169             try:
   229                 yield self.select_best(vobjects, *args, **kwargs)
   170                 yield self.select_best(appobjects, *args, **kwargs)
   230             except NoSelectableObject:
   171             except NoSelectableObject:
   231                 continue
   172                 continue
   232 
   173 
   233     def select_best(self, vobjects, *args, **kwargs):
   174     def select_best(self, appobjects, *args, **kwargs):
   234         """return an instance of the most specific object according
   175         """return an instance of the most specific object according
   235         to parameters
   176         to parameters
   236 
   177 
   237         raise `NoSelectableObject` if not object apply
   178         raise `NoSelectableObject` if not object apply
   238         """
   179         """
   239         if len(args) > 1:
   180         if len(args) > 1:
   240             warn('only the request param can not be named when calling select',
   181             warn('only the request param can not be named when calling select',
   241                  DeprecationWarning, stacklevel=3)
   182                  DeprecationWarning, stacklevel=3)
   242         score, winners = 0, []
   183         score, winners = 0, []
   243         for vobject in vobjects:
   184         for appobject in appobjects:
   244             vobjectscore = vobject.__select__(vobject, *args, **kwargs)
   185             appobjectscore = appobject.__select__(appobject, *args, **kwargs)
   245             if vobjectscore > score:
   186             if appobjectscore > score:
   246                 score, winners = vobjectscore, [vobject]
   187                 score, winners = appobjectscore, [appobject]
   247             elif vobjectscore > 0 and vobjectscore == score:
   188             elif appobjectscore > 0 and appobjectscore == score:
   248                 winners.append(vobject)
   189                 winners.append(appobject)
   249         if not winners:
   190         if not winners:
   250             raise NoSelectableObject('args: %s\nkwargs: %s %s'
   191             raise NoSelectableObject('args: %s\nkwargs: %s %s'
   251                                      % (args, kwargs.keys(),
   192                                      % (args, kwargs.keys(),
   252                                         [repr(v) for v in vobjects]))
   193                                         [repr(v) for v in appobjects]))
   253         if len(winners) > 1:
   194         if len(winners) > 1:
   254             if self.config.mode == 'installed':
   195             if self.config.mode == 'installed':
   255                 self.error('select ambiguity, args: %s\nkwargs: %s %s',
   196                 self.error('select ambiguity, args: %s\nkwargs: %s %s',
   256                            args, kwargs.keys(), [repr(v) for v in winners])
   197                            args, kwargs.keys(), [repr(v) for v in winners])
   257             else:
   198             else:
   258                 raise Exception('select ambiguity, args: %s\nkwargs: %s %s'
   199                 raise Exception('select ambiguity, args: %s\nkwargs: %s %s'
   259                                 % (args, kwargs.keys(),
   200                                 % (args, kwargs.keys(),
   260                                    [repr(v) for v in winners]))
   201                                    [repr(v) for v in winners]))
   261         # return the result of the .selected method of the vobject
   202         # return the result of calling the appobject
   262         return winners[0].selected(*args, **kwargs)
   203         return winners[0](*args, **kwargs)
       
   204 
       
   205 
       
   206 class VRegistry(dict):
       
   207     """class responsible to register, propose and select the various
       
   208     elements used to build the web interface. Currently, we have templates,
       
   209     views, actions and components.
       
   210     """
       
   211 
       
   212     def __init__(self, config):
       
   213         super(VRegistry, self).__init__()
       
   214         self.config = config
       
   215 
       
   216     def reset(self, force_reload=None):
       
   217         self.clear()
       
   218         self._lastmodifs = {}
       
   219         # don't reload uicfg when appobjects modules won't be reloaded as well
       
   220         if uicfg is not None:
       
   221             if force_reload is None:
       
   222                 force_reload = self.config.mode == 'dev'
       
   223             if force_reload:
       
   224                 reload(uicfg)
       
   225 
       
   226     def __getitem__(self, name):
       
   227         """return the registry (dictionary of class objects) associated to
       
   228         this name
       
   229         """
       
   230         try:
       
   231             return super(VRegistry, self).__getitem__(name)
       
   232         except KeyError:
       
   233             raise RegistryNotFound(name), None, sys.exc_info()[-1]
       
   234 
       
   235     # dynamic selection methods ################################################
       
   236 
       
   237     @deprecated('use vreg[registry].object_by_id(oid, *args, **kwargs)')
       
   238     def object_by_id(self, registry, oid, *args, **kwargs):
       
   239         """return object in <registry>.<oid>
       
   240 
       
   241         raise `ObjectNotFound` if not object with id <oid> in <registry>
       
   242         raise `AssertionError` if there is more than one object there
       
   243         """
       
   244         return self[registry].object_by_id(oid)
       
   245 
       
   246     @deprecated('use vreg[registry].select(oid, *args, **kwargs)')
       
   247     def select(self, registry, oid, *args, **kwargs):
       
   248         """return the most specific object in <registry>.<oid> according to
       
   249         the given context
       
   250 
       
   251         raise `ObjectNotFound` if not object with id <oid> in <registry>
       
   252         raise `NoSelectableObject` if not object apply
       
   253         """
       
   254         return self[registry].select(oid, *args, **kwargs)
       
   255 
       
   256     @deprecated('use vreg[registry].select_object(oid, *args, **kwargs)')
       
   257     def select_object(self, registry, oid, *args, **kwargs):
       
   258         """return the most specific object in <registry>.<oid> according to
       
   259         the given context, or None if no object apply
       
   260         """
       
   261         return self[registry].select_object(oid, *args, **kwargs)
       
   262 
       
   263     @deprecated('use vreg[registry].possible_objects(*args, **kwargs)')
       
   264     def possible_objects(self, registry, *args, **kwargs):
       
   265         """return an iterator on possible objects in <registry> for the given
       
   266         context
       
   267         """
       
   268         return self[registry].possible_objects(*args, **kwargs)
   263 
   269 
   264     # methods for explicit (un)registration ###################################
   270     # methods for explicit (un)registration ###################################
       
   271 
       
   272     # default class, when no specific class set
       
   273     REGISTRY_FACTORY = {None: Registry}
       
   274 
       
   275     def registry_class(self, regid):
       
   276         try:
       
   277             return self.REGISTRY_FACTORY[regid]
       
   278         except KeyError:
       
   279             return self.REGISTRY_FACTORY[None]
       
   280 
       
   281     def setdefault(self, regid):
       
   282         try:
       
   283             return self[regid]
       
   284         except KeyError:
       
   285             self[regid] = self.registry_class(regid)(self.config)
       
   286             return self[regid]
   265 
   287 
   266 #     def clear(self, key):
   288 #     def clear(self, key):
   267 #         regname, oid = key.split('.')
   289 #         regname, oid = key.split('.')
   268 #         self[regname].pop(oid, None)
   290 #         self[regname].pop(oid, None)
   269 
   291 
   280 
   302 
   281     def register(self, obj, registryname=None, oid=None, clear=False):
   303     def register(self, obj, registryname=None, oid=None, clear=False):
   282         """base method to add an object in the registry"""
   304         """base method to add an object in the registry"""
   283         assert not '__abstract__' in obj.__dict__
   305         assert not '__abstract__' in obj.__dict__
   284         registryname = registryname or obj.__registry__
   306         registryname = registryname or obj.__registry__
   285         oid = oid or obj.id
   307         registry = self.setdefault(registryname)
   286         assert oid
   308         registry.register(obj, oid=oid, clear=clear)
   287         registry = self._registries.setdefault(registryname, {})
   309         try:
   288         if clear:
   310             vname = obj.__name__
   289             vobjects = registry[oid] =  []
       
   290         else:
       
   291             vobjects = registry.setdefault(oid, [])
       
   292         # registered() is technically a classmethod but is not declared
       
   293         # as such because we need to compose registered in some cases
       
   294         vobject = obj.registered.im_func(obj, self)
       
   295         assert not vobject in vobjects, \
       
   296                'object %s is already registered' % vobject
       
   297         assert callable(vobject.__select__), vobject
       
   298         vobjects.append(vobject)
       
   299         try:
       
   300             vname = vobject.__name__
       
   301         except AttributeError:
   311         except AttributeError:
   302             vname = vobject.__class__.__name__
   312             vname = obj.__class__.__name__
   303         self.debug('registered vobject %s in registry %s with id %s',
   313         self.debug('registered appobject %s in registry %s with id %s',
   304                    vname, registryname, oid)
   314                    vname, registryname, oid)
   305         self._loadedmods[obj.__module__]['%s.%s' % (obj.__module__, oid)] = obj
   315         self._loadedmods[obj.__module__]['%s.%s' % (obj.__module__, oid)] = obj
   306 
   316 
   307     def unregister(self, obj, registryname=None):
   317     def unregister(self, obj, registryname=None):
   308         registryname = registryname or obj.__registry__
   318         self[registryname or obj.__registry__].unregister(obj)
   309         registry = self.registry(registryname)
       
   310         removed_id = obj.classid()
       
   311         for registered in registry.get(obj.id, ()):
       
   312             # use classid() to compare classes because vreg will probably
       
   313             # have its own version of the class, loaded through execfile
       
   314             if registered.classid() == removed_id:
       
   315                 # XXX automatic reloading management
       
   316                 registry[obj.id].remove(registered)
       
   317                 break
       
   318         else:
       
   319             self.warning('can\'t remove %s, no id %s in the %s registry',
       
   320                          removed_id, obj.id, registryname)
       
   321 
   319 
   322     def register_and_replace(self, obj, replaced, registryname=None):
   320     def register_and_replace(self, obj, replaced, registryname=None):
   323         # XXXFIXME this is a duplication of unregister()
   321         self[registryname or obj.__registry__].register_and_replace(obj, replaced)
   324         # remove register_and_replace in favor of unregister + register
   322 
   325         # or simplify by calling unregister then register here
   323     # initialization methods ###################################################
   326         if hasattr(replaced, 'classid'):
       
   327             replaced = replaced.classid()
       
   328         registryname = registryname or obj.__registry__
       
   329         registry = self.registry(registryname)
       
   330         registered_objs = registry.get(obj.id, ())
       
   331         for index, registered in enumerate(registered_objs):
       
   332             if registered.classid() == replaced:
       
   333                 del registry[obj.id][index]
       
   334                 break
       
   335         else:
       
   336             self.warning('trying to replace an unregistered view %s by %s',
       
   337                          replaced, obj)
       
   338         self.register(obj, registryname=registryname)
       
   339 
       
   340     # intialization methods ###################################################
       
   341 
   324 
   342     def init_registration(self, path, extrapath=None):
   325     def init_registration(self, path, extrapath=None):
   343         # compute list of all modules that have to be loaded
   326         # compute list of all modules that have to be loaded
   344         self._toloadmods, filemods = _toload_info(path, extrapath)
   327         self._toloadmods, filemods = _toload_info(path, extrapath)
   345         # XXX is _loadedmods still necessary ? It seems like it's useful
   328         # XXX is _loadedmods still necessary ? It seems like it's useful
   419             if objmodname in self._toloadmods:
   402             if objmodname in self._toloadmods:
   420                 self.load_file(self._toloadmods[objmodname], objmodname)
   403                 self.load_file(self._toloadmods[objmodname], objmodname)
   421             return
   404             return
   422         # skip non registerable object
   405         # skip non registerable object
   423         try:
   406         try:
   424             if not issubclass(obj, VObject):
   407             if not issubclass(obj, AppObject):
   425                 return
   408                 return
   426         except TypeError:
   409         except TypeError:
   427             return
   410             return
   428         objname = '%s.%s' % (modname, obj.__name__)
   411         objname = '%s.%s' % (modname, obj.__name__)
   429         if objname in self._loadedmods[modname]:
   412         if objname in self._loadedmods[modname]:
   433             self._load_ancestors_then_object(modname, parent)
   416             self._load_ancestors_then_object(modname, parent)
   434         self.load_object(obj)
   417         self.load_object(obj)
   435 
   418 
   436     def load_object(self, obj):
   419     def load_object(self, obj):
   437         try:
   420         try:
   438             self.register_vobject_class(obj)
   421             self.register_appobject_class(obj)
   439         except Exception, ex:
   422         except Exception, ex:
   440             if self.config.mode in ('test', 'dev'):
   423             if self.config.mode in ('test', 'dev'):
   441                 raise
   424                 raise
   442             self.exception('vobject %s registration failed: %s', obj, ex)
   425             self.exception('appobject %s registration failed: %s', obj, ex)
   443 
   426 
   444     # old automatic registration XXX deprecated ###############################
   427     # old automatic registration XXX deprecated ###############################
   445 
   428 
   446     def register_vobject_class(self, cls):
   429     def register_appobject_class(self, cls):
   447         """handle vobject class registration
   430         """handle appobject class registration
   448 
   431 
   449         vobject class with __abstract__ == True in their local dictionnary or
   432         appobject class with __abstract__ == True in their local dictionnary or
   450         with a name starting starting by an underscore are not registered.
   433         with a name starting starting by an underscore are not registered.
   451         Also a vobject class needs to have __registry__ and id attributes set
   434         Also a appobject class needs to have __registry__ and id attributes set
   452         to a non empty string to be registered.
   435         to a non empty string to be registered.
   453         """
   436         """
   454         if (cls.__dict__.get('__abstract__') or cls.__name__[0] == '_'
   437         if (cls.__dict__.get('__abstract__') or cls.__name__[0] == '_'
   455             or not cls.__registry__ or not cls.id):
   438             or not cls.__registry__ or not cls.id):
   456             return
   439             return
   458         if '%s.%s' % (regname, cls.id) in self.config['disable-appobjects']:
   441         if '%s.%s' % (regname, cls.id) in self.config['disable-appobjects']:
   459             return
   442             return
   460         self.register(cls)
   443         self.register(cls)
   461 
   444 
   462 # init logging
   445 # init logging
   463 set_log_methods(VObject, getLogger('cubicweb'))
   446 set_log_methods(VRegistry, getLogger('cubicweb.vreg'))
   464 set_log_methods(VRegistry, getLogger('cubicweb.registry'))
   447 set_log_methods(Registry, getLogger('cubicweb.registry'))
   465 
       
   466 
       
   467 # selector base classes and operations ########################################
       
   468 
       
   469 class Selector(object):
       
   470     """base class for selector classes providing implementation
       
   471     for operators ``&`` and ``|``
       
   472 
       
   473     This class is only here to give access to binary operators, the
       
   474     selector logic itself should be implemented in the __call__ method
       
   475 
       
   476 
       
   477     a selector is called to help choosing the correct object for a
       
   478     particular context by returning a score (`int`) telling how well
       
   479     the class given as first argument apply to the given context.
       
   480 
       
   481     0 score means that the class doesn't apply.
       
   482     """
       
   483 
       
   484     @property
       
   485     def func_name(self):
       
   486         # backward compatibility
       
   487         return self.__class__.__name__
       
   488 
       
   489     def search_selector(self, selector):
       
   490         """search for the given selector or selector instance in the selectors
       
   491         tree. Return it of None if not found
       
   492         """
       
   493         if self is selector:
       
   494             return self
       
   495         if isinstance(selector, type) and isinstance(self, selector):
       
   496             return self
       
   497         return None
       
   498 
       
   499     def __str__(self):
       
   500         return self.__class__.__name__
       
   501 
       
   502     def __and__(self, other):
       
   503         return AndSelector(self, other)
       
   504     def __rand__(self, other):
       
   505         return AndSelector(other, self)
       
   506 
       
   507     def __or__(self, other):
       
   508         return OrSelector(self, other)
       
   509     def __ror__(self, other):
       
   510         return OrSelector(other, self)
       
   511 
       
   512     def __invert__(self):
       
   513         return NotSelector(self)
       
   514 
       
   515     # XXX (function | function) or (function & function) not managed yet
       
   516 
       
   517     def __call__(self, cls, *args, **kwargs):
       
   518         return NotImplementedError("selector %s must implement its logic "
       
   519                                    "in its __call__ method" % self.__class__)
       
   520 
       
   521 class MultiSelector(Selector):
       
   522     """base class for compound selector classes"""
       
   523 
       
   524     def __init__(self, *selectors):
       
   525         self.selectors = self.merge_selectors(selectors)
       
   526 
       
   527     def __str__(self):
       
   528         return '%s(%s)' % (self.__class__.__name__,
       
   529                            ','.join(str(s) for s in self.selectors))
       
   530 
       
   531     @classmethod
       
   532     def merge_selectors(cls, selectors):
       
   533         """deal with selector instanciation when necessary and merge
       
   534         multi-selectors if possible:
       
   535 
       
   536         AndSelector(AndSelector(sel1, sel2), AndSelector(sel3, sel4))
       
   537         ==> AndSelector(sel1, sel2, sel3, sel4)
       
   538         """
       
   539         merged_selectors = []
       
   540         for selector in selectors:
       
   541             try:
       
   542                 selector = _instantiate_selector(selector)
       
   543             except:
       
   544                 pass
       
   545             #assert isinstance(selector, Selector), selector
       
   546             if isinstance(selector, cls):
       
   547                 merged_selectors += selector.selectors
       
   548             else:
       
   549                 merged_selectors.append(selector)
       
   550         return merged_selectors
       
   551 
       
   552     def search_selector(self, selector):
       
   553         """search for the given selector or selector instance in the selectors
       
   554         tree. Return it of None if not found
       
   555         """
       
   556         for childselector in self.selectors:
       
   557             if childselector is selector:
       
   558                 return childselector
       
   559             found = childselector.search_selector(selector)
       
   560             if found is not None:
       
   561                 return found
       
   562         return None
       
   563 
       
   564 
       
   565 def objectify_selector(selector_func):
       
   566     """convenience decorator for simple selectors where a class definition
       
   567     would be overkill::
       
   568 
       
   569         @objectify_selector
       
   570         def yes(cls, *args, **kwargs):
       
   571             return 1
       
   572 
       
   573     """
       
   574     return type(selector_func.__name__, (Selector,),
       
   575                 {'__call__': lambda self, *args, **kwargs: selector_func(*args, **kwargs)})
       
   576 
       
   577 def _instantiate_selector(selector):
       
   578     """ensures `selector` is a `Selector` instance
       
   579 
       
   580     NOTE: This should only be used locally in build___select__()
       
   581     XXX: then, why not do it ??
       
   582     """
       
   583     if isinstance(selector, types.FunctionType):
       
   584         return objectify_selector(selector)()
       
   585     if isinstance(selector, type) and issubclass(selector, Selector):
       
   586         return selector()
       
   587     return selector
       
   588 
       
   589 
       
   590 class AndSelector(MultiSelector):
       
   591     """and-chained selectors (formerly known as chainall)"""
       
   592     def __call__(self, cls, *args, **kwargs):
       
   593         score = 0
       
   594         for selector in self.selectors:
       
   595             partscore = selector(cls, *args, **kwargs)
       
   596             if not partscore:
       
   597                 return 0
       
   598             score += partscore
       
   599         return score
       
   600 
       
   601 
       
   602 class OrSelector(MultiSelector):
       
   603     """or-chained selectors (formerly known as chainfirst)"""
       
   604     def __call__(self, cls, *args, **kwargs):
       
   605         for selector in self.selectors:
       
   606             partscore = selector(cls, *args, **kwargs)
       
   607             if partscore:
       
   608                 return partscore
       
   609         return 0
       
   610 
       
   611 class NotSelector(Selector):
       
   612     """negation selector"""
       
   613     def __init__(self, selector):
       
   614         self.selector = selector
       
   615 
       
   616     def __call__(self, cls, *args, **kwargs):
       
   617         score = self.selector(cls, *args, **kwargs)
       
   618         return int(not score)
       
   619 
       
   620     def __str__(self):
       
   621         return 'NOT(%s)' % super(NotSelector, self).__str__()
       
   622 
   448 
   623 
   449 
   624 # XXX bw compat functions #####################################################
   450 # XXX bw compat functions #####################################################
   625 
   451 
       
   452 from cubicweb.appobject import objectify_selector, AndSelector, OrSelector, Selector
       
   453 
       
   454 objectify_selector = deprecated('objectify_selector has been moved to appobject module')(objectify_selector)
       
   455 
       
   456 Selector = class_moved(Selector)
       
   457 
       
   458 @deprecated('use & operator (binary and)')
   626 def chainall(*selectors, **kwargs):
   459 def chainall(*selectors, **kwargs):
   627     """return a selector chaining given selectors. If one of
   460     """return a selector chaining given selectors. If one of
   628     the selectors fail, selection will fail, else the returned score
   461     the selectors fail, selection will fail, else the returned score
   629     will be the sum of each selector'score
   462     will be the sum of each selector'score
   630     """
   463     """
   633     selector = AndSelector(*selectors)
   466     selector = AndSelector(*selectors)
   634     if 'name' in kwargs:
   467     if 'name' in kwargs:
   635         selector.__name__ = kwargs['name']
   468         selector.__name__ = kwargs['name']
   636     return selector
   469     return selector
   637 
   470 
       
   471 @deprecated('use | operator (binary or)')
   638 def chainfirst(*selectors, **kwargs):
   472 def chainfirst(*selectors, **kwargs):
   639     """return a selector chaining given selectors. If all
   473     """return a selector chaining given selectors. If all
   640     the selectors fail, selection will fail, else the returned score
   474     the selectors fail, selection will fail, else the returned score
   641     will be the first non-zero selector score
   475     will be the first non-zero selector score
   642     """
   476     """