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

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