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