--- 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