Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(482)

Side by Side Diff: third_party/logilab/logilab/common/registry.py

Issue 1920403002: [content/test/gpu] Run pylint check of gpu tests in unittest instead of PRESUBMIT (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Update path to LICENSE.txt of logilab/README.chromium Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
3 #
4 # This file is part of Logilab-common.
5 #
6 # Logilab-common is free software: you can redistribute it and/or modify it
7 # under the terms of the GNU Lesser General Public License as published by the
8 # Free Software Foundation, either version 2.1 of the License, or (at your
9 # option) any later version.
10 #
11 # Logilab-common is distributed in the hope that it will be useful, but WITHOUT
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
14 # details.
15 #
16 # You should have received a copy of the GNU Lesser General Public License along
17 # with Logilab-common. If not, see <http://www.gnu.org/licenses/>.
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 from __future__ import print_function
77
78 __docformat__ = "restructuredtext en"
79
80 import sys
81 import types
82 import weakref
83 import traceback as tb
84 from os import listdir, stat
85 from os.path import join, isdir, exists
86 from logging import getLogger
87 from warnings import warn
88
89 from six import string_types, add_metaclass
90
91 from logilab.common.modutils import modpath_from_file
92 from logilab.common.logging_ext import set_log_methods
93 from logilab.common.decorators import classproperty
94
95
96 class RegistryException(Exception):
97 """Base class for registry exception."""
98
99 class RegistryNotFound(RegistryException):
100 """Raised when an unknown registry is requested.
101
102 This is usually a programming/typo error.
103 """
104
105 class ObjectNotFound(RegistryException):
106 """Raised when an unregistered object is requested.
107
108 This may be a programming/typo or a misconfiguration error.
109 """
110
111 class NoSelectableObject(RegistryException):
112 """Raised when no object is selectable for a given context."""
113 def __init__(self, args, kwargs, objects):
114 self.args = args
115 self.kwargs = kwargs
116 self.objects = objects
117
118 def __str__(self):
119 return ('args: %s, kwargs: %s\ncandidates: %s'
120 % (self.args, self.kwargs.keys(), self.objects))
121
122
123 def _modname_from_path(path, extrapath=None):
124 modpath = modpath_from_file(path, extrapath)
125 # omit '__init__' from package's name to avoid loading that module
126 # once for each name when it is imported by some other object
127 # module. This supposes import in modules are done as::
128 #
129 # from package import something
130 #
131 # not::
132 #
133 # from package.__init__ import something
134 #
135 # which seems quite correct.
136 if modpath[-1] == '__init__':
137 modpath.pop()
138 return '.'.join(modpath)
139
140
141 def _toload_info(path, extrapath, _toload=None):
142 """Return a dictionary of <modname>: <modpath> and an ordered list of
143 (file, module name) to load
144 """
145 if _toload is None:
146 assert isinstance(path, list)
147 _toload = {}, []
148 for fileordir in path:
149 if isdir(fileordir) and exists(join(fileordir, '__init__.py')):
150 subfiles = [join(fileordir, fname) for fname in listdir(fileordir)]
151 _toload_info(subfiles, extrapath, _toload)
152 elif fileordir[-3:] == '.py':
153 modname = _modname_from_path(fileordir, extrapath)
154 _toload[0][modname] = fileordir
155 _toload[1].append((fileordir, modname))
156 return _toload
157
158
159 class RegistrableObject(object):
160 """This is the base class for registrable objects which are selected
161 according to a context.
162
163 :attr:`__registry__`
164 name of the registry for this object (string like 'views',
165 'templates'...). You may want to define `__registries__` directly if your
166 object should be registered in several registries.
167
168 :attr:`__regid__`
169 object's identifier in the registry (string like 'main',
170 'primary', 'folder_box')
171
172 :attr:`__select__`
173 class'selector
174
175 Moreover, the `__abstract__` attribute may be set to True to indicate that a
176 class is abstract and should not be registered.
177
178 You don't have to inherit from this class to put it in a registry (having
179 `__regid__` and `__select__` is enough), though this is needed for classes
180 that should be automatically registered.
181 """
182
183 __registry__ = None
184 __regid__ = None
185 __select__ = None
186 __abstract__ = True # see doc snipppets below (in Registry class)
187
188 @classproperty
189 def __registries__(cls):
190 if cls.__registry__ is None:
191 return ()
192 return (cls.__registry__,)
193
194
195 class RegistrableInstance(RegistrableObject):
196 """Inherit this class if you want instances of the classes to be
197 automatically registered.
198 """
199
200 def __new__(cls, *args, **kwargs):
201 """Add a __module__ attribute telling the module where the instance was
202 created, for automatic registration.
203 """
204 obj = super(RegistrableInstance, cls).__new__(cls)
205 # XXX subclass must no override __new__
206 filepath = tb.extract_stack(limit=2)[0][0]
207 obj.__module__ = _modname_from_path(filepath)
208 return obj
209
210
211 class Registry(dict):
212 """The registry store a set of implementations associated to identifier:
213
214 * to each identifier are associated a list of implementations
215
216 * to select an implementation of a given identifier, you should use one of t he
217 :meth:`select` or :meth:`select_or_none` method
218
219 * to select a list of implementations for a context, you should use the
220 :meth:`possible_objects` method
221
222 * dictionary like access to an identifier will return the bare list of
223 implementations for this identifier.
224
225 To be usable in a registry, the only requirement is to have a `__select__`
226 attribute.
227
228 At the end of the registration process, the :meth:`__registered__`
229 method is called on each registered object which have them, given the
230 registry in which it's registered as argument.
231
232 Registration methods:
233
234 .. automethod: register
235 .. automethod: unregister
236
237 Selection methods:
238
239 .. automethod: select
240 .. automethod: select_or_none
241 .. automethod: possible_objects
242 .. automethod: object_by_id
243 """
244 def __init__(self, debugmode):
245 super(Registry, self).__init__()
246 self.debugmode = debugmode
247
248 def __getitem__(self, name):
249 """return the registry (list of implementation objects) associated to
250 this name
251 """
252 try:
253 return super(Registry, self).__getitem__(name)
254 except KeyError:
255 exc = ObjectNotFound(name)
256 exc.__traceback__ = sys.exc_info()[-1]
257 raise exc
258
259 @classmethod
260 def objid(cls, obj):
261 """returns a unique identifier for an object stored in the registry"""
262 return '%s.%s' % (obj.__module__, cls.objname(obj))
263
264 @classmethod
265 def objname(cls, obj):
266 """returns a readable name for an object stored in the registry"""
267 return getattr(obj, '__name__', id(obj))
268
269 def initialization_completed(self):
270 """call method __registered__() on registered objects when the callback
271 is defined"""
272 for objects in self.values():
273 for objectcls in objects:
274 registered = getattr(objectcls, '__registered__', None)
275 if registered:
276 registered(self)
277 if self.debugmode:
278 wrap_predicates(_lltrace)
279
280 def register(self, obj, oid=None, clear=False):
281 """base method to add an object in the registry"""
282 assert not '__abstract__' in obj.__dict__, obj
283 assert obj.__select__, obj
284 oid = oid or obj.__regid__
285 assert oid, ('no explicit name supplied to register object %s, '
286 'which has no __regid__ set' % obj)
287 if clear:
288 objects = self[oid] = []
289 else:
290 objects = self.setdefault(oid, [])
291 assert not obj in objects, 'object %s is already registered' % obj
292 objects.append(obj)
293
294 def register_and_replace(self, obj, replaced):
295 """remove <replaced> and register <obj>"""
296 # XXXFIXME this is a duplication of unregister()
297 # remove register_and_replace in favor of unregister + register
298 # or simplify by calling unregister then register here
299 if not isinstance(replaced, string_types):
300 replaced = self.objid(replaced)
301 # prevent from misspelling
302 assert obj is not replaced, 'replacing an object by itself: %s' % obj
303 registered_objs = self.get(obj.__regid__, ())
304 for index, registered in enumerate(registered_objs):
305 if self.objid(registered) == replaced:
306 del registered_objs[index]
307 break
308 else:
309 self.warning('trying to replace %s that is not registered with %s',
310 replaced, obj)
311 self.register(obj)
312
313 def unregister(self, obj):
314 """remove object <obj> from this registry"""
315 objid = self.objid(obj)
316 oid = obj.__regid__
317 for registered in self.get(oid, ()):
318 # use self.objid() to compare objects because vreg will probably
319 # have its own version of the object, loaded through execfile
320 if self.objid(registered) == objid:
321 self[oid].remove(registered)
322 break
323 else:
324 self.warning('can\'t remove %s, no id %s in the registry',
325 objid, oid)
326
327 def all_objects(self):
328 """return a list containing all objects in this registry.
329 """
330 result = []
331 for objs in self.values():
332 result += objs
333 return result
334
335 # dynamic selection methods ################################################
336
337 def object_by_id(self, oid, *args, **kwargs):
338 """return object with the `oid` identifier. Only one object is expected
339 to be found.
340
341 raise :exc:`ObjectNotFound` if there are no object with id `oid` in this
342 registry
343
344 raise :exc:`AssertionError` if there is more than one object there
345 """
346 objects = self[oid]
347 assert len(objects) == 1, objects
348 return objects[0](*args, **kwargs)
349
350 def select(self, __oid, *args, **kwargs):
351 """return the most specific object among those with the given oid
352 according to the given context.
353
354 raise :exc:`ObjectNotFound` if there are no object with id `oid` in this
355 registry
356
357 raise :exc:`NoSelectableObject` if no object can be selected
358 """
359 obj = self._select_best(self[__oid], *args, **kwargs)
360 if obj is None:
361 raise NoSelectableObject(args, kwargs, self[__oid] )
362 return obj
363
364 def select_or_none(self, __oid, *args, **kwargs):
365 """return the most specific object among those with the given oid
366 according to the given context, or None if no object applies.
367 """
368 try:
369 return self._select_best(self[__oid], *args, **kwargs)
370 except ObjectNotFound:
371 return None
372
373 def possible_objects(self, *args, **kwargs):
374 """return an iterator on possible objects in this registry for the given
375 context
376 """
377 for objects in self.values():
378 obj = self._select_best(objects, *args, **kwargs)
379 if obj is None:
380 continue
381 yield obj
382
383 def _select_best(self, objects, *args, **kwargs):
384 """return an instance of the most specific object according
385 to parameters
386
387 return None if not object apply (don't raise `NoSelectableObject` since
388 it's costly when searching objects using `possible_objects`
389 (e.g. searching for hooks).
390 """
391 score, winners = 0, None
392 for obj in objects:
393 objectscore = obj.__select__(obj, *args, **kwargs)
394 if objectscore > score:
395 score, winners = objectscore, [obj]
396 elif objectscore > 0 and objectscore == score:
397 winners.append(obj)
398 if winners is None:
399 return None
400 if len(winners) > 1:
401 # log in production environement / test, error while debugging
402 msg = 'select ambiguity: %s\n(args: %s, kwargs: %s)'
403 if self.debugmode:
404 # raise bare exception in debug mode
405 raise Exception(msg % (winners, args, kwargs.keys()))
406 self.error(msg, winners, args, kwargs.keys())
407 # return the result of calling the object
408 return self.selected(winners[0], args, kwargs)
409
410 def selected(self, winner, args, kwargs):
411 """override here if for instance you don't want "instanciation"
412 """
413 return winner(*args, **kwargs)
414
415 # these are overridden by set_log_methods below
416 # only defining here to prevent pylint from complaining
417 info = warning = error = critical = exception = debug = lambda msg, *a, **kw : None
418
419
420 def obj_registries(cls, registryname=None):
421 """return a tuple of registry names (see __registries__)"""
422 if registryname:
423 return (registryname,)
424 return cls.__registries__
425
426
427 class RegistryStore(dict):
428 """This class is responsible for loading objects and storing them
429 in their registry which is created on the fly as needed.
430
431 It handles dynamic registration of objects and provides a
432 convenient api to access them. To be recognized as an object that
433 should be stored into one of the store's registry
434 (:class:`Registry`), an object must provide the following
435 attributes, used control how they interact with the registry:
436
437 :attr:`__registries__`
438 list of registry names (string like 'views', 'templates'...) into which
439 the object should be registered
440
441 :attr:`__regid__`
442 object identifier in the registry (string like 'main',
443 'primary', 'folder_box')
444
445 :attr:`__select__`
446 the object predicate selectors
447
448 Moreover, the :attr:`__abstract__` attribute may be set to `True`
449 to indicate that an object is abstract and should not be registered
450 (such inherited attributes not considered).
451
452 .. Note::
453
454 When using the store to load objects dynamically, you *always* have
455 to use **super()** to get the methods and attributes of the
456 superclasses, and not use the class identifier. If not, you'll get into
457 trouble at reload time.
458
459 For example, instead of writing::
460
461 class Thing(Parent):
462 __regid__ = 'athing'
463 __select__ = yes()
464
465 def f(self, arg1):
466 Parent.f(self, arg1)
467
468 You must write::
469
470 class Thing(Parent):
471 __regid__ = 'athing'
472 __select__ = yes()
473
474 def f(self, arg1):
475 super(Thing, self).f(arg1)
476
477 Controlling object registration
478 -------------------------------
479
480 Dynamic loading is triggered by calling the
481 :meth:`register_objects` method, given a list of directories to
482 inspect for python modules.
483
484 .. automethod: register_objects
485
486 For each module, by default, all compatible objects are registered
487 automatically. However if some objects come as replacement of
488 other objects, or have to be included only if some condition is
489 met, you'll have to define a `registration_callback(vreg)`
490 function in the module and explicitly register **all objects** in
491 this module, using the api defined below.
492
493
494 .. automethod:: RegistryStore.register_all
495 .. automethod:: RegistryStore.register_and_replace
496 .. automethod:: RegistryStore.register
497 .. automethod:: RegistryStore.unregister
498
499 .. Note::
500 Once the function `registration_callback(vreg)` is implemented in a
501 module, all the objects from this module have to be explicitly
502 registered as it disables the automatic object registration.
503
504
505 Examples:
506
507 .. sourcecode:: python
508
509 def registration_callback(store):
510 # register everything in the module except BabarClass
511 store.register_all(globals().values(), __name__, (BabarClass,))
512
513 # conditionally register BabarClass
514 if 'babar_relation' in store.schema:
515 store.register(BabarClass)
516
517 In this example, we register all application object classes defined in the m odule
518 except `BabarClass`. This class is then registered only if the 'babar_relati on'
519 relation type is defined in the instance schema.
520
521 .. sourcecode:: python
522
523 def registration_callback(store):
524 store.register(Elephant)
525 # replace Babar by Celeste
526 store.register_and_replace(Celeste, Babar)
527
528 In this example, we explicitly register classes one by one:
529
530 * the `Elephant` class
531 * the `Celeste` to replace `Babar`
532
533 If at some point we register a new appobject class in this module, it won't be
534 registered at all without modification to the `registration_callback`
535 implementation. The first example will register it though, thanks to the cal l
536 to the `register_all` method.
537
538 Controlling registry instantiation
539 ----------------------------------
540
541 The `REGISTRY_FACTORY` class dictionary allows to specify which class should
542 be instantiated for a given registry name. The class associated to `None`
543 key will be the class used when there is no specific class for a name.
544 """
545
546 def __init__(self, debugmode=False):
547 super(RegistryStore, self).__init__()
548 self.debugmode = debugmode
549
550 def reset(self):
551 """clear all registries managed by this store"""
552 # don't use self.clear, we want to keep existing subdictionaries
553 for subdict in self.values():
554 subdict.clear()
555 self._lastmodifs = {}
556
557 def __getitem__(self, name):
558 """return the registry (dictionary of class objects) associated to
559 this name
560 """
561 try:
562 return super(RegistryStore, self).__getitem__(name)
563 except KeyError:
564 exc = RegistryNotFound(name)
565 exc.__traceback__ = sys.exc_info()[-1]
566 raise exc
567
568 # methods for explicit (un)registration ###################################
569
570 # default class, when no specific class set
571 REGISTRY_FACTORY = {None: Registry}
572
573 def registry_class(self, regid):
574 """return existing registry named regid or use factory to create one and
575 return it"""
576 try:
577 return self.REGISTRY_FACTORY[regid]
578 except KeyError:
579 return self.REGISTRY_FACTORY[None]
580
581 def setdefault(self, regid):
582 try:
583 return self[regid]
584 except RegistryNotFound:
585 self[regid] = self.registry_class(regid)(self.debugmode)
586 return self[regid]
587
588 def register_all(self, objects, modname, butclasses=()):
589 """register registrable objects into `objects`.
590
591 Registrable objects are properly configured subclasses of
592 :class:`RegistrableObject`. Objects which are not defined in the module
593 `modname` or which are in `butclasses` won't be registered.
594
595 Typical usage is:
596
597 .. sourcecode:: python
598
599 store.register_all(globals().values(), __name__, (ClassIWantToRegist erExplicitly,))
600
601 So you get partially automatic registration, keeping manual registration
602 for some object (to use
603 :meth:`~logilab.common.registry.RegistryStore.register_and_replace` for
604 instance).
605 """
606 assert isinstance(modname, string_types), \
607 'modname expected to be a module name (ie string), got %r' % modname
608 for obj in objects:
609 if self.is_registrable(obj) and obj.__module__ == modname and not ob j in butclasses:
610 if isinstance(obj, type):
611 self._load_ancestors_then_object(modname, obj, butclasses)
612 else:
613 self.register(obj)
614
615 def register(self, obj, registryname=None, oid=None, clear=False):
616 """register `obj` implementation into `registryname` or
617 `obj.__registries__` if not specified, with identifier `oid` or
618 `obj.__regid__` if not specified.
619
620 If `clear` is true, all objects with the same identifier will be
621 previously unregistered.
622 """
623 assert not obj.__dict__.get('__abstract__'), obj
624 for registryname in obj_registries(obj, registryname):
625 registry = self.setdefault(registryname)
626 registry.register(obj, oid=oid, clear=clear)
627 self.debug("register %s in %s['%s']",
628 registry.objname(obj), registryname, oid or obj.__regid__ )
629 self._loadedmods.setdefault(obj.__module__, {})[registry.objid(obj)] = obj
630
631 def unregister(self, obj, registryname=None):
632 """unregister `obj` object from the registry `registryname` or
633 `obj.__registries__` if not specified.
634 """
635 for registryname in obj_registries(obj, registryname):
636 registry = self[registryname]
637 registry.unregister(obj)
638 self.debug("unregister %s from %s['%s']",
639 registry.objname(obj), registryname, obj.__regid__)
640
641 def register_and_replace(self, obj, replaced, registryname=None):
642 """register `obj` object into `registryname` or
643 `obj.__registries__` if not specified. If found, the `replaced` object
644 will be unregistered first (else a warning will be issued as it is
645 generally unexpected).
646 """
647 for registryname in obj_registries(obj, registryname):
648 registry = self[registryname]
649 registry.register_and_replace(obj, replaced)
650 self.debug("register %s in %s['%s'] instead of %s",
651 registry.objname(obj), registryname, obj.__regid__,
652 registry.objname(replaced))
653
654 # initialization methods ###################################################
655
656 def init_registration(self, path, extrapath=None):
657 """reset registry and walk down path to return list of (path, name)
658 file modules to be loaded"""
659 # XXX make this private by renaming it to _init_registration ?
660 self.reset()
661 # compute list of all modules that have to be loaded
662 self._toloadmods, filemods = _toload_info(path, extrapath)
663 # XXX is _loadedmods still necessary ? It seems like it's useful
664 # to avoid loading same module twice, especially with the
665 # _load_ancestors_then_object logic but this needs to be checked
666 self._loadedmods = {}
667 return filemods
668
669 def register_objects(self, path, extrapath=None):
670 """register all objects found walking down <path>"""
671 # load views from each directory in the instance's path
672 # XXX inline init_registration ?
673 filemods = self.init_registration(path, extrapath)
674 for filepath, modname in filemods:
675 self.load_file(filepath, modname)
676 self.initialization_completed()
677
678 def initialization_completed(self):
679 """call initialization_completed() on all known registries"""
680 for reg in self.values():
681 reg.initialization_completed()
682
683 def _mdate(self, filepath):
684 """ return the modification date of a file path """
685 try:
686 return stat(filepath)[-2]
687 except OSError:
688 # this typically happens on emacs backup files (.#foo.py)
689 self.warning('Unable to load %s. It is likely to be a backup file',
690 filepath)
691 return None
692
693 def is_reload_needed(self, path):
694 """return True if something module changed and the registry should be
695 reloaded
696 """
697 lastmodifs = self._lastmodifs
698 for fileordir in path:
699 if isdir(fileordir) and exists(join(fileordir, '__init__.py')):
700 if self.is_reload_needed([join(fileordir, fname)
701 for fname in listdir(fileordir)]):
702 return True
703 elif fileordir[-3:] == '.py':
704 mdate = self._mdate(fileordir)
705 if mdate is None:
706 continue # backup file, see _mdate implementation
707 elif "flymake" in fileordir:
708 # flymake + pylint in use, don't consider these they will co rrupt the registry
709 continue
710 if fileordir not in lastmodifs or lastmodifs[fileordir] < mdate:
711 self.info('File %s changed since last visit', fileordir)
712 return True
713 return False
714
715 def load_file(self, filepath, modname):
716 """ load registrable objects (if any) from a python file """
717 from logilab.common.modutils import load_module_from_name
718 if modname in self._loadedmods:
719 return
720 self._loadedmods[modname] = {}
721 mdate = self._mdate(filepath)
722 if mdate is None:
723 return # backup file, see _mdate implementation
724 elif "flymake" in filepath:
725 # flymake + pylint in use, don't consider these they will corrupt th e registry
726 return
727 # set update time before module loading, else we get some reloading
728 # weirdness in case of syntax error or other error while importing the
729 # module
730 self._lastmodifs[filepath] = mdate
731 # load the module
732 module = load_module_from_name(modname)
733 self.load_module(module)
734
735 def load_module(self, module):
736 """Automatically handle module objects registration.
737
738 Instances are registered as soon as they are hashable and have the
739 following attributes:
740
741 * __regid__ (a string)
742 * __select__ (a callable)
743 * __registries__ (a tuple/list of string)
744
745 For classes this is a bit more complicated :
746
747 - first ensure parent classes are already registered
748
749 - class with __abstract__ == True in their local dictionary are skipped
750
751 - object class needs to have registries and identifier properly set to a
752 non empty string to be registered.
753 """
754 self.info('loading %s from %s', module.__name__, module.__file__)
755 if hasattr(module, 'registration_callback'):
756 module.registration_callback(self)
757 else:
758 self.register_all(vars(module).values(), module.__name__)
759
760 def _load_ancestors_then_object(self, modname, objectcls, butclasses=()):
761 """handle class registration according to rules defined in
762 :meth:`load_module`
763 """
764 # backward compat, we used to allow whatever else than classes
765 if not isinstance(objectcls, type):
766 if self.is_registrable(objectcls) and objectcls.__module__ == modnam e:
767 self.register(objectcls)
768 return
769 # imported classes
770 objmodname = objectcls.__module__
771 if objmodname != modname:
772 # The module of the object is not the same as the currently
773 # worked on module, or this is actually an instance, which
774 # has no module at all
775 if objmodname in self._toloadmods:
776 # if this is still scheduled for loading, let's proceed immediat ely,
777 # but using the object module
778 self.load_file(self._toloadmods[objmodname], objmodname)
779 return
780 # ensure object hasn't been already processed
781 clsid = '%s.%s' % (modname, objectcls.__name__)
782 if clsid in self._loadedmods[modname]:
783 return
784 self._loadedmods[modname][clsid] = objectcls
785 # ensure ancestors are registered
786 for parent in objectcls.__bases__:
787 self._load_ancestors_then_object(modname, parent, butclasses)
788 # ensure object is registrable
789 if objectcls in butclasses or not self.is_registrable(objectcls):
790 return
791 # backward compat
792 reg = self.setdefault(obj_registries(objectcls)[0])
793 if reg.objname(objectcls)[0] == '_':
794 warn("[lgc 0.59] object whose name start with '_' won't be "
795 "skipped anymore at some point, use __abstract__ = True "
796 "instead (%s)" % objectcls, DeprecationWarning)
797 return
798 # register, finally
799 self.register(objectcls)
800
801 @classmethod
802 def is_registrable(cls, obj):
803 """ensure `obj` should be registered
804
805 as arbitrary stuff may be registered, do a lot of check and warn about
806 weird cases (think to dumb proxy objects)
807 """
808 if isinstance(obj, type):
809 if not issubclass(obj, RegistrableObject):
810 # ducktyping backward compat
811 if not (getattr(obj, '__registries__', None)
812 and getattr(obj, '__regid__', None)
813 and getattr(obj, '__select__', None)):
814 return False
815 elif issubclass(obj, RegistrableInstance):
816 return False
817 elif not isinstance(obj, RegistrableInstance):
818 return False
819 if not obj.__regid__:
820 return False # no regid
821 registries = obj.__registries__
822 if not registries:
823 return False # no registries
824 selector = obj.__select__
825 if not selector:
826 return False # no selector
827 if obj.__dict__.get('__abstract__', False):
828 return False
829 # then detect potential problems that should be warned
830 if not isinstance(registries, (tuple, list)):
831 cls.warning('%s has __registries__ which is not a list or tuple', ob j)
832 return False
833 if not callable(selector):
834 cls.warning('%s has not callable __select__', obj)
835 return False
836 return True
837
838 # these are overridden by set_log_methods below
839 # only defining here to prevent pylint from complaining
840 info = warning = error = critical = exception = debug = lambda msg, *a, **kw : None
841
842
843 # init logging
844 set_log_methods(RegistryStore, getLogger('registry.store'))
845 set_log_methods(Registry, getLogger('registry'))
846
847
848 # helpers for debugging selectors
849 TRACED_OIDS = None
850
851 def _trace_selector(cls, selector, args, ret):
852 vobj = args[0]
853 if TRACED_OIDS == 'all' or vobj.__regid__ in TRACED_OIDS:
854 print('%s -> %s for %s(%s)' % (cls, ret, vobj, vobj.__regid__))
855
856 def _lltrace(selector):
857 """use this decorator on your predicates so they become traceable with
858 :class:`traced_selection`
859 """
860 def traced(cls, *args, **kwargs):
861 ret = selector(cls, *args, **kwargs)
862 if TRACED_OIDS is not None:
863 _trace_selector(cls, selector, args, ret)
864 return ret
865 traced.__name__ = selector.__name__
866 traced.__doc__ = selector.__doc__
867 return traced
868
869 class traced_selection(object): # pylint: disable=C0103
870 """
871 Typical usage is :
872
873 .. sourcecode:: python
874
875 >>> from logilab.common.registry import traced_selection
876 >>> with traced_selection():
877 ... # some code in which you want to debug selectors
878 ... # for all objects
879
880 This will yield lines like this in the logs::
881
882 selector one_line_rset returned 0 for <class 'elephant.Babar'>
883
884 You can also give to :class:`traced_selection` the identifiers of objects on
885 which you want to debug selection ('oid1' and 'oid2' in the example above).
886
887 .. sourcecode:: python
888
889 >>> with traced_selection( ('regid1', 'regid2') ):
890 ... # some code in which you want to debug selectors
891 ... # for objects with __regid__ 'regid1' and 'regid2'
892
893 A potentially useful point to set up such a tracing function is
894 the `logilab.common.registry.Registry.select` method body.
895 """
896
897 def __init__(self, traced='all'):
898 self.traced = traced
899
900 def __enter__(self):
901 global TRACED_OIDS
902 TRACED_OIDS = self.traced
903
904 def __exit__(self, exctype, exc, traceback):
905 global TRACED_OIDS
906 TRACED_OIDS = None
907 return traceback is None
908
909 # selector base classes and operations ########################################
910
911 def objectify_predicate(selector_func):
912 """Most of the time, a simple score function is enough to build a selector.
913 The :func:`objectify_predicate` decorator turn it into a proper selector
914 class::
915
916 @objectify_predicate
917 def one(cls, req, rset=None, **kwargs):
918 return 1
919
920 class MyView(View):
921 __select__ = View.__select__ & one()
922
923 """
924 return type(selector_func.__name__, (Predicate,),
925 {'__doc__': selector_func.__doc__,
926 '__call__': lambda self, *a, **kw: selector_func(*a, **kw)})
927
928
929 _PREDICATES = {}
930
931 def wrap_predicates(decorator):
932 for predicate in _PREDICATES.values():
933 if not '_decorators' in predicate.__dict__:
934 predicate._decorators = set()
935 if decorator in predicate._decorators:
936 continue
937 predicate._decorators.add(decorator)
938 predicate.__call__ = decorator(predicate.__call__)
939
940 class PredicateMetaClass(type):
941 def __new__(mcs, *args, **kwargs):
942 # use __new__ so subclasses doesn't have to call Predicate.__init__
943 inst = type.__new__(mcs, *args, **kwargs)
944 proxy = weakref.proxy(inst, lambda p: _PREDICATES.pop(id(p)))
945 _PREDICATES[id(proxy)] = proxy
946 return inst
947
948
949 @add_metaclass(PredicateMetaClass)
950 class Predicate(object):
951 """base class for selector classes providing implementation
952 for operators ``&``, ``|`` and ``~``
953
954 This class is only here to give access to binary operators, the selector
955 logic itself should be implemented in the :meth:`__call__` method. Notice it
956 should usually accept any arbitrary arguments (the context), though that may
957 vary depending on your usage of the registry.
958
959 a selector is called to help choosing the correct object for a
960 particular context by returning a score (`int`) telling how well
961 the implementation given as first argument fit to the given context.
962
963 0 score means that the class doesn't apply.
964 """
965
966 @property
967 def func_name(self):
968 # backward compatibility
969 return self.__class__.__name__
970
971 def search_selector(self, selector):
972 """search for the given selector, selector instance or tuple of
973 selectors in the selectors tree. Return None if not found.
974 """
975 if self is selector:
976 return self
977 if (isinstance(selector, type) or isinstance(selector, tuple)) and \
978 isinstance(self, selector):
979 return self
980 return None
981
982 def __str__(self):
983 return self.__class__.__name__
984
985 def __and__(self, other):
986 return AndPredicate(self, other)
987 def __rand__(self, other):
988 return AndPredicate(other, self)
989 def __iand__(self, other):
990 return AndPredicate(self, other)
991 def __or__(self, other):
992 return OrPredicate(self, other)
993 def __ror__(self, other):
994 return OrPredicate(other, self)
995 def __ior__(self, other):
996 return OrPredicate(self, other)
997
998 def __invert__(self):
999 return NotPredicate(self)
1000
1001 # XXX (function | function) or (function & function) not managed yet
1002
1003 def __call__(self, cls, *args, **kwargs):
1004 return NotImplementedError("selector %s must implement its logic "
1005 "in its __call__ method" % self.__class__)
1006
1007 def __repr__(self):
1008 return u'<Predicate %s at %x>' % (self.__class__.__name__, id(self))
1009
1010
1011 class MultiPredicate(Predicate):
1012 """base class for compound selector classes"""
1013
1014 def __init__(self, *selectors):
1015 self.selectors = self.merge_selectors(selectors)
1016
1017 def __str__(self):
1018 return '%s(%s)' % (self.__class__.__name__,
1019 ','.join(str(s) for s in self.selectors))
1020
1021 @classmethod
1022 def merge_selectors(cls, selectors):
1023 """deal with selector instanciation when necessary and merge
1024 multi-selectors if possible:
1025
1026 AndPredicate(AndPredicate(sel1, sel2), AndPredicate(sel3, sel4))
1027 ==> AndPredicate(sel1, sel2, sel3, sel4)
1028 """
1029 merged_selectors = []
1030 for selector in selectors:
1031 # XXX do we really want magic-transformations below?
1032 # if so, wanna warn about them?
1033 if isinstance(selector, types.FunctionType):
1034 selector = objectify_predicate(selector)()
1035 if isinstance(selector, type) and issubclass(selector, Predicate):
1036 selector = selector()
1037 assert isinstance(selector, Predicate), selector
1038 if isinstance(selector, cls):
1039 merged_selectors += selector.selectors
1040 else:
1041 merged_selectors.append(selector)
1042 return merged_selectors
1043
1044 def search_selector(self, selector):
1045 """search for the given selector or selector instance (or tuple of
1046 selectors) in the selectors tree. Return None if not found
1047 """
1048 for childselector in self.selectors:
1049 if childselector is selector:
1050 return childselector
1051 found = childselector.search_selector(selector)
1052 if found is not None:
1053 return found
1054 # if not found in children, maybe we are looking for self?
1055 return super(MultiPredicate, self).search_selector(selector)
1056
1057
1058 class AndPredicate(MultiPredicate):
1059 """and-chained selectors"""
1060 def __call__(self, cls, *args, **kwargs):
1061 score = 0
1062 for selector in self.selectors:
1063 partscore = selector(cls, *args, **kwargs)
1064 if not partscore:
1065 return 0
1066 score += partscore
1067 return score
1068
1069
1070 class OrPredicate(MultiPredicate):
1071 """or-chained selectors"""
1072 def __call__(self, cls, *args, **kwargs):
1073 for selector in self.selectors:
1074 partscore = selector(cls, *args, **kwargs)
1075 if partscore:
1076 return partscore
1077 return 0
1078
1079 class NotPredicate(Predicate):
1080 """negation selector"""
1081 def __init__(self, selector):
1082 self.selector = selector
1083
1084 def __call__(self, cls, *args, **kwargs):
1085 score = self.selector(cls, *args, **kwargs)
1086 return int(not score)
1087
1088 def __str__(self):
1089 return 'NOT(%s)' % self.selector
1090
1091
1092 class yes(Predicate): # pylint: disable=C0103
1093 """Return the score given as parameter, with a default score of 0.5 so any
1094 other selector take precedence.
1095
1096 Usually used for objects which can be selected whatever the context, or
1097 also sometimes to add arbitrary points to a score.
1098
1099 Take care, `yes(0)` could be named 'no'...
1100 """
1101 def __init__(self, score=0.5):
1102 self.score = score
1103
1104 def __call__(self, *args, **kwargs):
1105 return self.score
1106
1107
1108 # deprecated stuff #############################################################
1109
1110 from logilab.common.deprecation import deprecated
1111
1112 @deprecated('[lgc 0.59] use Registry.objid class method instead')
1113 def classid(cls):
1114 return '%s.%s' % (cls.__module__, cls.__name__)
1115
1116 @deprecated('[lgc 0.59] use obj_registries function instead')
1117 def class_registries(cls, registryname):
1118 return obj_registries(cls, registryname)
1119
OLDNEW
« no previous file with comments | « third_party/logilab/logilab/common/pytest.py ('k') | third_party/logilab/logilab/common/shellutils.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698