1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """This module provides bases for predicates dispatching (the pattern in use
19 here is similar to what's refered as multi-dispatch or predicate-dispatch in the
20 literature, though a bit different since the idea is to select across different
21 implementation 'e.g. classes), not to dispatch a message to a function or
22 method. It contains the following classes:
23
24 * :class:`RegistryStore`, the top level object which loads implementation
25 objects and stores them into registries. You'll usually use it to access
26 registries and their contained objects;
27
28 * :class:`Registry`, the base class which contains objects semantically grouped
29 (for instance, sharing a same API, hence the 'implementation' name). You'll
30 use it to select the proper implementation according to a context. Notice you
31 may use registries on their own without using the store.
32
33 .. Note::
34
35 implementation objects are usually designed to be accessed through the
36 registry and not by direct instantiation, besides to use it as base classe.
37
38 The selection procedure is delegated to a selector, which is responsible for
39 scoring the object according to some context. At the end of the selection, if an
40 implementation has been found, an instance of this class is returned. A selector
41 is built from one or more predicates combined together using AND, OR, NOT
42 operators (actually `&`, `|` and `~`). You'll thus find some base classes to
43 build predicates:
44
45 * :class:`Predicate`, the abstract base predicate class
46
47 * :class:`AndPredicate`, :class:`OrPredicate`, :class:`NotPredicate`, which you
48 shouldn't have to use directly. You'll use `&`, `|` and '~' operators between
49 predicates directly
50
51 * :func:`objectify_predicate`
52
53 You'll eventually find one concrete predicate: :class:`yes`
54
55 .. autoclass:: RegistryStore
56 .. autoclass:: Registry
57
58 Predicates
59 ----------
60 .. autoclass:: Predicate
61 .. autofunc:: objectify_predicate
62 .. autoclass:: yes
63
64 Debugging
65 ---------
66 .. autoclass:: traced_selection
67
68 Exceptions
69 ----------
70 .. autoclass:: RegistryException
71 .. autoclass:: RegistryNotFound
72 .. autoclass:: ObjectNotFound
73 .. autoclass:: NoSelectableObject
74 """
75
76 __docformat__ = "restructuredtext en"
77
78 import sys
79 import types
80 import weakref
81 from os import listdir, stat
82 from os.path import dirname, join, realpath, isdir, exists
83 from logging import getLogger
84
85 from logilab.common.decorators import classproperty
86 from logilab.common.logging_ext import set_log_methods
90 """Base class for registry exception."""
91
93 """Raised when an unknown registry is requested.
94
95 This is usually a programming/typo error.
96 """
97
99 """Raised when an unregistered object is requested.
100
101 This may be a programming/typo or a misconfiguration error.
102 """
103
105 """Raised when no object is selectable for a given context."""
106 - def __init__(self, args, kwargs, objects):
107 self.args = args
108 self.kwargs = kwargs
109 self.objects = objects
110
112 return ('args: %s, kwargs: %s\ncandidates: %s'
113 % (self.args, self.kwargs.keys(), self.objects))
114
117 """Return a dictionary of <modname>: <modpath> and an ordered list of
118 (file, module name) to load
119 """
120 from logilab.common.modutils import modpath_from_file
121 if _toload is None:
122 assert isinstance(path, list)
123 _toload = {}, []
124 for fileordir in path:
125 if isdir(fileordir) and exists(join(fileordir, '__init__.py')):
126 subfiles = [join(fileordir, fname) for fname in listdir(fileordir)]
127 _toload_info(subfiles, extrapath, _toload)
128 elif fileordir[-3:] == '.py':
129 modpath = modpath_from_file(fileordir, extrapath)
130
131
132
133
134
135
136
137
138
139
140
141 if modpath[-1] == '__init__':
142 modpath.pop()
143 modname = '.'.join(modpath)
144 _toload[0][modname] = fileordir
145 _toload[1].append((fileordir, modname))
146 return _toload
147
150 """returns a unique identifier for an object class"""
151 return '%s.%s' % (cls.__module__, cls.__name__)
152
154 if registryname:
155 return (registryname,)
156 return cls.__registries__
157
160 """The registry store a set of implementations associated to identifier:
161
162 * to each identifier are associated a list of implementations
163
164 * to select an implementation of a given identifier, you should use one of the
165 :meth:`select` or :meth:`select_or_none` method
166
167 * to select a list of implementations for a context, you should use the
168 :meth:`possible_objects` method
169
170 * dictionary like access to an identifier will return the bare list of
171 implementations for this identifier.
172
173 To be usable in a registry, the only requirement is to have a `__select__`
174 attribute.
175
176 At the end of the registration process, the :meth:`__registered__`
177 method is called on each registered object which have them, given the
178 registry in which it's registered as argument.
179
180 Registration methods:
181
182 .. automethod: register
183 .. automethod: unregister
184
185 Selection methods:
186
187 .. automethod: select
188 .. automethod: select_or_none
189 .. automethod: possible_objects
190 .. automethod: object_by_id
191 """
195
197 """return the registry (list of implementation objects) associated to
198 this name
199 """
200 try:
201 return super(Registry, self).__getitem__(name)
202 except KeyError:
203 raise ObjectNotFound(name), None, sys.exc_info()[-1]
204
206 for objects in self.itervalues():
207 for objectcls in objects:
208 registered = getattr(objectcls, '__registered__', None)
209 if registered:
210 registered(self)
211 if self.debugmode:
212 wrap_predicates(_lltrace)
213
214 - def register(self, obj, oid=None, clear=False):
215 """base method to add an object in the registry"""
216 assert not '__abstract__' in obj.__dict__
217 assert obj.__select__
218 oid = oid or obj.__regid__
219 assert oid
220 if clear:
221 objects = self[oid] = []
222 else:
223 objects = self.setdefault(oid, [])
224 assert not obj in objects, \
225 'object %s is already registered' % obj
226 objects.append(obj)
227
229
230
231
232 if not isinstance(replaced, basestring):
233 replaced = classid(replaced)
234
235 assert obj is not replaced, 'replacing an object by itself: %s' % obj
236 registered_objs = self.get(obj.__regid__, ())
237 for index, registered in enumerate(registered_objs):
238 if classid(registered) == replaced:
239 del registered_objs[index]
240 break
241 else:
242 self.warning('trying to replace an unregistered view %s by %s',
243 replaced, obj)
244 self.register(obj)
245
247 clsid = classid(obj)
248 oid = obj.__regid__
249 for registered in self.get(oid, ()):
250
251
252 if classid(registered) == clsid:
253 self[oid].remove(registered)
254 break
255 else:
256 self.warning('can\'t remove %s, no id %s in the registry',
257 clsid, oid)
258
260 """return a list containing all objects in this registry.
261 """
262 result = []
263 for objs in self.values():
264 result += objs
265 return result
266
267
268
270 """return object with the `oid` identifier. Only one object is expected
271 to be found.
272
273 raise :exc:`ObjectNotFound` if not object with id <oid> in <registry>
274
275 raise :exc:`AssertionError` if there is more than one object there
276 """
277 objects = self[oid]
278 assert len(objects) == 1, objects
279 return objects[0](*args, **kwargs)
280
281 - def select(self, __oid, *args, **kwargs):
282 """return the most specific object among those with the given oid
283 according to the given context.
284
285 raise :exc:`ObjectNotFound` if not object with id <oid> in <registry>
286
287 raise :exc:`NoSelectableObject` if not object apply
288 """
289 obj = self._select_best(self[__oid], *args, **kwargs)
290 if obj is None:
291 raise NoSelectableObject(args, kwargs, self[__oid] )
292 return obj
293
295 """return the most specific object among those with the given oid
296 according to the given context, or None if no object applies.
297 """
298 try:
299 return self.select(__oid, *args, **kwargs)
300 except (NoSelectableObject, ObjectNotFound):
301 return None
302
304 """return an iterator on possible objects in this registry for the given
305 context
306 """
307 for objects in self.itervalues():
308 obj = self._select_best(objects, *args, **kwargs)
309 if obj is None:
310 continue
311 yield obj
312
314 """return an instance of the most specific object according
315 to parameters
316
317 return None if not object apply (don't raise `NoSelectableObject` since
318 it's costly when searching objects using `possible_objects`
319 (e.g. searching for hooks).
320 """
321 score, winners = 0, None
322 for object in objects:
323 objectscore = object.__select__(object, *args, **kwargs)
324 if objectscore > score:
325 score, winners = objectscore, [object]
326 elif objectscore > 0 and objectscore == score:
327 winners.append(object)
328 if winners is None:
329 return None
330 if len(winners) > 1:
331
332 msg = 'select ambiguity: %s\n(args: %s, kwargs: %s)'
333 if self.debugmode:
334
335 raise Exception(msg % (winners, args, kwargs.keys()))
336 self.error(msg, winners, args, kwargs.keys())
337
338 return winners[0](*args, **kwargs)
339
340
341
342 info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
343
346 """This class is responsible for loading implementations and storing them
347 in their registry which are created on the fly as needed.
348
349 It handles dynamic registration of objects and provides a convenient api to
350 access them. To be recognized as an object that should be stored into one of
351 the store's registry (:class:`Registry`), an object (usually a class) has
352 the following attributes, used control how they interact with the registry:
353
354 :attr:`__registry__` or `__registries__`
355 name of the registry for this object (string like 'views', 'templates'...)
356 or list of registry names if you want your object to be added to multiple
357 registries
358
359 :attr:`__regid__`
360 implementation's identifier in the registry (string like 'main',
361 'primary', 'folder_box')
362
363 :attr:`__select__`
364 the implementation's selector
365
366 Moreover, the :attr:`__abstract__` attribute may be set to `True` to
367 indicate that a class is abstract and should not be registered (inherited
368 attributes not considered).
369
370 .. Note::
371
372 When using the store to load objects dynamically, you *always* have
373 to use **super()** to get the methods and attributes of the
374 superclasses, and not use the class identifier. Else, you'll get into
375 trouble when reloading comes into the place.
376
377 For example, instead of writing::
378
379 class Thing(Parent):
380 __regid__ = 'athing'
381 __select__ = yes()
382 def f(self, arg1):
383 Parent.f(self, arg1)
384
385 You must write::
386
387 class Thing(Parent):
388 __regid__ = 'athing'
389 __select__ = yes()
390 def f(self, arg1):
391 super(Parent, self).f(arg1)
392
393 Controlling objects registration
394 --------------------------------
395
396 Dynamic loading is triggered by calling the :meth:`register_objects` method,
397 given a list of directory to inspect for python modules.
398
399 .. automethod: register_objects
400
401 For each module, by default, all compatible objects are registered
402 automatically, though if some objects have to replace other objects, or have
403 to be included only if some condition is met, you'll have to define a
404 `registration_callback(vreg)` function in your module and explicitly
405 register **all objects** in this module, using the api defined below.
406
407
408 .. automethod:: RegistryStore.register_all
409 .. automethod:: RegistryStore.register_and_replace
410 .. automethod:: RegistryStore.register
411 .. automethod:: RegistryStore.unregister
412
413 .. Note::
414 Once the function `registration_callback(vreg)` is implemented in a
415 module, all the objects from this module have to be explicitly
416 registered as it disables the automatic objects registration.
417
418
419 Examples:
420
421 .. sourcecode:: python
422
423 # cubicweb/web/views/basecomponents.py
424 def registration_callback(store):
425 # register everything in the module except SeeAlsoComponent
426 store.register_all(globals().values(), __name__, (SeeAlsoVComponent,))
427 # conditionally register SeeAlsoVComponent
428 if 'see_also' in store.schema:
429 store.register(SeeAlsoVComponent)
430
431 In this example, we register all application object classes defined in the module
432 except `SeeAlsoVComponent`. This class is then registered only if the 'see_also'
433 relation type is defined in the instance'schema.
434
435 .. sourcecode:: python
436
437 # goa/appobjects/sessions.py
438 def registration_callback(store):
439 store.register(SessionsCleaner)
440 # replace AuthenticationManager by GAEAuthenticationManager
441 store.register_and_replace(GAEAuthenticationManager, AuthenticationManager)
442 # replace PersistentSessionManager by GAEPersistentSessionManager
443 store.register_and_replace(GAEPersistentSessionManager, PersistentSessionManager)
444
445 In this example, we explicitly register classes one by one:
446
447 * the `SessionCleaner` class
448 * the `GAEAuthenticationManager` to replace the `AuthenticationManager`
449 * the `GAEPersistentSessionManager` to replace the `PersistentSessionManager`
450
451 If at some point we register a new appobject class in this module, it won't be
452 registered at all without modification to the `registration_callback`
453 implementation. The previous example will register it though, thanks to the call
454 to the `register_all` method.
455
456 Controlling registry instantation
457 ---------------------------------
458 The `REGISTRY_FACTORY` class dictionary allows to specify which class should
459 be instantiated for a given registry name. The class associated to `None` in
460 it will be the class used when there is no specific class for a name.
461 """
462
466
468
469 for subdict in self.itervalues():
470 subdict.clear()
471 self._lastmodifs = {}
472
481
482
483
484
485 REGISTRY_FACTORY = {None: Registry}
486
492
494 try:
495 return self[regid]
496 except KeyError:
497 self[regid] = self.registry_class(regid)(self.debugmode)
498 return self[regid]
499
501 """register all `objects` given. Objects which are not from the module
502 `modname` or which are in `butclasses` won't be registered.
503
504 Typical usage is:
505
506 .. sourcecode:: python
507
508 store.register_all(globals().values(), __name__, (ClassIWantToRegisterExplicitly,))
509
510 So you get partially automatic registration, keeping manual registration
511 for some object (to use
512 :meth:`~logilab.common.registry.RegistryStore.register_and_replace`
513 for instance)
514 """
515 for obj in objects:
516 try:
517 if obj.__module__ != modname or obj in butclasses:
518 continue
519 oid = obj.__regid__
520 except AttributeError:
521 continue
522 if oid and not obj.__dict__.get('__abstract__'):
523 self.register(obj, oid=oid)
524
525 - def register(self, obj, registryname=None, oid=None, clear=False):
526 """register `obj` implementation into `registryname` or
527 `obj.__registry__` if not specified, with identifier `oid` or
528 `obj.__regid__` if not specified.
529
530 If `clear` is true, all objects with the same identifier will be
531 previously unregistered.
532 """
533 assert not obj.__dict__.get('__abstract__')
534 try:
535 vname = obj.__name__
536 except AttributeError:
537
538 vname = obj.__class__.__name__
539 for registryname in class_registries(obj, registryname):
540 registry = self.setdefault(registryname)
541 registry.register(obj, oid=oid, clear=clear)
542 self.debug('register %s in %s[\'%s\']',
543 vname, registryname, oid or obj.__regid__)
544 self._loadedmods.setdefault(obj.__module__, {})[classid(obj)] = obj
545
547 """unregister `obj` implementation object from the registry
548 `registryname` or `obj.__registry__` if not specified.
549 """
550 for registryname in class_registries(obj, registryname):
551 self[registryname].unregister(obj)
552
554 """register `obj` implementation object into `registryname` or
555 `obj.__registry__` if not specified. If found, the `replaced` object
556 will be unregistered first (else a warning will be issued as it's
557 generally unexpected).
558 """
559 for registryname in class_registries(obj, registryname):
560 self[registryname].register_and_replace(obj, replaced)
561
562
563
565 self.reset()
566
567 self._toloadmods, filemods = _toload_info(path, extrapath)
568
569
570
571 self._loadedmods = {}
572 return filemods
573
580
584
586 try:
587 return stat(filepath)[-2]
588 except OSError:
589
590 self.warning('Unable to load %s. It is likely to be a backup file',
591 filepath)
592 return None
593
595 """return True if something module changed and the registry should be
596 reloaded
597 """
598 lastmodifs = self._lastmodifs
599 for fileordir in path:
600 if isdir(fileordir) and exists(join(fileordir, '__init__.py')):
601 if self.is_reload_needed([join(fileordir, fname)
602 for fname in listdir(fileordir)]):
603 return True
604 elif fileordir[-3:] == '.py':
605 mdate = self._mdate(fileordir)
606 if mdate is None:
607 continue
608 elif "flymake" in fileordir:
609
610 continue
611 if fileordir not in lastmodifs or lastmodifs[fileordir] < mdate:
612 self.info('File %s changed since last visit', fileordir)
613 return True
614 return False
615
617 """load app objects from a python file"""
618 from logilab.common.modutils import load_module_from_name
619 if modname in self._loadedmods:
620 return
621 self._loadedmods[modname] = {}
622 mdate = self._mdate(filepath)
623 if mdate is None:
624 return
625 elif "flymake" in filepath:
626
627 return
628
629
630
631 self._lastmodifs[filepath] = mdate
632
633 module = load_module_from_name(modname)
634 self.load_module(module)
635
637 self.info('loading %s from %s', module.__name__, module.__file__)
638 if hasattr(module, 'registration_callback'):
639 module.registration_callback(self)
640 else:
641 for objname, obj in vars(module).items():
642 if objname.startswith('_'):
643 continue
644 self._load_ancestors_then_object(module.__name__, obj)
645
647 """handle automatic object class registration:
648
649 - first ensure parent classes are already registered
650
651 - class with __abstract__ == True in their local dictionary or
652 with a name starting with an underscore are not registered
653
654 - object class needs to have __registry__ and __regid__ attributes
655 set to a non empty string to be registered.
656 """
657
658 objmodname = getattr(objectcls, '__module__', None)
659 if objmodname != modname:
660 if objmodname in self._toloadmods:
661 self.load_file(self._toloadmods[objmodname], objmodname)
662 return
663
664 try:
665 if not (getattr(objectcls, '__regid__', None)
666 and getattr(objectcls, '__select__', None)):
667 return
668 except TypeError:
669 return
670 clsid = classid(objectcls)
671 if clsid in self._loadedmods[modname]:
672 return
673 self._loadedmods[modname][clsid] = objectcls
674 for parent in objectcls.__bases__:
675 self._load_ancestors_then_object(modname, parent)
676 if (objectcls.__dict__.get('__abstract__')
677 or objectcls.__name__[0] == '_'
678 or not objectcls.__registries__
679 or not objectcls.__regid__):
680 return
681 try:
682 self.register(objectcls)
683 except Exception, ex:
684 if self.debugmode:
685 raise
686 self.exception('object %s registration failed: %s',
687 objectcls, ex)
688
689
690
691 info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None
692
693
694
695 set_log_methods(RegistryStore, getLogger('registry.store'))
696 set_log_methods(Registry, getLogger('registry'))
697
698
699
700 TRACED_OIDS = None
703 vobj = args[0]
704 if TRACED_OIDS == 'all' or vobj.__regid__ in TRACED_OIDS:
705 print '%s -> %s for %s(%s)' % (cls, ret, vobj, vobj.__regid__)
706
708 """use this decorator on your predicates so they become traceable with
709 :class:`traced_selection`
710 """
711 def traced(cls, *args, **kwargs):
712 ret = selector(cls, *args, **kwargs)
713 if TRACED_OIDS is not None:
714 _trace_selector(cls, selector, args, ret)
715 return ret
716 traced.__name__ = selector.__name__
717 traced.__doc__ = selector.__doc__
718 return traced
719
721 """
722 Typical usage is :
723
724 .. sourcecode:: python
725
726 >>> from logilab.common.registry import traced_selection
727 >>> with traced_selection():
728 ... # some code in which you want to debug selectors
729 ... # for all objects
730
731 Don't forget the 'from __future__ import with_statement' at the module top-level
732 if you're using python prior to 2.6.
733
734 This will yield lines like this in the logs::
735
736 selector one_line_rset returned 0 for <class 'cubicweb.web.views.basecomponents.WFHistoryVComponent'>
737
738 You can also give to :class:`traced_selection` the identifiers of objects on
739 which you want to debug selection ('oid1' and 'oid2' in the example above).
740
741 .. sourcecode:: python
742
743 >>> with traced_selection( ('regid1', 'regid2') ):
744 ... # some code in which you want to debug selectors
745 ... # for objects with __regid__ 'regid1' and 'regid2'
746
747 A potentially useful point to set up such a tracing function is
748 the `logilab.common.registry.Registry.select` method body.
749 """
750
753
757
758 - def __exit__(self, exctype, exc, traceback):
762
766 """Most of the time, a simple score function is enough to build a selector.
767 The :func:`objectify_predicate` decorator turn it into a proper selector
768 class::
769
770 @objectify_predicate
771 def one(cls, req, rset=None, **kwargs):
772 return 1
773
774 class MyView(View):
775 __select__ = View.__select__ & one()
776
777 """
778 return type(selector_func.__name__, (Predicate,),
779 {'__doc__': selector_func.__doc__,
780 '__call__': lambda self, *a, **kw: selector_func(*a, **kw)})
781
782
783 _PREDICATES = {}
786 for predicate in _PREDICATES.itervalues():
787 if not '_decorators' in predicate.__dict__:
788 predicate._decorators = set()
789 if decorator in predicate._decorators:
790 continue
791 predicate._decorators.add(decorator)
792 predicate.__call__ = decorator(predicate.__call__)
793
801
803 """base class for selector classes providing implementation
804 for operators ``&``, ``|`` and ``~``
805
806 This class is only here to give access to binary operators, the selector
807 logic itself should be implemented in the :meth:`__call__` method. Notice it
808 should usually accept any arbitrary arguments (the context), though that may
809 vary depending on your usage of the registry.
810
811 a selector is called to help choosing the correct object for a
812 particular context by returning a score (`int`) telling how well
813 the implementation given as first argument fit to the given context.
814
815 0 score means that the class doesn't apply.
816 """
817 __metaclass__ = PredicateMetaClass
818
819 @property
821
822 return self.__class__.__name__
823
825 """search for the given selector, selector instance or tuple of
826 selectors in the selectors tree. Return None if not found.
827 """
828 if self is selector:
829 return self
830 if (isinstance(selector, type) or isinstance(selector, tuple)) and \
831 isinstance(self, selector):
832 return self
833 return None
834
836 return self.__class__.__name__
837
850
853
854
855
856 - def __call__(self, cls, *args, **kwargs):
857 return NotImplementedError("selector %s must implement its logic "
858 "in its __call__ method" % self.__class__)
859
861 return u'<Predicate %s at %x>' % (self.__class__.__name__, id(self))
862
865 """base class for compound selector classes"""
866
869
871 return '%s(%s)' % (self.__class__.__name__,
872 ','.join(str(s) for s in self.selectors))
873
874 @classmethod
876 """deal with selector instanciation when necessary and merge
877 multi-selectors if possible:
878
879 AndPredicate(AndPredicate(sel1, sel2), AndPredicate(sel3, sel4))
880 ==> AndPredicate(sel1, sel2, sel3, sel4)
881 """
882 merged_selectors = []
883 for selector in selectors:
884
885
886 if isinstance(selector, types.FunctionType):
887 selector = objectify_predicate(selector)()
888 if isinstance(selector, type) and issubclass(selector, Predicate):
889 selector = selector()
890 assert isinstance(selector, Predicate), selector
891 if isinstance(selector, cls):
892 merged_selectors += selector.selectors
893 else:
894 merged_selectors.append(selector)
895 return merged_selectors
896
898 """search for the given selector or selector instance (or tuple of
899 selectors) in the selectors tree. Return None if not found
900 """
901 for childselector in self.selectors:
902 if childselector is selector:
903 return childselector
904 found = childselector.search_selector(selector)
905 if found is not None:
906 return found
907
908 return super(MultiPredicate, self).search_selector(selector)
909
912 """and-chained selectors"""
913 - def __call__(self, cls, *args, **kwargs):
914 score = 0
915 for selector in self.selectors:
916 partscore = selector(cls, *args, **kwargs)
917 if not partscore:
918 return 0
919 score += partscore
920 return score
921
924 """or-chained selectors"""
925 - def __call__(self, cls, *args, **kwargs):
926 for selector in self.selectors:
927 partscore = selector(cls, *args, **kwargs)
928 if partscore:
929 return partscore
930 return 0
931
933 """negation selector"""
935 self.selector = selector
936
937 - def __call__(self, cls, *args, **kwargs):
938 score = self.selector(cls, *args, **kwargs)
939 return int(not score)
940
942 return 'NOT(%s)' % self.selector
943
944
945 -class yes(Predicate):
946 """Return the score given as parameter, with a default score of 0.5 so any
947 other selector take precedence.
948
949 Usually used for objects which can be selected whatever the context, or
950 also sometimes to add arbitrary points to a score.
951
952 Take care, `yes(0)` could be named 'no'...
953 """
956
959