Package logilab :: Package common :: Module registry

[frames] | no frames]

# Source Code for Module logilab.common.registry

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