| Index: third_party/logilab/common/registry.py
 | 
| ===================================================================
 | 
| --- third_party/logilab/common/registry.py	(revision 0)
 | 
| +++ third_party/logilab/common/registry.py	(working copy)
 | 
| @@ -0,0 +1,1119 @@
 | 
| +# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 | 
| +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
 | 
| +#
 | 
| +# This file is part of Logilab-common.
 | 
| +#
 | 
| +# Logilab-common is free software: you can redistribute it and/or modify it
 | 
| +# under the terms of the GNU Lesser General Public License as published by the
 | 
| +# Free Software Foundation, either version 2.1 of the License, or (at your
 | 
| +# option) any later version.
 | 
| +#
 | 
| +# Logilab-common is distributed in the hope that it will be useful, but WITHOUT
 | 
| +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | 
| +# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
 | 
| +# details.
 | 
| +#
 | 
| +# You should have received a copy of the GNU Lesser General Public License along
 | 
| +# with Logilab-common.  If not, see <http://www.gnu.org/licenses/>.
 | 
| +"""This module provides bases for predicates dispatching (the pattern in use
 | 
| +here is similar to what's refered as multi-dispatch or predicate-dispatch in the
 | 
| +literature, though a bit different since the idea is to select across different
 | 
| +implementation 'e.g. classes), not to dispatch a message to a function or
 | 
| +method. It contains the following classes:
 | 
| +
 | 
| +* :class:`RegistryStore`, the top level object which loads implementation
 | 
| +  objects and stores them into registries. You'll usually use it to access
 | 
| +  registries and their contained objects;
 | 
| +
 | 
| +* :class:`Registry`, the base class which contains objects semantically grouped
 | 
| +  (for instance, sharing a same API, hence the 'implementation' name). You'll
 | 
| +  use it to select the proper implementation according to a context. Notice you
 | 
| +  may use registries on their own without using the store.
 | 
| +
 | 
| +.. Note::
 | 
| +
 | 
| +  implementation objects are usually designed to be accessed through the
 | 
| +  registry and not by direct instantiation, besides to use it as base classe.
 | 
| +
 | 
| +The selection procedure is delegated to a selector, which is responsible for
 | 
| +scoring the object according to some context. At the end of the selection, if an
 | 
| +implementation has been found, an instance of this class is returned. A selector
 | 
| +is built from one or more predicates combined together using AND, OR, NOT
 | 
| +operators (actually `&`, `|` and `~`). You'll thus find some base classes to
 | 
| +build predicates:
 | 
| +
 | 
| +* :class:`Predicate`, the abstract base predicate class
 | 
| +
 | 
| +* :class:`AndPredicate`, :class:`OrPredicate`, :class:`NotPredicate`, which you
 | 
| +  shouldn't have to use directly. You'll use `&`, `|` and '~' operators between
 | 
| +  predicates directly
 | 
| +
 | 
| +* :func:`objectify_predicate`
 | 
| +
 | 
| +You'll eventually find one concrete predicate: :class:`yes`
 | 
| +
 | 
| +.. autoclass:: RegistryStore
 | 
| +.. autoclass:: Registry
 | 
| +
 | 
| +Predicates
 | 
| +----------
 | 
| +.. autoclass:: Predicate
 | 
| +.. autofunc:: objectify_predicate
 | 
| +.. autoclass:: yes
 | 
| +
 | 
| +Debugging
 | 
| +---------
 | 
| +.. autoclass:: traced_selection
 | 
| +
 | 
| +Exceptions
 | 
| +----------
 | 
| +.. autoclass:: RegistryException
 | 
| +.. autoclass:: RegistryNotFound
 | 
| +.. autoclass:: ObjectNotFound
 | 
| +.. autoclass:: NoSelectableObject
 | 
| +"""
 | 
| +
 | 
| +from __future__ import print_function
 | 
| +
 | 
| +__docformat__ = "restructuredtext en"
 | 
| +
 | 
| +import sys
 | 
| +import types
 | 
| +import weakref
 | 
| +import traceback as tb
 | 
| +from os import listdir, stat
 | 
| +from os.path import join, isdir, exists
 | 
| +from logging import getLogger
 | 
| +from warnings import warn
 | 
| +
 | 
| +from six import string_types, add_metaclass
 | 
| +
 | 
| +from logilab.common.modutils import modpath_from_file
 | 
| +from logilab.common.logging_ext import set_log_methods
 | 
| +from logilab.common.decorators import classproperty
 | 
| +
 | 
| +
 | 
| +class RegistryException(Exception):
 | 
| +    """Base class for registry exception."""
 | 
| +
 | 
| +class RegistryNotFound(RegistryException):
 | 
| +    """Raised when an unknown registry is requested.
 | 
| +
 | 
| +    This is usually a programming/typo error.
 | 
| +    """
 | 
| +
 | 
| +class ObjectNotFound(RegistryException):
 | 
| +    """Raised when an unregistered object is requested.
 | 
| +
 | 
| +    This may be a programming/typo or a misconfiguration error.
 | 
| +    """
 | 
| +
 | 
| +class NoSelectableObject(RegistryException):
 | 
| +    """Raised when no object is selectable for a given context."""
 | 
| +    def __init__(self, args, kwargs, objects):
 | 
| +        self.args = args
 | 
| +        self.kwargs = kwargs
 | 
| +        self.objects = objects
 | 
| +
 | 
| +    def __str__(self):
 | 
| +        return ('args: %s, kwargs: %s\ncandidates: %s'
 | 
| +                % (self.args, self.kwargs.keys(), self.objects))
 | 
| +
 | 
| +
 | 
| +def _modname_from_path(path, extrapath=None):
 | 
| +    modpath = modpath_from_file(path, extrapath)
 | 
| +    # omit '__init__' from package's name to avoid loading that module
 | 
| +    # once for each name when it is imported by some other object
 | 
| +    # module. This supposes import in modules are done as::
 | 
| +    #
 | 
| +    #   from package import something
 | 
| +    #
 | 
| +    # not::
 | 
| +    #
 | 
| +    #   from package.__init__ import something
 | 
| +    #
 | 
| +    # which seems quite correct.
 | 
| +    if modpath[-1] == '__init__':
 | 
| +        modpath.pop()
 | 
| +    return '.'.join(modpath)
 | 
| +
 | 
| +
 | 
| +def _toload_info(path, extrapath, _toload=None):
 | 
| +    """Return a dictionary of <modname>: <modpath> and an ordered list of
 | 
| +    (file, module name) to load
 | 
| +    """
 | 
| +    if _toload is None:
 | 
| +        assert isinstance(path, list)
 | 
| +        _toload = {}, []
 | 
| +    for fileordir in path:
 | 
| +        if isdir(fileordir) and exists(join(fileordir, '__init__.py')):
 | 
| +            subfiles = [join(fileordir, fname) for fname in listdir(fileordir)]
 | 
| +            _toload_info(subfiles, extrapath, _toload)
 | 
| +        elif fileordir[-3:] == '.py':
 | 
| +            modname = _modname_from_path(fileordir, extrapath)
 | 
| +            _toload[0][modname] = fileordir
 | 
| +            _toload[1].append((fileordir, modname))
 | 
| +    return _toload
 | 
| +
 | 
| +
 | 
| +class RegistrableObject(object):
 | 
| +    """This is the base class for registrable objects which are selected
 | 
| +    according to a context.
 | 
| +
 | 
| +    :attr:`__registry__`
 | 
| +      name of the registry for this object (string like 'views',
 | 
| +      'templates'...). You may want to define `__registries__` directly if your
 | 
| +      object should be registered in several registries.
 | 
| +
 | 
| +    :attr:`__regid__`
 | 
| +      object's identifier in the registry (string like 'main',
 | 
| +      'primary', 'folder_box')
 | 
| +
 | 
| +    :attr:`__select__`
 | 
| +      class'selector
 | 
| +
 | 
| +    Moreover, the `__abstract__` attribute may be set to True to indicate that a
 | 
| +    class is abstract and should not be registered.
 | 
| +
 | 
| +    You don't have to inherit from this class to put it in a registry (having
 | 
| +    `__regid__` and `__select__` is enough), though this is needed for classes
 | 
| +    that should be automatically registered.
 | 
| +    """
 | 
| +
 | 
| +    __registry__ = None
 | 
| +    __regid__ = None
 | 
| +    __select__ = None
 | 
| +    __abstract__ = True # see doc snipppets below (in Registry class)
 | 
| +
 | 
| +    @classproperty
 | 
| +    def __registries__(cls):
 | 
| +        if cls.__registry__ is None:
 | 
| +            return ()
 | 
| +        return (cls.__registry__,)
 | 
| +
 | 
| +
 | 
| +class RegistrableInstance(RegistrableObject):
 | 
| +    """Inherit this class if you want instances of the classes to be
 | 
| +    automatically registered.
 | 
| +    """
 | 
| +
 | 
| +    def __new__(cls, *args, **kwargs):
 | 
| +        """Add a __module__ attribute telling the module where the instance was
 | 
| +        created, for automatic registration.
 | 
| +        """
 | 
| +        obj = super(RegistrableInstance, cls).__new__(cls)
 | 
| +        # XXX subclass must no override __new__
 | 
| +        filepath = tb.extract_stack(limit=2)[0][0]
 | 
| +        obj.__module__ = _modname_from_path(filepath)
 | 
| +        return obj
 | 
| +
 | 
| +
 | 
| +class Registry(dict):
 | 
| +    """The registry store a set of implementations associated to identifier:
 | 
| +
 | 
| +    * to each identifier are associated a list of implementations
 | 
| +
 | 
| +    * to select an implementation of a given identifier, you should use one of the
 | 
| +      :meth:`select` or :meth:`select_or_none` method
 | 
| +
 | 
| +    * to select a list of implementations for a context, you should use the
 | 
| +      :meth:`possible_objects` method
 | 
| +
 | 
| +    * dictionary like access to an identifier will return the bare list of
 | 
| +      implementations for this identifier.
 | 
| +
 | 
| +    To be usable in a registry, the only requirement is to have a `__select__`
 | 
| +    attribute.
 | 
| +
 | 
| +    At the end of the registration process, the :meth:`__registered__`
 | 
| +    method is called on each registered object which have them, given the
 | 
| +    registry in which it's registered as argument.
 | 
| +
 | 
| +    Registration methods:
 | 
| +
 | 
| +    .. automethod: register
 | 
| +    .. automethod: unregister
 | 
| +
 | 
| +    Selection methods:
 | 
| +
 | 
| +    .. automethod: select
 | 
| +    .. automethod: select_or_none
 | 
| +    .. automethod: possible_objects
 | 
| +    .. automethod: object_by_id
 | 
| +    """
 | 
| +    def __init__(self, debugmode):
 | 
| +        super(Registry, self).__init__()
 | 
| +        self.debugmode = debugmode
 | 
| +
 | 
| +    def __getitem__(self, name):
 | 
| +        """return the registry (list of implementation objects) associated to
 | 
| +        this name
 | 
| +        """
 | 
| +        try:
 | 
| +            return super(Registry, self).__getitem__(name)
 | 
| +        except KeyError:
 | 
| +            exc = ObjectNotFound(name)
 | 
| +            exc.__traceback__ = sys.exc_info()[-1]
 | 
| +            raise exc
 | 
| +
 | 
| +    @classmethod
 | 
| +    def objid(cls, obj):
 | 
| +        """returns a unique identifier for an object stored in the registry"""
 | 
| +        return '%s.%s' % (obj.__module__, cls.objname(obj))
 | 
| +
 | 
| +    @classmethod
 | 
| +    def objname(cls, obj):
 | 
| +        """returns a readable name for an object stored in the registry"""
 | 
| +        return getattr(obj, '__name__', id(obj))
 | 
| +
 | 
| +    def initialization_completed(self):
 | 
| +        """call method __registered__() on registered objects when the callback
 | 
| +        is defined"""
 | 
| +        for objects in self.values():
 | 
| +            for objectcls in objects:
 | 
| +                registered = getattr(objectcls, '__registered__', None)
 | 
| +                if registered:
 | 
| +                    registered(self)
 | 
| +        if self.debugmode:
 | 
| +            wrap_predicates(_lltrace)
 | 
| +
 | 
| +    def register(self, obj, oid=None, clear=False):
 | 
| +        """base method to add an object in the registry"""
 | 
| +        assert not '__abstract__' in obj.__dict__, obj
 | 
| +        assert obj.__select__, obj
 | 
| +        oid = oid or obj.__regid__
 | 
| +        assert oid, ('no explicit name supplied to register object %s, '
 | 
| +                     'which has no __regid__ set' % obj)
 | 
| +        if clear:
 | 
| +            objects = self[oid] =  []
 | 
| +        else:
 | 
| +            objects = self.setdefault(oid, [])
 | 
| +        assert not obj in objects, 'object %s is already registered' % obj
 | 
| +        objects.append(obj)
 | 
| +
 | 
| +    def register_and_replace(self, obj, replaced):
 | 
| +        """remove <replaced> and register <obj>"""
 | 
| +        # 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 not isinstance(replaced, string_types):
 | 
| +            replaced = self.objid(replaced)
 | 
| +        # prevent from misspelling
 | 
| +        assert obj is not replaced, 'replacing an object by itself: %s' % obj
 | 
| +        registered_objs = self.get(obj.__regid__, ())
 | 
| +        for index, registered in enumerate(registered_objs):
 | 
| +            if self.objid(registered) == replaced:
 | 
| +                del registered_objs[index]
 | 
| +                break
 | 
| +        else:
 | 
| +            self.warning('trying to replace %s that is not registered with %s',
 | 
| +                         replaced, obj)
 | 
| +        self.register(obj)
 | 
| +
 | 
| +    def unregister(self, obj):
 | 
| +        """remove object <obj> from this registry"""
 | 
| +        objid = self.objid(obj)
 | 
| +        oid = obj.__regid__
 | 
| +        for registered in self.get(oid, ()):
 | 
| +            # use self.objid() to compare objects because vreg will probably
 | 
| +            # have its own version of the object, loaded through execfile
 | 
| +            if self.objid(registered) == objid:
 | 
| +                self[oid].remove(registered)
 | 
| +                break
 | 
| +        else:
 | 
| +            self.warning('can\'t remove %s, no id %s in the registry',
 | 
| +                         objid, oid)
 | 
| +
 | 
| +    def all_objects(self):
 | 
| +        """return a list containing all objects in this registry.
 | 
| +        """
 | 
| +        result = []
 | 
| +        for objs in self.values():
 | 
| +            result += objs
 | 
| +        return result
 | 
| +
 | 
| +    # dynamic selection methods ################################################
 | 
| +
 | 
| +    def object_by_id(self, oid, *args, **kwargs):
 | 
| +        """return object with the `oid` identifier. Only one object is expected
 | 
| +        to be found.
 | 
| +
 | 
| +        raise :exc:`ObjectNotFound` if there are no object with id `oid` in this
 | 
| +        registry
 | 
| +
 | 
| +        raise :exc:`AssertionError` if there is more than one object there
 | 
| +        """
 | 
| +        objects = self[oid]
 | 
| +        assert len(objects) == 1, objects
 | 
| +        return objects[0](*args, **kwargs)
 | 
| +
 | 
| +    def select(self, __oid, *args, **kwargs):
 | 
| +        """return the most specific object among those with the given oid
 | 
| +        according to the given context.
 | 
| +
 | 
| +        raise :exc:`ObjectNotFound` if there are no object with id `oid` in this
 | 
| +        registry
 | 
| +
 | 
| +        raise :exc:`NoSelectableObject` if no object can be selected
 | 
| +        """
 | 
| +        obj =  self._select_best(self[__oid], *args, **kwargs)
 | 
| +        if obj is None:
 | 
| +            raise NoSelectableObject(args, kwargs, self[__oid] )
 | 
| +        return obj
 | 
| +
 | 
| +    def select_or_none(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_best(self[__oid], *args, **kwargs)
 | 
| +        except ObjectNotFound:
 | 
| +            return None
 | 
| +
 | 
| +    def possible_objects(self, *args, **kwargs):
 | 
| +        """return an iterator on possible objects in this registry for the given
 | 
| +        context
 | 
| +        """
 | 
| +        for objects in self.values():
 | 
| +            obj = self._select_best(objects,  *args, **kwargs)
 | 
| +            if obj is None:
 | 
| +                continue
 | 
| +            yield obj
 | 
| +
 | 
| +    def _select_best(self, objects, *args, **kwargs):
 | 
| +        """return an instance of the most specific object according
 | 
| +        to parameters
 | 
| +
 | 
| +        return None if not object apply (don't raise `NoSelectableObject` since
 | 
| +        it's costly when searching objects using `possible_objects`
 | 
| +        (e.g. searching for hooks).
 | 
| +        """
 | 
| +        score, winners = 0, None
 | 
| +        for obj in objects:
 | 
| +            objectscore = obj.__select__(obj, *args, **kwargs)
 | 
| +            if objectscore > score:
 | 
| +                score, winners = objectscore, [obj]
 | 
| +            elif objectscore > 0 and objectscore == score:
 | 
| +                winners.append(obj)
 | 
| +        if winners is None:
 | 
| +            return None
 | 
| +        if len(winners) > 1:
 | 
| +            # log in production environement / test, error while debugging
 | 
| +            msg = 'select ambiguity: %s\n(args: %s, kwargs: %s)'
 | 
| +            if self.debugmode:
 | 
| +                # raise bare exception in debug mode
 | 
| +                raise Exception(msg % (winners, args, kwargs.keys()))
 | 
| +            self.error(msg, winners, args, kwargs.keys())
 | 
| +        # return the result of calling the object
 | 
| +        return self.selected(winners[0], args, kwargs)
 | 
| +
 | 
| +    def selected(self, winner, args, kwargs):
 | 
| +        """override here if for instance you don't want "instanciation"
 | 
| +        """
 | 
| +        return winner(*args, **kwargs)
 | 
| +
 | 
| +    # these are overridden by set_log_methods below
 | 
| +    # only defining here to prevent pylint from complaining
 | 
| +    info = warning = error = critical = exception = debug = lambda msg, *a, **kw: None
 | 
| +
 | 
| +
 | 
| +def obj_registries(cls, registryname=None):
 | 
| +    """return a tuple of registry names (see __registries__)"""
 | 
| +    if registryname:
 | 
| +        return (registryname,)
 | 
| +    return cls.__registries__
 | 
| +
 | 
| +
 | 
| +class RegistryStore(dict):
 | 
| +    """This class is responsible for loading objects and storing them
 | 
| +    in their registry which is created on the fly as needed.
 | 
| +
 | 
| +    It handles dynamic registration of objects and provides a
 | 
| +    convenient api to access them. To be recognized as an object that
 | 
| +    should be stored into one of the store's registry
 | 
| +    (:class:`Registry`), an object must provide the following
 | 
| +    attributes, used control how they interact with the registry:
 | 
| +
 | 
| +    :attr:`__registries__`
 | 
| +      list of registry names (string like 'views', 'templates'...) into which
 | 
| +      the object should be registered
 | 
| +
 | 
| +    :attr:`__regid__`
 | 
| +      object identifier in the registry (string like 'main',
 | 
| +      'primary', 'folder_box')
 | 
| +
 | 
| +    :attr:`__select__`
 | 
| +      the object predicate selectors
 | 
| +
 | 
| +    Moreover, the :attr:`__abstract__` attribute may be set to `True`
 | 
| +    to indicate that an object is abstract and should not be registered
 | 
| +    (such inherited attributes not considered).
 | 
| +
 | 
| +    .. Note::
 | 
| +
 | 
| +      When using the store to load objects dynamically, you *always* have
 | 
| +      to use **super()** to get the methods and attributes of the
 | 
| +      superclasses, and not use the class identifier. If not, you'll get into
 | 
| +      trouble at reload time.
 | 
| +
 | 
| +      For example, instead of writing::
 | 
| +
 | 
| +          class Thing(Parent):
 | 
| +              __regid__ = 'athing'
 | 
| +              __select__ = yes()
 | 
| +
 | 
| +              def f(self, arg1):
 | 
| +                  Parent.f(self, arg1)
 | 
| +
 | 
| +      You must write::
 | 
| +
 | 
| +          class Thing(Parent):
 | 
| +              __regid__ = 'athing'
 | 
| +              __select__ = yes()
 | 
| +
 | 
| +              def f(self, arg1):
 | 
| +                  super(Thing, self).f(arg1)
 | 
| +
 | 
| +    Controlling object registration
 | 
| +    -------------------------------
 | 
| +
 | 
| +    Dynamic loading is triggered by calling the
 | 
| +    :meth:`register_objects` method, given a list of directories to
 | 
| +    inspect for python modules.
 | 
| +
 | 
| +    .. automethod: register_objects
 | 
| +
 | 
| +    For each module, by default, all compatible objects are registered
 | 
| +    automatically. However if some objects come as replacement of
 | 
| +    other objects, or have to be included only if some condition is
 | 
| +    met, you'll have to define a `registration_callback(vreg)`
 | 
| +    function in the module and explicitly register **all objects** in
 | 
| +    this module, using the api defined below.
 | 
| +
 | 
| +
 | 
| +    .. automethod:: RegistryStore.register_all
 | 
| +    .. automethod:: RegistryStore.register_and_replace
 | 
| +    .. automethod:: RegistryStore.register
 | 
| +    .. automethod:: RegistryStore.unregister
 | 
| +
 | 
| +    .. Note::
 | 
| +        Once the function `registration_callback(vreg)` is implemented in a
 | 
| +        module, all the objects from this module have to be explicitly
 | 
| +        registered as it disables the automatic object registration.
 | 
| +
 | 
| +
 | 
| +    Examples:
 | 
| +
 | 
| +    .. sourcecode:: python
 | 
| +
 | 
| +       def registration_callback(store):
 | 
| +          # register everything in the module except BabarClass
 | 
| +          store.register_all(globals().values(), __name__, (BabarClass,))
 | 
| +
 | 
| +          # conditionally register BabarClass
 | 
| +          if 'babar_relation' in store.schema:
 | 
| +              store.register(BabarClass)
 | 
| +
 | 
| +    In this example, we register all application object classes defined in the module
 | 
| +    except `BabarClass`. This class is then registered only if the 'babar_relation'
 | 
| +    relation type is defined in the instance schema.
 | 
| +
 | 
| +    .. sourcecode:: python
 | 
| +
 | 
| +       def registration_callback(store):
 | 
| +          store.register(Elephant)
 | 
| +          # replace Babar by Celeste
 | 
| +          store.register_and_replace(Celeste, Babar)
 | 
| +
 | 
| +    In this example, we explicitly register classes one by one:
 | 
| +
 | 
| +    * the `Elephant` class
 | 
| +    * the `Celeste` to replace `Babar`
 | 
| +
 | 
| +    If at some point we register a new appobject class in this module, it won't be
 | 
| +    registered at all without modification to the `registration_callback`
 | 
| +    implementation. The first example will register it though, thanks to the call
 | 
| +    to the `register_all` method.
 | 
| +
 | 
| +    Controlling registry instantiation
 | 
| +    ----------------------------------
 | 
| +
 | 
| +    The `REGISTRY_FACTORY` class dictionary allows to specify which class should
 | 
| +    be instantiated for a given registry name. The class associated to `None`
 | 
| +    key will be the class used when there is no specific class for a name.
 | 
| +    """
 | 
| +
 | 
| +    def __init__(self, debugmode=False):
 | 
| +        super(RegistryStore, self).__init__()
 | 
| +        self.debugmode = debugmode
 | 
| +
 | 
| +    def reset(self):
 | 
| +        """clear all registries managed by this store"""
 | 
| +        # don't use self.clear, we want to keep existing subdictionaries
 | 
| +        for subdict in self.values():
 | 
| +            subdict.clear()
 | 
| +        self._lastmodifs = {}
 | 
| +
 | 
| +    def __getitem__(self, name):
 | 
| +        """return the registry (dictionary of class objects) associated to
 | 
| +        this name
 | 
| +        """
 | 
| +        try:
 | 
| +            return super(RegistryStore, self).__getitem__(name)
 | 
| +        except KeyError:
 | 
| +            exc = RegistryNotFound(name)
 | 
| +            exc.__traceback__ = sys.exc_info()[-1]
 | 
| +            raise exc
 | 
| +
 | 
| +    # methods for explicit (un)registration ###################################
 | 
| +
 | 
| +    # default class, when no specific class set
 | 
| +    REGISTRY_FACTORY = {None: Registry}
 | 
| +
 | 
| +    def registry_class(self, regid):
 | 
| +        """return existing registry named regid or use factory to create one and
 | 
| +        return it"""
 | 
| +        try:
 | 
| +            return self.REGISTRY_FACTORY[regid]
 | 
| +        except KeyError:
 | 
| +            return self.REGISTRY_FACTORY[None]
 | 
| +
 | 
| +    def setdefault(self, regid):
 | 
| +        try:
 | 
| +            return self[regid]
 | 
| +        except RegistryNotFound:
 | 
| +            self[regid] = self.registry_class(regid)(self.debugmode)
 | 
| +            return self[regid]
 | 
| +
 | 
| +    def register_all(self, objects, modname, butclasses=()):
 | 
| +        """register registrable objects into `objects`.
 | 
| +
 | 
| +        Registrable objects are properly configured subclasses of
 | 
| +        :class:`RegistrableObject`.  Objects which are not defined in the module
 | 
| +        `modname` or which are in `butclasses` won't be registered.
 | 
| +
 | 
| +        Typical usage is:
 | 
| +
 | 
| +        .. sourcecode:: python
 | 
| +
 | 
| +            store.register_all(globals().values(), __name__, (ClassIWantToRegisterExplicitly,))
 | 
| +
 | 
| +        So you get partially automatic registration, keeping manual registration
 | 
| +        for some object (to use
 | 
| +        :meth:`~logilab.common.registry.RegistryStore.register_and_replace` for
 | 
| +        instance).
 | 
| +        """
 | 
| +        assert isinstance(modname, string_types), \
 | 
| +            'modname expected to be a module name (ie string), got %r' % modname
 | 
| +        for obj in objects:
 | 
| +            if self.is_registrable(obj) and obj.__module__ == modname and not obj in butclasses:
 | 
| +                if isinstance(obj, type):
 | 
| +                    self._load_ancestors_then_object(modname, obj, butclasses)
 | 
| +                else:
 | 
| +                    self.register(obj)
 | 
| +
 | 
| +    def register(self, obj, registryname=None, oid=None, clear=False):
 | 
| +        """register `obj` implementation into `registryname` or
 | 
| +        `obj.__registries__` if not specified, with identifier `oid` or
 | 
| +        `obj.__regid__` if not specified.
 | 
| +
 | 
| +        If `clear` is true, all objects with the same identifier will be
 | 
| +        previously unregistered.
 | 
| +        """
 | 
| +        assert not obj.__dict__.get('__abstract__'), obj
 | 
| +        for registryname in obj_registries(obj, registryname):
 | 
| +            registry = self.setdefault(registryname)
 | 
| +            registry.register(obj, oid=oid, clear=clear)
 | 
| +            self.debug("register %s in %s['%s']",
 | 
| +                       registry.objname(obj), registryname, oid or obj.__regid__)
 | 
| +            self._loadedmods.setdefault(obj.__module__, {})[registry.objid(obj)] = obj
 | 
| +
 | 
| +    def unregister(self, obj, registryname=None):
 | 
| +        """unregister `obj` object from the registry `registryname` or
 | 
| +        `obj.__registries__` if not specified.
 | 
| +        """
 | 
| +        for registryname in obj_registries(obj, registryname):
 | 
| +            registry = self[registryname]
 | 
| +            registry.unregister(obj)
 | 
| +            self.debug("unregister %s from %s['%s']",
 | 
| +                       registry.objname(obj), registryname, obj.__regid__)
 | 
| +
 | 
| +    def register_and_replace(self, obj, replaced, registryname=None):
 | 
| +        """register `obj` object into `registryname` or
 | 
| +        `obj.__registries__` if not specified. If found, the `replaced` object
 | 
| +        will be unregistered first (else a warning will be issued as it is
 | 
| +        generally unexpected).
 | 
| +        """
 | 
| +        for registryname in obj_registries(obj, registryname):
 | 
| +            registry = self[registryname]
 | 
| +            registry.register_and_replace(obj, replaced)
 | 
| +            self.debug("register %s in %s['%s'] instead of %s",
 | 
| +                       registry.objname(obj), registryname, obj.__regid__,
 | 
| +                       registry.objname(replaced))
 | 
| +
 | 
| +    # initialization methods ###################################################
 | 
| +
 | 
| +    def init_registration(self, path, extrapath=None):
 | 
| +        """reset registry and walk down path to return list of (path, name)
 | 
| +        file modules to be loaded"""
 | 
| +        # XXX make this private by renaming it to _init_registration ?
 | 
| +        self.reset()
 | 
| +        # compute list of all modules that have to be loaded
 | 
| +        self._toloadmods, filemods = _toload_info(path, extrapath)
 | 
| +        # XXX is _loadedmods still necessary ? It seems like it's useful
 | 
| +        #     to avoid loading same module twice, especially with the
 | 
| +        #     _load_ancestors_then_object logic but this needs to be checked
 | 
| +        self._loadedmods = {}
 | 
| +        return filemods
 | 
| +
 | 
| +    def register_objects(self, path, extrapath=None):
 | 
| +        """register all objects found walking down <path>"""
 | 
| +        # load views from each directory in the instance's path
 | 
| +        # XXX inline init_registration ?
 | 
| +        filemods = self.init_registration(path, extrapath)
 | 
| +        for filepath, modname in filemods:
 | 
| +            self.load_file(filepath, modname)
 | 
| +        self.initialization_completed()
 | 
| +
 | 
| +    def initialization_completed(self):
 | 
| +        """call initialization_completed() on all known registries"""
 | 
| +        for reg in self.values():
 | 
| +            reg.initialization_completed()
 | 
| +
 | 
| +    def _mdate(self, filepath):
 | 
| +        """ return the modification date of a file path """
 | 
| +        try:
 | 
| +            return stat(filepath)[-2]
 | 
| +        except OSError:
 | 
| +            # this typically happens on emacs backup files (.#foo.py)
 | 
| +            self.warning('Unable to load %s. It is likely to be a backup file',
 | 
| +                         filepath)
 | 
| +            return None
 | 
| +
 | 
| +    def is_reload_needed(self, path):
 | 
| +        """return True if something module changed and the registry should be
 | 
| +        reloaded
 | 
| +        """
 | 
| +        lastmodifs = self._lastmodifs
 | 
| +        for fileordir in path:
 | 
| +            if isdir(fileordir) and exists(join(fileordir, '__init__.py')):
 | 
| +                if self.is_reload_needed([join(fileordir, fname)
 | 
| +                                          for fname in listdir(fileordir)]):
 | 
| +                    return True
 | 
| +            elif fileordir[-3:] == '.py':
 | 
| +                mdate = self._mdate(fileordir)
 | 
| +                if mdate is None:
 | 
| +                    continue # backup file, see _mdate implementation
 | 
| +                elif "flymake" in fileordir:
 | 
| +                    # flymake + pylint in use, don't consider these they will corrupt the registry
 | 
| +                    continue
 | 
| +                if fileordir not in lastmodifs or lastmodifs[fileordir] < mdate:
 | 
| +                    self.info('File %s changed since last visit', fileordir)
 | 
| +                    return True
 | 
| +        return False
 | 
| +
 | 
| +    def load_file(self, filepath, modname):
 | 
| +        """ load registrable objects (if any) from a python file """
 | 
| +        from logilab.common.modutils import load_module_from_name
 | 
| +        if modname in self._loadedmods:
 | 
| +            return
 | 
| +        self._loadedmods[modname] = {}
 | 
| +        mdate = self._mdate(filepath)
 | 
| +        if mdate is None:
 | 
| +            return # backup file, see _mdate implementation
 | 
| +        elif "flymake" in filepath:
 | 
| +            # flymake + pylint in use, don't consider these they will corrupt the registry
 | 
| +            return
 | 
| +        # set update time before module loading, else we get some reloading
 | 
| +        # weirdness in case of syntax error or other error while importing the
 | 
| +        # module
 | 
| +        self._lastmodifs[filepath] = mdate
 | 
| +        # load the module
 | 
| +        module = load_module_from_name(modname)
 | 
| +        self.load_module(module)
 | 
| +
 | 
| +    def load_module(self, module):
 | 
| +        """Automatically handle module objects registration.
 | 
| +
 | 
| +        Instances are registered as soon as they are hashable and have the
 | 
| +        following attributes:
 | 
| +
 | 
| +        * __regid__ (a string)
 | 
| +        * __select__ (a callable)
 | 
| +        * __registries__ (a tuple/list of string)
 | 
| +
 | 
| +        For classes this is a bit more complicated :
 | 
| +
 | 
| +        - first ensure parent classes are already registered
 | 
| +
 | 
| +        - class with __abstract__ == True in their local dictionary are skipped
 | 
| +
 | 
| +        - object class needs to have registries and identifier properly set to a
 | 
| +          non empty string to be registered.
 | 
| +        """
 | 
| +        self.info('loading %s from %s', module.__name__, module.__file__)
 | 
| +        if hasattr(module, 'registration_callback'):
 | 
| +            module.registration_callback(self)
 | 
| +        else:
 | 
| +            self.register_all(vars(module).values(), module.__name__)
 | 
| +
 | 
| +    def _load_ancestors_then_object(self, modname, objectcls, butclasses=()):
 | 
| +        """handle class registration according to rules defined in
 | 
| +        :meth:`load_module`
 | 
| +        """
 | 
| +        # backward compat, we used to allow whatever else than classes
 | 
| +        if not isinstance(objectcls, type):
 | 
| +            if self.is_registrable(objectcls) and objectcls.__module__ == modname:
 | 
| +                self.register(objectcls)
 | 
| +            return
 | 
| +        # imported classes
 | 
| +        objmodname = objectcls.__module__
 | 
| +        if objmodname != modname:
 | 
| +            # The module of the object is not the same as the currently
 | 
| +            # worked on module, or this is actually an instance, which
 | 
| +            # has no module at all
 | 
| +            if objmodname in self._toloadmods:
 | 
| +                # if this is still scheduled for loading, let's proceed immediately,
 | 
| +                # but using the object module
 | 
| +                self.load_file(self._toloadmods[objmodname], objmodname)
 | 
| +            return
 | 
| +        # ensure object hasn't been already processed
 | 
| +        clsid = '%s.%s' % (modname, objectcls.__name__)
 | 
| +        if clsid in self._loadedmods[modname]:
 | 
| +            return
 | 
| +        self._loadedmods[modname][clsid] = objectcls
 | 
| +        # ensure ancestors are registered
 | 
| +        for parent in objectcls.__bases__:
 | 
| +            self._load_ancestors_then_object(modname, parent, butclasses)
 | 
| +        # ensure object is registrable
 | 
| +        if objectcls in butclasses or not self.is_registrable(objectcls):
 | 
| +            return
 | 
| +        # backward compat
 | 
| +        reg = self.setdefault(obj_registries(objectcls)[0])
 | 
| +        if reg.objname(objectcls)[0] == '_':
 | 
| +            warn("[lgc 0.59] object whose name start with '_' won't be "
 | 
| +                 "skipped anymore at some point, use __abstract__ = True "
 | 
| +                 "instead (%s)" % objectcls, DeprecationWarning)
 | 
| +            return
 | 
| +        # register, finally
 | 
| +        self.register(objectcls)
 | 
| +
 | 
| +    @classmethod
 | 
| +    def is_registrable(cls, obj):
 | 
| +        """ensure `obj` should be registered
 | 
| +
 | 
| +        as arbitrary stuff may be registered, do a lot of check and warn about
 | 
| +        weird cases (think to dumb proxy objects)
 | 
| +        """
 | 
| +        if isinstance(obj, type):
 | 
| +            if not issubclass(obj, RegistrableObject):
 | 
| +                # ducktyping backward compat
 | 
| +                if not (getattr(obj, '__registries__', None)
 | 
| +                        and getattr(obj, '__regid__', None)
 | 
| +                        and getattr(obj, '__select__', None)):
 | 
| +                    return False
 | 
| +            elif issubclass(obj, RegistrableInstance):
 | 
| +                return False
 | 
| +        elif not isinstance(obj, RegistrableInstance):
 | 
| +            return False
 | 
| +        if not obj.__regid__:
 | 
| +            return False # no regid
 | 
| +        registries = obj.__registries__
 | 
| +        if not registries:
 | 
| +            return False # no registries
 | 
| +        selector = obj.__select__
 | 
| +        if not selector:
 | 
| +            return False # no selector
 | 
| +        if obj.__dict__.get('__abstract__', False):
 | 
| +            return False
 | 
| +        # then detect potential problems that should be warned
 | 
| +        if not isinstance(registries, (tuple, list)):
 | 
| +            cls.warning('%s has __registries__ which is not a list or tuple', obj)
 | 
| +            return False
 | 
| +        if not callable(selector):
 | 
| +            cls.warning('%s has not callable __select__', obj)
 | 
| +            return False
 | 
| +        return True
 | 
| +
 | 
| +    # these are overridden by set_log_methods below
 | 
| +    # only defining here to prevent pylint from complaining
 | 
| +    info = warning = error = critical = exception = debug = lambda msg, *a, **kw: None
 | 
| +
 | 
| +
 | 
| +# init logging
 | 
| +set_log_methods(RegistryStore, getLogger('registry.store'))
 | 
| +set_log_methods(Registry, getLogger('registry'))
 | 
| +
 | 
| +
 | 
| +# helpers for debugging selectors
 | 
| +TRACED_OIDS = None
 | 
| +
 | 
| +def _trace_selector(cls, selector, args, ret):
 | 
| +    vobj = args[0]
 | 
| +    if TRACED_OIDS == 'all' or vobj.__regid__ in TRACED_OIDS:
 | 
| +        print('%s -> %s for %s(%s)' % (cls, ret, vobj, vobj.__regid__))
 | 
| +
 | 
| +def _lltrace(selector):
 | 
| +    """use this decorator on your predicates so they become traceable with
 | 
| +    :class:`traced_selection`
 | 
| +    """
 | 
| +    def traced(cls, *args, **kwargs):
 | 
| +        ret = selector(cls, *args, **kwargs)
 | 
| +        if TRACED_OIDS is not None:
 | 
| +            _trace_selector(cls, selector, args, ret)
 | 
| +        return ret
 | 
| +    traced.__name__ = selector.__name__
 | 
| +    traced.__doc__ = selector.__doc__
 | 
| +    return traced
 | 
| +
 | 
| +class traced_selection(object): # pylint: disable=C0103
 | 
| +    """
 | 
| +    Typical usage is :
 | 
| +
 | 
| +    .. sourcecode:: python
 | 
| +
 | 
| +        >>> from logilab.common.registry import traced_selection
 | 
| +        >>> with traced_selection():
 | 
| +        ...     # some code in which you want to debug selectors
 | 
| +        ...     # for all objects
 | 
| +
 | 
| +    This will yield lines like this in the logs::
 | 
| +
 | 
| +        selector one_line_rset returned 0 for <class 'elephant.Babar'>
 | 
| +
 | 
| +    You can also give to :class:`traced_selection` the identifiers of objects on
 | 
| +    which you want to debug selection ('oid1' and 'oid2' in the example above).
 | 
| +
 | 
| +    .. sourcecode:: python
 | 
| +
 | 
| +        >>> with traced_selection( ('regid1', 'regid2') ):
 | 
| +        ...     # some code in which you want to debug selectors
 | 
| +        ...     # for objects with __regid__ 'regid1' and 'regid2'
 | 
| +
 | 
| +    A potentially useful point to set up such a tracing function is
 | 
| +    the `logilab.common.registry.Registry.select` method body.
 | 
| +    """
 | 
| +
 | 
| +    def __init__(self, traced='all'):
 | 
| +        self.traced = traced
 | 
| +
 | 
| +    def __enter__(self):
 | 
| +        global TRACED_OIDS
 | 
| +        TRACED_OIDS = self.traced
 | 
| +
 | 
| +    def __exit__(self, exctype, exc, traceback):
 | 
| +        global TRACED_OIDS
 | 
| +        TRACED_OIDS = None
 | 
| +        return traceback is None
 | 
| +
 | 
| +# selector base classes and operations ########################################
 | 
| +
 | 
| +def objectify_predicate(selector_func):
 | 
| +    """Most of the time, a simple score function is enough to build a selector.
 | 
| +    The :func:`objectify_predicate` decorator turn it into a proper selector
 | 
| +    class::
 | 
| +
 | 
| +        @objectify_predicate
 | 
| +        def one(cls, req, rset=None, **kwargs):
 | 
| +            return 1
 | 
| +
 | 
| +        class MyView(View):
 | 
| +            __select__ = View.__select__ & one()
 | 
| +
 | 
| +    """
 | 
| +    return type(selector_func.__name__, (Predicate,),
 | 
| +                {'__doc__': selector_func.__doc__,
 | 
| +                 '__call__': lambda self, *a, **kw: selector_func(*a, **kw)})
 | 
| +
 | 
| +
 | 
| +_PREDICATES = {}
 | 
| +
 | 
| +def wrap_predicates(decorator):
 | 
| +    for predicate in _PREDICATES.values():
 | 
| +        if not '_decorators' in predicate.__dict__:
 | 
| +            predicate._decorators = set()
 | 
| +        if decorator in predicate._decorators:
 | 
| +            continue
 | 
| +        predicate._decorators.add(decorator)
 | 
| +        predicate.__call__ = decorator(predicate.__call__)
 | 
| +
 | 
| +class PredicateMetaClass(type):
 | 
| +    def __new__(mcs, *args, **kwargs):
 | 
| +        # use __new__ so subclasses doesn't have to call Predicate.__init__
 | 
| +        inst = type.__new__(mcs, *args, **kwargs)
 | 
| +        proxy = weakref.proxy(inst, lambda p: _PREDICATES.pop(id(p)))
 | 
| +        _PREDICATES[id(proxy)] = proxy
 | 
| +        return inst
 | 
| +
 | 
| +
 | 
| +@add_metaclass(PredicateMetaClass)
 | 
| +class Predicate(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 :meth:`__call__` method. Notice it
 | 
| +    should usually accept any arbitrary arguments (the context), though that may
 | 
| +    vary depending on your usage of the registry.
 | 
| +
 | 
| +    a selector is called to help choosing the correct object for a
 | 
| +    particular context by returning a score (`int`) telling how well
 | 
| +    the implementation given as first argument fit 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, selector instance or tuple of
 | 
| +        selectors in the selectors tree. Return None if not found.
 | 
| +        """
 | 
| +        if self is selector:
 | 
| +            return self
 | 
| +        if (isinstance(selector, type) or isinstance(selector, tuple)) and \
 | 
| +               isinstance(self, selector):
 | 
| +            return self
 | 
| +        return None
 | 
| +
 | 
| +    def __str__(self):
 | 
| +        return self.__class__.__name__
 | 
| +
 | 
| +    def __and__(self, other):
 | 
| +        return AndPredicate(self, other)
 | 
| +    def __rand__(self, other):
 | 
| +        return AndPredicate(other, self)
 | 
| +    def __iand__(self, other):
 | 
| +        return AndPredicate(self, other)
 | 
| +    def __or__(self, other):
 | 
| +        return OrPredicate(self, other)
 | 
| +    def __ror__(self, other):
 | 
| +        return OrPredicate(other, self)
 | 
| +    def __ior__(self, other):
 | 
| +        return OrPredicate(self, other)
 | 
| +
 | 
| +    def __invert__(self):
 | 
| +        return NotPredicate(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__)
 | 
| +
 | 
| +    def __repr__(self):
 | 
| +        return u'<Predicate %s at %x>' % (self.__class__.__name__, id(self))
 | 
| +
 | 
| +
 | 
| +class MultiPredicate(Predicate):
 | 
| +    """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:
 | 
| +
 | 
| +        AndPredicate(AndPredicate(sel1, sel2), AndPredicate(sel3, sel4))
 | 
| +        ==> AndPredicate(sel1, sel2, sel3, sel4)
 | 
| +        """
 | 
| +        merged_selectors = []
 | 
| +        for selector in selectors:
 | 
| +            # XXX do we really want magic-transformations below?
 | 
| +            # if so, wanna warn about them?
 | 
| +            if isinstance(selector, types.FunctionType):
 | 
| +                selector = objectify_predicate(selector)()
 | 
| +            if isinstance(selector, type) and issubclass(selector, Predicate):
 | 
| +                selector = selector()
 | 
| +            assert isinstance(selector, Predicate), 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 (or tuple of
 | 
| +        selectors) in the selectors tree. Return 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
 | 
| +        # if not found in children, maybe we are looking for self?
 | 
| +        return super(MultiPredicate, self).search_selector(selector)
 | 
| +
 | 
| +
 | 
| +class AndPredicate(MultiPredicate):
 | 
| +    """and-chained selectors"""
 | 
| +    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 OrPredicate(MultiPredicate):
 | 
| +    """or-chained selectors"""
 | 
| +    def __call__(self, cls, *args, **kwargs):
 | 
| +        for selector in self.selectors:
 | 
| +            partscore = selector(cls, *args, **kwargs)
 | 
| +            if partscore:
 | 
| +                return partscore
 | 
| +        return 0
 | 
| +
 | 
| +class NotPredicate(Predicate):
 | 
| +    """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)' % self.selector
 | 
| +
 | 
| +
 | 
| +class yes(Predicate): # pylint: disable=C0103
 | 
| +    """Return the score given as parameter, with a default score of 0.5 so any
 | 
| +    other selector take precedence.
 | 
| +
 | 
| +    Usually used for objects which can be selected whatever the context, or
 | 
| +    also sometimes to add arbitrary points to a score.
 | 
| +
 | 
| +    Take care, `yes(0)` could be named 'no'...
 | 
| +    """
 | 
| +    def __init__(self, score=0.5):
 | 
| +        self.score = score
 | 
| +
 | 
| +    def __call__(self, *args, **kwargs):
 | 
| +        return self.score
 | 
| +
 | 
| +
 | 
| +# deprecated stuff #############################################################
 | 
| +
 | 
| +from logilab.common.deprecation import deprecated
 | 
| +
 | 
| +@deprecated('[lgc 0.59] use Registry.objid class method instead')
 | 
| +def classid(cls):
 | 
| +    return '%s.%s' % (cls.__module__, cls.__name__)
 | 
| +
 | 
| +@deprecated('[lgc 0.59] use obj_registries function instead')
 | 
| +def class_registries(cls, registryname):
 | 
| +    return obj_registries(cls, registryname)
 | 
| +
 | 
| 
 |