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