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 import traceback as tb
82 from os import listdir, stat
83 from os.path import join, isdir, exists
84 from logging import getLogger
85 from warnings import warn
86
87 from logilab.common.modutils import modpath_from_file
88 from logilab.common.logging_ext import set_log_methods
89 from logilab.common.decorators import classproperty
90 import collections
94 """Base class for registry exception."""
95
97 """Raised when an unknown registry is requested.
98
99 This is usually a programming/typo error.
100 """
101
103 """Raised when an unregistered object is requested.
104
105 This may be a programming/typo or a misconfiguration error.
106 """
107
109 """Raised when no object is selectable for a given context."""
110 - def __init__(self, args, kwargs, objects):
111 self.args = args
112 self.kwargs = kwargs
113 self.objects = objects
114
116 return ('args: %s, kwargs: %s\ncandidates: %s'
117 % (self.args, list(self.kwargs.keys()), self.objects))
118
121 modpath = modpath_from_file(path, extrapath)
122
123
124
125
126
127
128
129
130
131
132
133 if modpath[-1] == '__init__':
134 modpath.pop()
135 return '.'.join(modpath)
136
139 """Return a dictionary of <modname>: <modpath> and an ordered list of
140 (file, module name) to load
141 """
142 if _toload is None:
143 assert isinstance(path, list)
144 _toload = {}, []
145 for fileordir in path:
146 if isdir(fileordir) and exists(join(fileordir, '__init__.py')):
147 subfiles = [join(fileordir, fname) for fname in listdir(fileordir)]
148 _toload_info(subfiles, extrapath, _toload)
149 elif fileordir[-3:] == '.py':
150 modname = _modname_from_path(fileordir, extrapath)
151 _toload[0][modname] = fileordir
152 _toload[1].append((fileordir, modname))
153 return _toload
154
157 """This is the base class for registrable objects which are selected
158 according to a context.
159
160 :attr:`__registry__`
161 name of the registry for this object (string like 'views',
162 'templates'...). You may want to define `__registries__` directly if your
163 object should be registered in several registries.
164
165 :attr:`__regid__`
166 object's identifier in the registry (string like 'main',
167 'primary', 'folder_box')
168
169 :attr:`__select__`
170 class'selector
171
172 Moreover, the `__abstract__` attribute may be set to True to indicate that a
173 class is abstract and should not be registered.
174
175 You don't have to inherit from this class to put it in a registry (having
176 `__regid__` and `__select__` is enough), though this is needed for classes
177 that should be automatically registered.
178 """
179
180 __registry__ = None
181 __regid__ = None
182 __select__ = None
183 __abstract__ = True
184
185 @classproperty
190
193 """Inherit this class if you want instances of the classes to be
194 automatically registered.
195 """
196
197 - def __new__(cls, *args, **kwargs):
198 """Add a __module__ attribute telling the module where the instance was
199 created, for automatic registration.
200 """
201 obj = super(RegistrableInstance, cls).__new__(cls)
202
203 filepath = tb.extract_stack(limit=2)[0][0]
204 obj.__module__ = _modname_from_path(filepath)
205 return obj
206
209 """The registry store a set of implementations associated to identifier:
210
211 * to each identifier are associated a list of implementations
212
213 * to select an implementation of a given identifier, you should use one of the
214 :meth:`select` or :meth:`select_or_none` method
215
216 * to select a list of implementations for a context, you should use the
217 :meth:`possible_objects` method
218
219 * dictionary like access to an identifier will return the bare list of
220 implementations for this identifier.
221
222 To be usable in a registry, the only requirement is to have a `__select__`
223 attribute.
224
225 At the end of the registration process, the :meth:`__registered__`
226 method is called on each registered object which have them, given the
227 registry in which it's registered as argument.
228
229 Registration methods:
230
231 .. automethod: register
232 .. automethod: unregister
233
234 Selection methods:
235
236 .. automethod: select
237 .. automethod: select_or_none
238 .. automethod: possible_objects
239 .. automethod: object_by_id
240 """
244
246 """return the registry (list of implementation objects) associated to
247 this name
248 """
249 try:
250 return super(Registry, self).__getitem__(name)
251 except KeyError:
252 raise ObjectNotFound(name).with_traceback(sys.exc_info()[-1])
253
254 @classmethod
256 """returns a unique identifier for an object stored in the registry"""
257 return '%s.%s' % (obj.__module__, cls.objname(obj))
258
259 @classmethod
261 """returns a readable name for an object stored in the registry"""
262 return getattr(obj, '__name__', id(obj))
263
265 """call method __registered__() on registered objects when the callback
266 is defined"""
267 for objects in self.values():
268 for objectcls in objects:
269 registered = getattr(objectcls, '__registered__', None)
270 if registered:
271 registered(self)
272 if self.debugmode:
273 wrap_predicates(_lltrace)
274
275 - def register(self, obj, oid=None, clear=False):
276 """base method to add an object in the registry"""
277 assert not '__abstract__' in obj.__dict__, obj
278 assert obj.__select__, obj
279 oid = oid or obj.__regid__
280 assert oid, ('no explicit name supplied to register object %s, '
281 'which has no __regid__ set' % obj)
282 if clear:
283 objects = self[oid] = []
284 else:
285 objects = self.setdefault(oid, [])
286 assert not obj in objects, 'object %s is already registered' % obj
287 objects.append(obj)
288
290 """remove <replaced> and register <obj>"""
291
292
293
294 if not isinstance(replaced, str):
295 replaced = self.objid(replaced)
296
297 assert obj is not replaced, 'replacing an object by itself: %s' % obj
298 registered_objs = self.get(obj.__regid__, ())
299 for index, registered in enumerate(registered_objs):
300 if self.objid(registered) == replaced:
301 del registered_objs[index]
302 break
303 else:
304 self.warning('trying to replace %s that is not registered with %s',
305 replaced, obj)
306 self.register(obj)
307
309 """remove object <obj> from this registry"""
310 objid = self.objid(obj)
311 oid = obj.__regid__
312 for registered in self.get(oid, ()):
313
314
315 if self.objid(registered) == objid:
316 self[oid].remove(registered)
317 break
318 else:
319 self.warning('can\'t remove %s, no id %s in the registry',
320 objid, oid)
321
323 """return a list containing all objects in this registry.
324 """
325 result = []
326 for objs in list(self.values()):
327 result += objs
328 return result
329
330
331
333 """return object with the `oid` identifier. Only one object is expected
334 to be found.
335
336 raise :exc:`ObjectNotFound` if there are no object with id `oid` in this
337 registry
338
339 raise :exc:`AssertionError` if there is more than one object there
340 """
341 objects = self[oid]
342 assert len(objects) == 1, objects
343 return objects[0](*args, **kwargs)
344
345 - def select(self, __oid, *args, **kwargs):
346 """return the most specific object among those with the given oid
347 according to the given context.
348
349 raise :exc:`ObjectNotFound` if there are no object with id `oid` in this
350 registry
351
352 raise :exc:`NoSelectableObject` if no object can be selected
353 """
354 obj = self._select_best(self[__oid], *args, **kwargs)
355 if obj is None:
356 raise NoSelectableObject(args, kwargs, self[__oid] )
357 return obj
358
360 """return the most specific object among those with the given oid
361 according to the given context, or None if no object applies.
362 """
363 try:
364 return self._select_best(self[__oid], *args, **kwargs)
365 except ObjectNotFound:
366 return None
367
369 """return an iterator on possible objects in this registry for the given
370 context
371 """
372 for objects in self.values():
373 obj = self._select_best(objects, *args, **kwargs)
374 if obj is None:
375 continue
376 yield obj
377
379 """return an instance of the most specific object according
380 to parameters
381
382 return None if not object apply (don't raise `NoSelectableObject` since
383 it's costly when searching objects using `possible_objects`
384 (e.g. searching for hooks).
385 """
386 score, winners = 0, None
387 for obj in objects:
388 objectscore = obj.__select__(obj, *args, **kwargs)
389 if objectscore > score:
390 score, winners = objectscore, [obj]
391 elif objectscore > 0 and objectscore == score:
392 winners.append(obj)
393 if winners is None:
394 return None
395 if len(winners) > 1:
396
397 msg = 'select ambiguity: %s\n(args: %s, kwargs: %s)'
398 if self.debugmode:
399
400 raise Exception(msg % (winners, args, list(kwargs.keys())))
401 self.error(msg, winners, args, list(kwargs.keys()))
402
403 return self.selected(winners[0], args, kwargs)
404
405 - def selected(self, winner, args, kwargs):
406 """override here if for instance you don't want "instanciation"
407 """
408 return winner(*args, **kwargs)
409
410
411
412 info = warning = error = critical = exception = debug = lambda msg, *a, **kw: None
413
416 """return a tuple of registry names (see __registries__)"""
417 if registryname:
418 return (registryname,)
419 return cls.__registries__
420
423 """This class is responsible for loading objects and storing them
424 in their registry which is created on the fly as needed.
425
426 It handles dynamic registration of objects and provides a
427 convenient api to access them. To be recognized as an object that
428 should be stored into one of the store's registry
429 (:class:`Registry`), an object must provide the following
430 attributes, used control how they interact with the registry:
431
432 :attr:`__registries__`
433 list of registry names (string like 'views', 'templates'...) into which
434 the object should be registered
435
436 :attr:`__regid__`
437 object identifier in the registry (string like 'main',
438 'primary', 'folder_box')
439
440 :attr:`__select__`
441 the object predicate selectors
442
443 Moreover, the :attr:`__abstract__` attribute may be set to `True`
444 to indicate that an object is abstract and should not be registered
445 (such inherited attributes not considered).
446
447 .. Note::
448
449 When using the store to load objects dynamically, you *always* have
450 to use **super()** to get the methods and attributes of the
451 superclasses, and not use the class identifier. If not, you'll get into
452 trouble at reload time.
453
454 For example, instead of writing::
455
456 class Thing(Parent):
457 __regid__ = 'athing'
458 __select__ = yes()
459
460 def f(self, arg1):
461 Parent.f(self, arg1)
462
463 You must write::
464
465 class Thing(Parent):
466 __regid__ = 'athing'
467 __select__ = yes()
468
469 def f(self, arg1):
470 super(Thing, self).f(arg1)
471
472 Controlling object registration
473 -------------------------------
474
475 Dynamic loading is triggered by calling the
476 :meth:`register_objects` method, given a list of directories to
477 inspect for python modules.
478
479 .. automethod: register_objects
480
481 For each module, by default, all compatible objects are registered
482 automatically. However if some objects come as replacement of
483 other objects, or have to be included only if some condition is
484 met, you'll have to define a `registration_callback(vreg)`
485 function in the module and explicitly register **all objects** in
486 this module, using the api defined below.
487
488
489 .. automethod:: RegistryStore.register_all
490 .. automethod:: RegistryStore.register_and_replace
491 .. automethod:: RegistryStore.register
492 .. automethod:: RegistryStore.unregister
493
494 .. Note::
495 Once the function `registration_callback(vreg)` is implemented in a
496 module, all the objects from this module have to be explicitly
497 registered as it disables the automatic object registration.
498
499
500 Examples:
501
502 .. sourcecode:: python
503
504 def registration_callback(store):
505 # register everything in the module except BabarClass
506 store.register_all(globals().values(), __name__, (BabarClass,))
507
508 # conditionally register BabarClass
509 if 'babar_relation' in store.schema:
510 store.register(BabarClass)
511
512 In this example, we register all application object classes defined in the module
513 except `BabarClass`. This class is then registered only if the 'babar_relation'
514 relation type is defined in the instance schema.
515
516 .. sourcecode:: python
517
518 def registration_callback(store):
519 store.register(Elephant)
520 # replace Babar by Celeste
521 store.register_and_replace(Celeste, Babar)
522
523 In this example, we explicitly register classes one by one:
524
525 * the `Elephant` class
526 * the `Celeste` to replace `Babar`
527
528 If at some point we register a new appobject class in this module, it won't be
529 registered at all without modification to the `registration_callback`
530 implementation. The first example will register it though, thanks to the call
531 to the `register_all` method.
532
533 Controlling registry instantiation
534 ----------------------------------
535
536 The `REGISTRY_FACTORY` class dictionary allows to specify which class should
537 be instantiated for a given registry name. The class associated to `None`
538 key will be the class used when there is no specific class for a name.
539 """
540
544
546 """clear all registries managed by this store"""
547
548 for subdict in self.values():
549 subdict.clear()
550 self._lastmodifs = {}
551
553 """return the registry (dictionary of class objects) associated to
554 this name
555 """
556 try:
557 return super(RegistryStore, self).__getitem__(name)
558 except KeyError:
559 raise RegistryNotFound(name).with_traceback(sys.exc_info()[-1])
560
561
562
563
564 REGISTRY_FACTORY = {None: Registry}
565
567 """return existing registry named regid or use factory to create one and
568 return it"""
569 try:
570 return self.REGISTRY_FACTORY[regid]
571 except KeyError:
572 return self.REGISTRY_FACTORY[None]
573
580
582 """register registrable objects into `objects`.
583
584 Registrable objects are properly configured subclasses of
585 :class:`RegistrableObject`. Objects which are not defined in the module
586 `modname` or which are in `butclasses` won't be registered.
587
588 Typical usage is:
589
590 .. sourcecode:: python
591
592 store.register_all(globals().values(), __name__, (ClassIWantToRegisterExplicitly,))
593
594 So you get partially automatic registration, keeping manual registration
595 for some object (to use
596 :meth:`~logilab.common.registry.RegistryStore.register_and_replace` for
597 instance).
598 """
599 assert isinstance(modname, str), \
600 'modname expected to be a module name (ie string), got %r' % modname
601 for obj in objects:
602 if self.is_registrable(obj) and obj.__module__ == modname and not obj in butclasses:
603 if isinstance(obj, type):
604 self._load_ancestors_then_object(modname, obj, butclasses)
605 else:
606 self.register(obj)
607
608 - def register(self, obj, registryname=None, oid=None, clear=False):
609 """register `obj` implementation into `registryname` or
610 `obj.__registries__` if not specified, with identifier `oid` or
611 `obj.__regid__` if not specified.
612
613 If `clear` is true, all objects with the same identifier will be
614 previously unregistered.
615 """
616 assert not obj.__dict__.get('__abstract__'), obj
617 for registryname in obj_registries(obj, registryname):
618 registry = self.setdefault(registryname)
619 registry.register(obj, oid=oid, clear=clear)
620 self.debug("register %s in %s['%s']",
621 registry.objname(obj), registryname, oid or obj.__regid__)
622 self._loadedmods.setdefault(obj.__module__, {})[registry.objid(obj)] = obj
623
633
635 """register `obj` object into `registryname` or
636 `obj.__registries__` if not specified. If found, the `replaced` object
637 will be unregistered first (else a warning will be issued as it is
638 generally unexpected).
639 """
640 for registryname in obj_registries(obj, registryname):
641 registry = self[registryname]
642 registry.register_and_replace(obj, replaced)
643 self.debug("register %s in %s['%s'] instead of %s",
644 registry.objname(obj), registryname, obj.__regid__,
645 registry.objname(replaced))
646
647
648
650 """reset registry and walk down path to return list of (path, name)
651 file modules to be loaded"""
652
653 self.reset()
654
655 self._toloadmods, filemods = _toload_info(path, extrapath)
656
657
658
659 self._loadedmods = {}
660 return filemods
661
670
672 """call initialization_completed() on all known registries"""
673 for reg in self.values():
674 reg.initialization_completed()
675
677 """ return the modification date of a file path """
678 try:
679 return stat(filepath)[-2]
680 except OSError:
681
682 self.warning('Unable to load %s. It is likely to be a backup file',
683 filepath)
684 return None
685
687 """return True if something module changed and the registry should be
688 reloaded
689 """
690 lastmodifs = self._lastmodifs
691 for fileordir in path:
692 if isdir(fileordir) and exists(join(fileordir, '__init__.py')):
693 if self.is_reload_needed([join(fileordir, fname)
694 for fname in listdir(fileordir)]):
695 return True
696 elif fileordir[-3:] == '.py':
697 mdate = self._mdate(fileordir)
698 if mdate is None:
699 continue
700 elif "flymake" in fileordir:
701
702 continue
703 if fileordir not in lastmodifs or lastmodifs[fileordir] < mdate:
704 self.info('File %s changed since last visit', fileordir)
705 return True
706 return False
707
709 """ load registrable objects (if any) from a python file """
710 from logilab.common.modutils import load_module_from_name
711 if modname in self._loadedmods:
712 return
713 self._loadedmods[modname] = {}
714 mdate = self._mdate(filepath)
715 if mdate is None:
716 return
717 elif "flymake" in filepath:
718
719 return
720
721
722
723 self._lastmodifs[filepath] = mdate
724
725 module = load_module_from_name(modname)
726 self.load_module(module)
727
729 """Automatically handle module objects registration.
730
731 Instances are registered as soon as they are hashable and have the
732 following attributes:
733
734 * __regid__ (a string)
735 * __select__ (a callable)
736 * __registries__ (a tuple/list of string)
737
738 For classes this is a bit more complicated :
739
740 - first ensure parent classes are already registered
741
742 - class with __abstract__ == True in their local dictionary are skipped
743
744 - object class needs to have registries and identifier properly set to a
745 non empty string to be registered.
746 """
747 self.info('loading %s from %s', module.__name__, module.__file__)
748 if hasattr(module, 'registration_callback'):
749 module.registration_callback(self)
750 else:
751 self.register_all(iter(vars(module).values()), module.__name__)
752
754 """handle class registration according to rules defined in
755 :meth:`load_module`
756 """
757
758 if not isinstance(objectcls, type):
759 if self.is_registrable(objectcls) and objectcls.__module__ == modname:
760 self.register(objectcls)
761 return
762
763 objmodname = objectcls.__module__
764 if objmodname != modname:
765
766
767
768 if objmodname in self._toloadmods:
769
770
771 self.load_file(self._toloadmods[objmodname], objmodname)
772 return
773
774 clsid = '%s.%s' % (modname, objectcls.__name__)
775 if clsid in self._loadedmods[modname]:
776 return
777 self._loadedmods[modname][clsid] = objectcls
778
779 for parent in objectcls.__bases__:
780 self._load_ancestors_then_object(modname, parent, butclasses)
781
782 if objectcls in butclasses or not self.is_registrable(objectcls):
783 return
784
785 reg = self.setdefault(obj_registries(objectcls)[0])
786 if reg.objname(objectcls)[0] == '_':
787 warn("[lgc 0.59] object whose name start with '_' won't be "
788 "skipped anymore at some point, use __abstract__ = True "
789 "instead (%s)" % objectcls, DeprecationWarning)
790 return
791
792 self.register(objectcls)
793
794 @classmethod
796 """ensure `obj` should be registered
797
798 as arbitrary stuff may be registered, do a lot of check and warn about
799 weird cases (think to dumb proxy objects)
800 """
801 if isinstance(obj, type):
802 if not issubclass(obj, RegistrableObject):
803
804 if not (getattr(obj, '__registries__', None)
805 and getattr(obj, '__regid__', None)
806 and getattr(obj, '__select__', None)):
807 return False
808 elif issubclass(obj, RegistrableInstance):
809 return False
810 elif not isinstance(obj, RegistrableInstance):
811 return False
812 if not obj.__regid__:
813 return False
814 registries = obj.__registries__
815 if not registries:
816 return False
817 selector = obj.__select__
818 if not selector:
819 return False
820 if obj.__dict__.get('__abstract__', False):
821 return False
822
823 if not isinstance(registries, (tuple, list)):
824 cls.warning('%s has __registries__ which is not a list or tuple', obj)
825 return False
826 if not isinstance(selector, collections.Callable):
827 cls.warning('%s has not callable __select__', obj)
828 return False
829 return True
830
831
832
833 info = warning = error = critical = exception = debug = lambda msg, *a, **kw: None
834
835
836
837 set_log_methods(RegistryStore, getLogger('registry.store'))
838 set_log_methods(Registry, getLogger('registry'))
839
840
841
842 TRACED_OIDS = None
848
850 """use this decorator on your predicates so they become traceable with
851 :class:`traced_selection`
852 """
853 def traced(cls, *args, **kwargs):
854 ret = selector(cls, *args, **kwargs)
855 if TRACED_OIDS is not None:
856 _trace_selector(cls, selector, args, ret)
857 return ret
858 traced.__name__ = selector.__name__
859 traced.__doc__ = selector.__doc__
860 return traced
861
863 """
864 Typical usage is :
865
866 .. sourcecode:: python
867
868 >>> from logilab.common.registry import traced_selection
869 >>> with traced_selection():
870 ... # some code in which you want to debug selectors
871 ... # for all objects
872
873 Don't forget the 'from __future__ import with_statement' at the module top-level
874 if you're using python prior to 2.6.
875
876 This will yield lines like this in the logs::
877
878 selector one_line_rset returned 0 for <class 'elephant.Babar'>
879
880 You can also give to :class:`traced_selection` the identifiers of objects on
881 which you want to debug selection ('oid1' and 'oid2' in the example above).
882
883 .. sourcecode:: python
884
885 >>> with traced_selection( ('regid1', 'regid2') ):
886 ... # some code in which you want to debug selectors
887 ... # for objects with __regid__ 'regid1' and 'regid2'
888
889 A potentially useful point to set up such a tracing function is
890 the `logilab.common.registry.Registry.select` method body.
891 """
892
895
899
900 - def __exit__(self, exctype, exc, traceback):
904
908 """Most of the time, a simple score function is enough to build a selector.
909 The :func:`objectify_predicate` decorator turn it into a proper selector
910 class::
911
912 @objectify_predicate
913 def one(cls, req, rset=None, **kwargs):
914 return 1
915
916 class MyView(View):
917 __select__ = View.__select__ & one()
918
919 """
920 return type(selector_func.__name__, (Predicate,),
921 {'__doc__': selector_func.__doc__,
922 '__call__': lambda self, *a, **kw: selector_func(*a, **kw)})
923
924
925 _PREDICATES = {}
928 for predicate in _PREDICATES.values():
929 if not '_decorators' in predicate.__dict__:
930 predicate._decorators = set()
931 if decorator in predicate._decorators:
932 continue
933 predicate._decorators.add(decorator)
934 predicate.__call__ = decorator(predicate.__call__)
935
943
944 -class Predicate(object, metaclass=PredicateMetaClass):
945 """base class for selector classes providing implementation
946 for operators ``&``, ``|`` and ``~``
947
948 This class is only here to give access to binary operators, the selector
949 logic itself should be implemented in the :meth:`__call__` method. Notice it
950 should usually accept any arbitrary arguments (the context), though that may
951 vary depending on your usage of the registry.
952
953 a selector is called to help choosing the correct object for a
954 particular context by returning a score (`int`) telling how well
955 the implementation given as first argument fit to the given context.
956
957 0 score means that the class doesn't apply.
958 """
959
960 @property
962
963 return self.__class__.__name__
964
966 """search for the given selector, selector instance or tuple of
967 selectors in the selectors tree. Return None if not found.
968 """
969 if self is selector:
970 return self
971 if (isinstance(selector, type) or isinstance(selector, tuple)) and \
972 isinstance(self, selector):
973 return self
974 return None
975
977 return self.__class__.__name__
978
991
994
995
996
997 - def __call__(self, cls, *args, **kwargs):
998 return NotImplementedError("selector %s must implement its logic "
999 "in its __call__ method" % self.__class__)
1000
1002 return '<Predicate %s at %x>' % (self.__class__.__name__, id(self))
1003
1006 """base class for compound selector classes"""
1007
1010
1012 return '%s(%s)' % (self.__class__.__name__,
1013 ','.join(str(s) for s in self.selectors))
1014
1015 @classmethod
1017 """deal with selector instanciation when necessary and merge
1018 multi-selectors if possible:
1019
1020 AndPredicate(AndPredicate(sel1, sel2), AndPredicate(sel3, sel4))
1021 ==> AndPredicate(sel1, sel2, sel3, sel4)
1022 """
1023 merged_selectors = []
1024 for selector in selectors:
1025
1026
1027 if isinstance(selector, types.FunctionType):
1028 selector = objectify_predicate(selector)()
1029 if isinstance(selector, type) and issubclass(selector, Predicate):
1030 selector = selector()
1031 assert isinstance(selector, Predicate), selector
1032 if isinstance(selector, cls):
1033 merged_selectors += selector.selectors
1034 else:
1035 merged_selectors.append(selector)
1036 return merged_selectors
1037
1039 """search for the given selector or selector instance (or tuple of
1040 selectors) in the selectors tree. Return None if not found
1041 """
1042 for childselector in self.selectors:
1043 if childselector is selector:
1044 return childselector
1045 found = childselector.search_selector(selector)
1046 if found is not None:
1047 return found
1048
1049 return super(MultiPredicate, self).search_selector(selector)
1050
1053 """and-chained selectors"""
1054 - def __call__(self, cls, *args, **kwargs):
1055 score = 0
1056 for selector in self.selectors:
1057 partscore = selector(cls, *args, **kwargs)
1058 if not partscore:
1059 return 0
1060 score += partscore
1061 return score
1062
1065 """or-chained selectors"""
1066 - def __call__(self, cls, *args, **kwargs):
1067 for selector in self.selectors:
1068 partscore = selector(cls, *args, **kwargs)
1069 if partscore:
1070 return partscore
1071 return 0
1072
1074 """negation selector"""
1076 self.selector = selector
1077
1078 - def __call__(self, cls, *args, **kwargs):
1079 score = self.selector(cls, *args, **kwargs)
1080 return int(not score)
1081
1083 return 'NOT(%s)' % self.selector
1084
1085
1086 -class yes(Predicate):
1087 """Return the score given as parameter, with a default score of 0.5 so any
1088 other selector take precedence.
1089
1090 Usually used for objects which can be selected whatever the context, or
1091 also sometimes to add arbitrary points to a score.
1092
1093 Take care, `yes(0)` could be named 'no'...
1094 """
1097
1100
1101
1102
1103
1104 from logilab.common.deprecation import deprecated
1105
1106 @deprecated('[lgc 0.59] use Registry.objid class method instead')
1107 -def classid(cls):
1108 return '%s.%s' % (cls.__module__, cls.__name__)
1109
1110 @deprecated('[lgc 0.59] use obj_registries function instead')
1111 -def class_registries(cls, registryname):
1113