OLD | NEW |
| (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 | |
OLD | NEW |