Package logilab :: Package common :: Module decorators

[frames] | no frames]

# Source Code for Module logilab.common.decorators

      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 under
      7  # the terms of the GNU Lesser General Public License as published by the Free
      8  # Software Foundation, either version 2.1 of the License, or (at your option) any
      9  # 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  """ A few useful function/method decorators. """
     19
     20  from __future__ import print_function
     21
     22  __docformat__ = "restructuredtext en"
     23
     24  import sys
     25  import types
     26  from time import clock, time
     27  from inspect import isgeneratorfunction, getargspec
     28
     29  from logilab.common.compat import method_type
     30
     31  # XXX rewrite so we can use the decorator syntax when keyarg has to be specified
     32
     33 -class cached_decorator(object):
     34 -    def __init__(self, cacheattr=None, keyarg=None):
     35          self.cacheattr = cacheattr
     36          self.keyarg = keyarg
     37 -    def __call__(self, callableobj=None):
     38          assert not isgeneratorfunction(callableobj), \
     39                 'cannot cache generator function: %s' % callableobj
     40          if len(getargspec(callableobj).args) == 1 or self.keyarg == 0:
     41              cache = _SingleValueCache(callableobj, self.cacheattr)
     42          elif self.keyarg:
     43              cache = _MultiValuesKeyArgCache(callableobj, self.keyarg, self.cacheattr)
     44          else:
     45              cache = _MultiValuesCache(callableobj, self.cacheattr)
     46          return cache.closure()
     47
     48 -class _SingleValueCache(object):
     49 -    def __init__(self, callableobj, cacheattr=None):
     50          self.callable = callableobj
     51          if cacheattr is None:
     52              self.cacheattr = '_%s_cache_' % callableobj.__name__
     53          else:
     54              assert cacheattr != callableobj.__name__
     55              self.cacheattr = cacheattr
     56
     57 -    def __call__(__me, self, *args):
     58          try:
     59              return self.__dict__[__me.cacheattr]
     60          except KeyError:
     61              value = __me.callable(self, *args)
     62              setattr(self, __me.cacheattr, value)
     63              return value
     64
     65 -    def closure(self):
     66          def wrapped(*args, **kwargs):
     67              return self.__call__(*args, **kwargs)
     68          wrapped.cache_obj = self
     69          try:
     70              wrapped.__doc__ = self.callable.__doc__
     71              wrapped.__name__ = self.callable.__name__
     72          except:
     73              pass
     74          return wrapped
     75
     76 -    def clear(self, holder):
     77          holder.__dict__.pop(self.cacheattr, None)
     78
     79
     80 -class _MultiValuesCache(_SingleValueCache):
     81 -    def _get_cache(self, holder):
     82          try:
     83              _cache = holder.__dict__[self.cacheattr]
     84          except KeyError:
     85              _cache = {}
     86              setattr(holder, self.cacheattr, _cache)
     87          return _cache
     88
     89 -    def __call__(__me, self, *args, **kwargs):
     90          _cache = __me._get_cache(self)
     91          try:
     92              return _cache[args]
     93          except KeyError:
     94              _cache[args] = __me.callable(self, *args)
     95              return _cache[args]
     96
     97 -class _MultiValuesKeyArgCache(_MultiValuesCache):
     98 -    def __init__(self, callableobj, keyarg, cacheattr=None):
     99          super(_MultiValuesKeyArgCache, self).__init__(callableobj, cacheattr)
    100          self.keyarg = keyarg
    101
    102 -    def __call__(__me, self, *args, **kwargs):
    103          _cache = __me._get_cache(self)
    104          key = args[__me.keyarg-1]
    105          try:
    106              return _cache[key]
    107          except KeyError:
    108              _cache[key] = __me.callable(self, *args, **kwargs)
    109              return _cache[key]
    110
    111
    112 -def cached(callableobj=None, keyarg=None, **kwargs):
    113      """Simple decorator to cache result of method call."""
    114      kwargs['keyarg'] = keyarg
    115      decorator = cached_decorator(**kwargs)
    116      if callableobj is None:
    117          return decorator
    118      else:
    119          return decorator(callableobj)
    120
    121
    122 -class cachedproperty(object):
    123      """ Provides a cached property equivalent to the stacking of
    124      @cached and @property, but more efficient.
    125
    126      After first usage, the <property_name> becomes part of the object's
    127      __dict__. Doing:
    128
    129        del obj.<property_name> empties the cache.
    130
    131      Idea taken from the pyramid_ framework and the mercurial_ project.
    132
    133      .. _pyramid: http://pypi.python.org/pypi/pyramid
    134      .. _mercurial: http://pypi.python.org/pypi/Mercurial
    135      """
    136      __slots__ = ('wrapped',)
    137
    138 -    def __init__(self, wrapped):
    139          try:
    140              wrapped.__name__
    141          except AttributeError:
    142              raise TypeError('%s must have a __name__ attribute' %
    143                              wrapped)
    144          self.wrapped = wrapped
    145
    146      @property
    147 -    def __doc__(self):
    148          doc = getattr(self.wrapped, '__doc__', None)
    149          return ('<wrapped by the cachedproperty decorator>%s'
    150                  % ('\n%s' % doc if doc else ''))
    151
    152 -    def __get__(self, inst, objtype=None):
    153          if inst is None:
    154              return self
    155          val = self.wrapped(inst)
    156          setattr(inst, self.wrapped.__name__, val)
    157          return val
    158
    159
    160 -def get_cache_impl(obj, funcname):
    161      cls = obj.__class__
    162      member = getattr(cls, funcname)
    163      if isinstance(member, property):
    164          member = member.fget
    165      return member.cache_obj
    166
    167 -def clear_cache(obj, funcname):
    168      """Clear a cache handled by the :func:`cached` decorator. If 'x' class has
    169      @cached on its method `foo`, type
    170
    171      >>> clear_cache(x, 'foo')
    172
    173      to purge this method's cache on the instance.
    174      """
    175      get_cache_impl(obj, funcname).clear(obj)
    176
    177 -def copy_cache(obj, funcname, cacheobj):
    178      """Copy cache for <funcname> from cacheobj to obj."""
    179      cacheattr = get_cache_impl(obj, funcname).cacheattr
    180      try:
    181          setattr(obj, cacheattr, cacheobj.__dict__[cacheattr])
    182      except KeyError:
    183          pass
    184
    185
    186 -class wproperty(object):
    187      """Simple descriptor expecting to take a modifier function as first argument
    188      and looking for a _<function name> to retrieve the attribute.
    189      """
    190 -    def __init__(self, setfunc):
    191          self.setfunc = setfunc
    192          self.attrname = '_%s' % setfunc.__name__
    193
    194 -    def __set__(self, obj, value):
    195          self.setfunc(obj, value)
    196
    197 -    def __get__(self, obj, cls):
    198          assert obj is not None
    199          return getattr(obj, self.attrname)
    200
    201
    202 -class classproperty(object):
    203      """this is a simple property-like class but for class attributes.
    204      """
    205 -    def __init__(self, get):
    206          self.get = get
    207 -    def __get__(self, inst, cls):
    208          return self.get(cls)
    209
    210
    211 -class iclassmethod(object):
    212      '''Descriptor for method which should be available as class method if called
    213      on the class or instance method if called on an instance.
    214      '''
    215 -    def __init__(self, func):
    216          self.func = func
    217 -    def __get__(self, instance, objtype):
    218          if instance is None:
    219              return method_type(self.func, objtype, objtype.__class__)
    220          return method_type(self.func, instance, objtype)
    221 -    def __set__(self, instance, value):
    222          raise AttributeError("can't set attribute")
    223
    224
    225 -def timed(f):
    226      def wrap(*args, **kwargs):
    227          t = time()
    228          c = clock()
    229          res = f(*args, **kwargs)
    230          print('%s clock: %.9f / time: %.9f' % (f.__name__,
    231                                                 clock() - c, time() - t))
    232          return res
    233      return wrap
    234
    235
    236 -def locked(acquire, release):
    237      """Decorator taking two methods to acquire/release a lock as argument,
    238      returning a decorator function which will call the inner method after
    239      having called acquire(self) et will call release(self) afterwards.
    240      """
    241      def decorator(f):
    242          def wrapper(self, *args, **kwargs):
    243              acquire(self)
    244              try:
    245                  return f(self, *args, **kwargs)
    246              finally:
    247                  release(self)
    248          return wrapper
    249      return decorator
    250
    251
    252 -def monkeypatch(klass, methodname=None):
    253      """Decorator extending class with the decorated callable. This is basically
    254      a syntactic sugar vs class assignment.
    255
    256      >>> class A:
    257      ...     pass
    258      >>> @monkeypatch(A)
    259      ... def meth(self):
    260      ...     return 12
    261      ...
    262      >>> a = A()
    263      >>> a.meth()
    264      12
    265      >>> @monkeypatch(A, 'foo')
    266      ... def meth(self):
    267      ...     return 12
    268      ...
    269      >>> a.foo()
    270      12
    271      """
    272      def decorator(func):
    273          try:
    274              name = methodname or func.__name__
    275          except AttributeError:
    276              raise AttributeError('%s has no __name__ attribute: '
    277                                   'you should provide an explicit `methodname`'
    278                                   % func)
    279          setattr(klass, name, func)
    280          return func
    281      return decorator
    282