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

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