Package logilab :: Package common :: Module registry
[frames] | no frames]

Source Code for Module logilab.common.registry

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