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:
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;
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.
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:
Predicate, the abstract base predicate class
You’ll eventually find one concrete predicate:
- class logilab.common.registry.RegistryStore(debugmode: bool = False)#
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 (
Registry), an object must provide the following attributes, used control how they interact with the registry:
list of registry names (string like ‘views’, ‘templates’…) into which the object should be registered
object identifier in the registry (string like ‘main’, ‘primary’, ‘folder_box’)
the object predicate selectors
__abstract__attribute may be set to True to indicate that an object is abstract and should not be registered (such inherited attributes not considered).
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)
Dynamic loading is triggered by calling the
register_modnames()method, given a list of modules names to inspect.
- register_modnames(modnames: List[str]) None #
register all objects found in <modnames>
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.
- register_all(objects: Iterable, modname: str, butclasses: Sequence = ()) None #
register registrable objects into objects.
Registrable objects are properly configured subclasses of
RegistrableObject. Objects which are not defined in the module modname or which are in butclasses won’t be registered.
Typical usage is:
store.register_all(globals().values(), __name__, (ClassIWantToRegisterExplicitly,))
So you get partially automatic registration, keeping manual registration for some object (to use
- register_and_replace(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).
- register(obj: Any, registryname: Optional[Any] = None, oid: Optional[Any] = None, clear: bool = False) None #
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.
- unregister(obj, registryname=None)#
unregister obj object from the registry registryname or obj.__registries__ if not specified.
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.
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.
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.
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.
- class logilab.common.registry.Registry(debugmode: bool)#
The registry store a set of implementations associated to identifier:
to each identifier are associated a list of implementations
to select a list of implementations for a context, you should use the
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
__registered__()method is called on each registered object which have them, given the registry in which it’s registered as argument.
- register(obj: Any, oid: Optional[Any] = None, clear: bool = False) None #
base method to add an object in the registry
remove object <obj> from this registry
- select(_Registry__oid, *args, **kwargs)#
return the most specific object among those with the given oid according to the given context.
ObjectNotFoundif there are no object with id oid in this registry
NoSelectableObjectif no object can be selected
- select_or_none(_Registry__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.
- possible_objects(*args, **kwargs)#
return an iterator on possible objects in this registry for the given context
- class logilab.common.registry.Predicate#
base class for selector classes providing implementation for operators
This class is only here to give access to binary operators, the selector logic itself should be implemented in the
__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.
- logilab.common.registry.objectify_predicate(selector_func: Callable) Any #
Most of the time, a simple score function is enough to build a selector. The
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()
- class logilab.common.registry.yes(score: float = 0.5)#
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’…
- class logilab.common.registry.AndPredicate(*selectors: Any)#
- class logilab.common.registry.OrPredicate(*selectors: Any)#
- class logilab.common.registry.NotPredicate(selector)#
- class logilab.common.registry.traced_selection(traced='all')#
Typical usage is :
>>> 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
traced_selectionthe identifiers of objects on which you want to debug selection (‘oid1’ and ‘oid2’ in the example above).
>>> 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.
- class logilab.common.registry.RegistryException#
Base class for registry exception.
- class logilab.common.registry.RegistryNotFound#
Raised when an unknown registry is requested.
This is usually a programming/typo error.
- class logilab.common.registry.ObjectNotFound#
Raised when an unregistered object is requested.
This may be a programming/typo or a misconfiguration error.
- class logilab.common.registry.NoSelectableObject(args, kwargs, objects)#
Raised when no object is selectable for a given context.