vregistry.py
changeset 2675 f84ba1a66abb
parent 2662 87aed8cb7ff6
child 2689 44f041222d0f
--- a/vregistry.py	Tue Aug 04 15:06:09 2009 +0200
+++ b/vregistry.py	Tue Aug 04 15:08:18 2009 +0200
@@ -5,13 +5,13 @@
   according to a context
 
 * to interact with the vregistry, objects should inherit from the
-  VObject abstract class
+  AppObject abstract class
 
 * the selection procedure has been generalized by delegating to a
-  selector, which is responsible to score the vobject according to the
+  selector, which is responsible to score the appobject according to the
   current state (req, rset, row, col). At the end of the selection, if
-  a vobject class has been found, an instance of this class is
-  returned. The selector is instantiated at vobject registration
+  a appobject class has been found, an instance of this class is
+  returned. The selector is instantiated at appobject registration
 
 
 :organization: Logilab
@@ -22,15 +22,18 @@
 __docformat__ = "restructuredtext en"
 
 import sys
-import types
 from os import listdir, stat
 from os.path import dirname, join, realpath, split, isdir, exists
 from logging import getLogger
 from warnings import warn
 
-from cubicweb import CW_SOFTWARE_ROOT, set_log_methods
+from logilab.common.deprecation import deprecated, class_moved
+from logilab.common.logging_ext import set_log_methods
+
+from cubicweb import CW_SOFTWARE_ROOT
 from cubicweb import (RegistryNotFound, ObjectNotFound, NoSelectableObject,
                       RegistryOutOfDate)
+from cubicweb.appobject import AppObject
 
 # XXX depending on cubicweb.web is ugly, we should deal with uicfg
 #     reset with a good old event / callback system
@@ -58,179 +61,117 @@
     return _toload
 
 
-class VObject(object):
-    """visual object, use to be handled somehow by the visual components
-    registry.
-
-    The following attributes should be set on concret vobject subclasses:
-
-    :__registry__:
-      name of the registry for this object (string like 'views',
-      'templates'...)
-    :id:
-      object's identifier in the registry (string like 'main',
-      'primary', 'folder_box')
-    :__select__:
-      class'selector
-
-    Moreover, the `__abstract__` attribute may be set to True to indicate
-    that a vobject is abstract and should not be registered
-    """
-    # necessary attributes to interact with the registry
-    id = None
-    __registry__ = None
-    __select__ = None
-
-    @classmethod
-    def registered(cls, registry):
-        """called by the registry when the vobject has been registered.
-
-        It must return the  object that will be actually registered (this
-        may be the right hook to create an instance for example). By
-        default the vobject is returned without any transformation.
-        """
-        cls.build___select__()
-        return cls
-
-    @classmethod
-    def selected(cls, *args, **kwargs):
-        """called by the registry when the vobject has been selected.
-
-        It must return the  object that will be actually returned by the
-        .select method (this may be the right hook to create an
-        instance for example). By default the selected object is
-        returned without any transformation.
-        """
-        return cls
-
-    @classmethod
-    def classid(cls):
-        """returns a unique identifier for the vobject"""
-        return '%s.%s' % (cls.__module__, cls.__name__)
+class Registry(dict):
 
-    # XXX bw compat code
-    @classmethod
-    def build___select__(cls):
-        for klass in cls.mro():
-            if klass.__name__ == 'AppRsetObject':
-                continue # the bw compat __selector__ is there
-            klassdict = klass.__dict__
-            if ('__select__' in klassdict and '__selectors__' in klassdict
-                and '__selgenerated__' not in klassdict):
-                raise TypeError("__select__ and __selectors__ can't be used together on class %s" % cls)
-            if '__selectors__' in klassdict and '__selgenerated__' not in klassdict:
-                cls.__selgenerated__ = True
-                # case where __selectors__ is defined locally (but __select__
-                # is in a parent class)
-                selectors = klassdict['__selectors__']
-                if len(selectors) == 1:
-                    # micro optimization: don't bother with AndSelector if there's
-                    # only one selector
-                    select = _instantiate_selector(selectors[0])
-                else:
-                    select = AndSelector(*selectors)
-                cls.__select__ = select
-
+    def __init__(self, config):
+        super(Registry, self).__init__()
+        self.config = config
 
-class VRegistry(object):
-    """class responsible to register, propose and select the various
-    elements used to build the web interface. Currently, we have templates,
-    views, actions and components.
-    """
-
-    def __init__(self, config):#, cache_size=1000):
-        self.config = config
-        # dictionnary of registry (themself dictionnary) by name
-        self._registries = {}
-        self._lastmodifs = {}
-
-    def reset(self):
-        self._registries = {}
-        self._lastmodifs = {}
-        if uicfg is not None:
-            reload(uicfg)
-
-    def __getitem__(self, key):
-        return self._registries[key]
-
-    def get(self, key, default=None):
-        return self._registries.get(key, default)
-
-    def items(self):
-        return self._registries.items()
-
-    def values(self):
-        return self._registries.values()
-
-    def __contains__(self, key):
-        return key in self._registries
-
-    def registry(self, name):
+    def __getitem__(self, name):
         """return the registry (dictionary of class objects) associated to
         this name
         """
         try:
-            return self._registries[name]
+            return super(Registry, self).__getitem__(name)
         except KeyError:
-            raise RegistryNotFound(name), None, sys.exc_info()[-1]
+            raise ObjectNotFound(name), None, sys.exc_info()[-1]
+
+    def register(self, obj, oid=None, clear=False):
+        """base method to add an object in the registry"""
+        assert not '__abstract__' in obj.__dict__
+        oid = oid or obj.id
+        assert oid
+        if clear:
+            appobjects = self[oid] =  []
+        else:
+            appobjects = self.setdefault(oid, [])
+        # registered() is technically a classmethod but is not declared
+        # as such because we need to compose registered in some cases
+        appobject = obj.registered.im_func(obj, self)
+        assert not appobject in appobjects, \
+               'object %s is already registered' % appobject
+        assert callable(appobject.__select__), appobject
+        appobjects.append(appobject)
 
-    def registry_objects(self, name, oid=None):
-        """returns objects registered with the given oid in the given registry.
-        If no oid is given, return all objects in this registry
+    def register_and_replace(self, obj, replaced):
+        # XXXFIXME this is a duplication of unregister()
+        # remove register_and_replace in favor of unregister + register
+        # or simplify by calling unregister then register here
+        if hasattr(replaced, 'classid'):
+            replaced = replaced.classid()
+        registered_objs = self.get(obj.id, ())
+        for index, registered in enumerate(registered_objs):
+            if registered.classid() == replaced:
+                del registered_objs[index]
+                break
+        else:
+            self.warning('trying to replace an unregistered view %s by %s',
+                         replaced, obj)
+        self.register(obj)
+
+    def unregister(self, obj):
+        oid = obj.classid()
+        for registered in self.get(obj.id, ()):
+            # use classid() to compare classes because vreg will probably
+            # have its own version of the class, loaded through execfile
+            if registered.classid() == oid:
+                # XXX automatic reloading management
+                self[obj.id].remove(registered)
+                break
+        else:
+            self.warning('can\'t remove %s, no id %s in the registry',
+                         oid, obj.id)
+
+    def all_objects(self):
+        """return a list containing all objects in this registry.
         """
-        registry = self.registry(name)
-        if oid is not None:
-            try:
-                return registry[oid]
-            except KeyError:
-                raise ObjectNotFound(oid), None, sys.exc_info()[-1]
         result = []
-        for objs in registry.values():
+        for objs in self.values():
             result += objs
         return result
 
     # dynamic selection methods ################################################
 
-    def object_by_id(self, registry, oid, *args, **kwargs):
-        """return object in <registry>.<oid>
+    def object_by_id(self, oid, *args, **kwargs):
+        """return object with the given oid. Only one object is expected to be
+        found.
 
         raise `ObjectNotFound` if not object with id <oid> in <registry>
         raise `AssertionError` if there is more than one object there
         """
-        objects = self.registry_objects(registry, oid)
+        objects = self[oid]
         assert len(objects) == 1, objects
-        return objects[0].selected(*args, **kwargs)
+        return objects[0](*args, **kwargs)
 
-    def select(self, registry, oid, *args, **kwargs):
-        """return the most specific object in <registry>.<oid> according to
-        the given context
+    def select(self, oid, *args, **kwargs):
+        """return the most specific object among those with the given oid
+        according to the given context.
 
         raise `ObjectNotFound` if not object with id <oid> in <registry>
         raise `NoSelectableObject` if not object apply
         """
-        return self.select_best(self.registry_objects(registry, oid),
-                                *args, **kwargs)
+        return self.select_best(self[oid], *args, **kwargs)
 
-    def select_object(self, registry, oid, *args, **kwargs):
-        """return the most specific object in <registry>.<oid> according to
-        the given context, or None if no object apply
+    def select_object(self, oid, *args, **kwargs):
+        """return the most specific object among those with the given oid
+        according to the given context, or None if no object applies.
         """
         try:
-            return self.select(registry, oid, *args, **kwargs)
+            return self.select(oid, *args, **kwargs)
         except (NoSelectableObject, ObjectNotFound):
             return None
 
-    def possible_objects(self, registry, *args, **kwargs):
-        """return an iterator on possible objects in <registry> for the given
+    def possible_objects(self, *args, **kwargs):
+        """return an iterator on possible objects in this registry for the given
         context
         """
-        for vobjects in self.registry(registry).itervalues():
+        for appobjects in self.itervalues():
             try:
-                yield self.select_best(vobjects, *args, **kwargs)
+                yield self.select_best(appobjects, *args, **kwargs)
             except NoSelectableObject:
                 continue
 
-    def select_best(self, vobjects, *args, **kwargs):
+    def select_best(self, appobjects, *args, **kwargs):
         """return an instance of the most specific object according
         to parameters
 
@@ -240,16 +181,16 @@
             warn('only the request param can not be named when calling select',
                  DeprecationWarning, stacklevel=3)
         score, winners = 0, []
-        for vobject in vobjects:
-            vobjectscore = vobject.__select__(vobject, *args, **kwargs)
-            if vobjectscore > score:
-                score, winners = vobjectscore, [vobject]
-            elif vobjectscore > 0 and vobjectscore == score:
-                winners.append(vobject)
+        for appobject in appobjects:
+            appobjectscore = appobject.__select__(appobject, *args, **kwargs)
+            if appobjectscore > score:
+                score, winners = appobjectscore, [appobject]
+            elif appobjectscore > 0 and appobjectscore == score:
+                winners.append(appobject)
         if not winners:
             raise NoSelectableObject('args: %s\nkwargs: %s %s'
                                      % (args, kwargs.keys(),
-                                        [repr(v) for v in vobjects]))
+                                        [repr(v) for v in appobjects]))
         if len(winners) > 1:
             if self.config.mode == 'installed':
                 self.error('select ambiguity, args: %s\nkwargs: %s %s',
@@ -258,11 +199,92 @@
                 raise Exception('select ambiguity, args: %s\nkwargs: %s %s'
                                 % (args, kwargs.keys(),
                                    [repr(v) for v in winners]))
-        # return the result of the .selected method of the vobject
-        return winners[0].selected(*args, **kwargs)
+        # return the result of calling the appobject
+        return winners[0](*args, **kwargs)
+
+
+class VRegistry(dict):
+    """class responsible to register, propose and select the various
+    elements used to build the web interface. Currently, we have templates,
+    views, actions and components.
+    """
+
+    def __init__(self, config):
+        super(VRegistry, self).__init__()
+        self.config = config
+
+    def reset(self, force_reload=None):
+        self.clear()
+        self._lastmodifs = {}
+        # don't reload uicfg when appobjects modules won't be reloaded as well
+        if uicfg is not None:
+            if force_reload is None:
+                force_reload = self.config.mode == 'dev'
+            if force_reload:
+                reload(uicfg)
+
+    def __getitem__(self, name):
+        """return the registry (dictionary of class objects) associated to
+        this name
+        """
+        try:
+            return super(VRegistry, self).__getitem__(name)
+        except KeyError:
+            raise RegistryNotFound(name), None, sys.exc_info()[-1]
+
+    # dynamic selection methods ################################################
+
+    @deprecated('use vreg[registry].object_by_id(oid, *args, **kwargs)')
+    def object_by_id(self, registry, oid, *args, **kwargs):
+        """return object in <registry>.<oid>
+
+        raise `ObjectNotFound` if not object with id <oid> in <registry>
+        raise `AssertionError` if there is more than one object there
+        """
+        return self[registry].object_by_id(oid)
+
+    @deprecated('use vreg[registry].select(oid, *args, **kwargs)')
+    def select(self, registry, oid, *args, **kwargs):
+        """return the most specific object in <registry>.<oid> according to
+        the given context
+
+        raise `ObjectNotFound` if not object with id <oid> in <registry>
+        raise `NoSelectableObject` if not object apply
+        """
+        return self[registry].select(oid, *args, **kwargs)
+
+    @deprecated('use vreg[registry].select_object(oid, *args, **kwargs)')
+    def select_object(self, registry, oid, *args, **kwargs):
+        """return the most specific object in <registry>.<oid> according to
+        the given context, or None if no object apply
+        """
+        return self[registry].select_object(oid, *args, **kwargs)
+
+    @deprecated('use vreg[registry].possible_objects(*args, **kwargs)')
+    def possible_objects(self, registry, *args, **kwargs):
+        """return an iterator on possible objects in <registry> for the given
+        context
+        """
+        return self[registry].possible_objects(*args, **kwargs)
 
     # methods for explicit (un)registration ###################################
 
+    # default class, when no specific class set
+    REGISTRY_FACTORY = {None: Registry}
+
+    def registry_class(self, regid):
+        try:
+            return self.REGISTRY_FACTORY[regid]
+        except KeyError:
+            return self.REGISTRY_FACTORY[None]
+
+    def setdefault(self, regid):
+        try:
+            return self[regid]
+        except KeyError:
+            self[regid] = self.registry_class(regid)(self.config)
+            return self[regid]
+
 #     def clear(self, key):
 #         regname, oid = key.split('.')
 #         self[regname].pop(oid, None)
@@ -282,62 +304,23 @@
         """base method to add an object in the registry"""
         assert not '__abstract__' in obj.__dict__
         registryname = registryname or obj.__registry__
-        oid = oid or obj.id
-        assert oid
-        registry = self._registries.setdefault(registryname, {})
-        if clear:
-            vobjects = registry[oid] =  []
-        else:
-            vobjects = registry.setdefault(oid, [])
-        # registered() is technically a classmethod but is not declared
-        # as such because we need to compose registered in some cases
-        vobject = obj.registered.im_func(obj, self)
-        assert not vobject in vobjects, \
-               'object %s is already registered' % vobject
-        assert callable(vobject.__select__), vobject
-        vobjects.append(vobject)
+        registry = self.setdefault(registryname)
+        registry.register(obj, oid=oid, clear=clear)
         try:
-            vname = vobject.__name__
+            vname = obj.__name__
         except AttributeError:
-            vname = vobject.__class__.__name__
-        self.debug('registered vobject %s in registry %s with id %s',
+            vname = obj.__class__.__name__
+        self.debug('registered appobject %s in registry %s with id %s',
                    vname, registryname, oid)
         self._loadedmods[obj.__module__]['%s.%s' % (obj.__module__, oid)] = obj
 
     def unregister(self, obj, registryname=None):
-        registryname = registryname or obj.__registry__
-        registry = self.registry(registryname)
-        removed_id = obj.classid()
-        for registered in registry.get(obj.id, ()):
-            # use classid() to compare classes because vreg will probably
-            # have its own version of the class, loaded through execfile
-            if registered.classid() == removed_id:
-                # XXX automatic reloading management
-                registry[obj.id].remove(registered)
-                break
-        else:
-            self.warning('can\'t remove %s, no id %s in the %s registry',
-                         removed_id, obj.id, registryname)
+        self[registryname or obj.__registry__].unregister(obj)
 
     def register_and_replace(self, obj, replaced, registryname=None):
-        # XXXFIXME this is a duplication of unregister()
-        # remove register_and_replace in favor of unregister + register
-        # or simplify by calling unregister then register here
-        if hasattr(replaced, 'classid'):
-            replaced = replaced.classid()
-        registryname = registryname or obj.__registry__
-        registry = self.registry(registryname)
-        registered_objs = registry.get(obj.id, ())
-        for index, registered in enumerate(registered_objs):
-            if registered.classid() == replaced:
-                del registry[obj.id][index]
-                break
-        else:
-            self.warning('trying to replace an unregistered view %s by %s',
-                         replaced, obj)
-        self.register(obj, registryname=registryname)
+        self[registryname or obj.__registry__].register_and_replace(obj, replaced)
 
-    # intialization methods ###################################################
+    # initialization methods ###################################################
 
     def init_registration(self, path, extrapath=None):
         # compute list of all modules that have to be loaded
@@ -421,7 +404,7 @@
             return
         # skip non registerable object
         try:
-            if not issubclass(obj, VObject):
+            if not issubclass(obj, AppObject):
                 return
         except TypeError:
             return
@@ -435,20 +418,20 @@
 
     def load_object(self, obj):
         try:
-            self.register_vobject_class(obj)
+            self.register_appobject_class(obj)
         except Exception, ex:
             if self.config.mode in ('test', 'dev'):
                 raise
-            self.exception('vobject %s registration failed: %s', obj, ex)
+            self.exception('appobject %s registration failed: %s', obj, ex)
 
     # old automatic registration XXX deprecated ###############################
 
-    def register_vobject_class(self, cls):
-        """handle vobject class registration
+    def register_appobject_class(self, cls):
+        """handle appobject class registration
 
-        vobject class with __abstract__ == True in their local dictionnary or
+        appobject class with __abstract__ == True in their local dictionnary or
         with a name starting starting by an underscore are not registered.
-        Also a vobject class needs to have __registry__ and id attributes set
+        Also a appobject class needs to have __registry__ and id attributes set
         to a non empty string to be registered.
         """
         if (cls.__dict__.get('__abstract__') or cls.__name__[0] == '_'
@@ -460,169 +443,19 @@
         self.register(cls)
 
 # init logging
-set_log_methods(VObject, getLogger('cubicweb'))
-set_log_methods(VRegistry, getLogger('cubicweb.registry'))
-
-
-# selector base classes and operations ########################################
-
-class Selector(object):
-    """base class for selector classes providing implementation
-    for operators ``&`` and ``|``
-
-    This class is only here to give access to binary operators, the
-    selector logic itself should be implemented in the __call__ method
-
-
-    a selector is called to help choosing the correct object for a
-    particular context by returning a score (`int`) telling how well
-    the class given as first argument apply to the given context.
-
-    0 score means that the class doesn't apply.
-    """
-
-    @property
-    def func_name(self):
-        # backward compatibility
-        return self.__class__.__name__
-
-    def search_selector(self, selector):
-        """search for the given selector or selector instance in the selectors
-        tree. Return it of None if not found
-        """
-        if self is selector:
-            return self
-        if isinstance(selector, type) and isinstance(self, selector):
-            return self
-        return None
-
-    def __str__(self):
-        return self.__class__.__name__
-
-    def __and__(self, other):
-        return AndSelector(self, other)
-    def __rand__(self, other):
-        return AndSelector(other, self)
-
-    def __or__(self, other):
-        return OrSelector(self, other)
-    def __ror__(self, other):
-        return OrSelector(other, self)
-
-    def __invert__(self):
-        return NotSelector(self)
-
-    # XXX (function | function) or (function & function) not managed yet
-
-    def __call__(self, cls, *args, **kwargs):
-        return NotImplementedError("selector %s must implement its logic "
-                                   "in its __call__ method" % self.__class__)
-
-class MultiSelector(Selector):
-    """base class for compound selector classes"""
-
-    def __init__(self, *selectors):
-        self.selectors = self.merge_selectors(selectors)
-
-    def __str__(self):
-        return '%s(%s)' % (self.__class__.__name__,
-                           ','.join(str(s) for s in self.selectors))
-
-    @classmethod
-    def merge_selectors(cls, selectors):
-        """deal with selector instanciation when necessary and merge
-        multi-selectors if possible:
-
-        AndSelector(AndSelector(sel1, sel2), AndSelector(sel3, sel4))
-        ==> AndSelector(sel1, sel2, sel3, sel4)
-        """
-        merged_selectors = []
-        for selector in selectors:
-            try:
-                selector = _instantiate_selector(selector)
-            except:
-                pass
-            #assert isinstance(selector, Selector), selector
-            if isinstance(selector, cls):
-                merged_selectors += selector.selectors
-            else:
-                merged_selectors.append(selector)
-        return merged_selectors
-
-    def search_selector(self, selector):
-        """search for the given selector or selector instance in the selectors
-        tree. Return it of None if not found
-        """
-        for childselector in self.selectors:
-            if childselector is selector:
-                return childselector
-            found = childselector.search_selector(selector)
-            if found is not None:
-                return found
-        return None
-
-
-def objectify_selector(selector_func):
-    """convenience decorator for simple selectors where a class definition
-    would be overkill::
-
-        @objectify_selector
-        def yes(cls, *args, **kwargs):
-            return 1
-
-    """
-    return type(selector_func.__name__, (Selector,),
-                {'__call__': lambda self, *args, **kwargs: selector_func(*args, **kwargs)})
-
-def _instantiate_selector(selector):
-    """ensures `selector` is a `Selector` instance
-
-    NOTE: This should only be used locally in build___select__()
-    XXX: then, why not do it ??
-    """
-    if isinstance(selector, types.FunctionType):
-        return objectify_selector(selector)()
-    if isinstance(selector, type) and issubclass(selector, Selector):
-        return selector()
-    return selector
-
-
-class AndSelector(MultiSelector):
-    """and-chained selectors (formerly known as chainall)"""
-    def __call__(self, cls, *args, **kwargs):
-        score = 0
-        for selector in self.selectors:
-            partscore = selector(cls, *args, **kwargs)
-            if not partscore:
-                return 0
-            score += partscore
-        return score
-
-
-class OrSelector(MultiSelector):
-    """or-chained selectors (formerly known as chainfirst)"""
-    def __call__(self, cls, *args, **kwargs):
-        for selector in self.selectors:
-            partscore = selector(cls, *args, **kwargs)
-            if partscore:
-                return partscore
-        return 0
-
-class NotSelector(Selector):
-    """negation selector"""
-    def __init__(self, selector):
-        self.selector = selector
-
-    def __call__(self, cls, *args, **kwargs):
-        score = self.selector(cls, *args, **kwargs)
-        return int(not score)
-
-    def __str__(self):
-        return 'NOT(%s)' % super(NotSelector, self).__str__()
+set_log_methods(VRegistry, getLogger('cubicweb.vreg'))
+set_log_methods(Registry, getLogger('cubicweb.registry'))
 
 
 # XXX bw compat functions #####################################################
 
+from cubicweb.appobject import objectify_selector, AndSelector, OrSelector, Selector
+
+objectify_selector = deprecated('objectify_selector has been moved to appobject module')(objectify_selector)
+
+Selector = class_moved(Selector)
+
+@deprecated('use & operator (binary and)')
 def chainall(*selectors, **kwargs):
     """return a selector chaining given selectors. If one of
     the selectors fail, selection will fail, else the returned score
@@ -635,6 +468,7 @@
         selector.__name__ = kwargs['name']
     return selector
 
+@deprecated('use | operator (binary or)')
 def chainfirst(*selectors, **kwargs):
     """return a selector chaining given selectors. If all
     the selectors fail, selection will fail, else the returned score