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

Source Code for Module common.registry

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