| Index: third_party/logilab/common/decorators.py
|
| diff --git a/third_party/logilab/common/decorators.py b/third_party/logilab/common/decorators.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..43c365212540e7e9e5ec62ab02b144d4abd0de70
|
| --- /dev/null
|
| +++ b/third_party/logilab/common/decorators.py
|
| @@ -0,0 +1,283 @@
|
| +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
|
| +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
| +#
|
| +# This file is part of logilab-common.
|
| +#
|
| +# logilab-common is free software: you can redistribute it and/or modify it under
|
| +# the terms of the GNU Lesser General Public License as published by the Free
|
| +# Software Foundation, either version 2.1 of the License, or (at your option) any
|
| +# later version.
|
| +#
|
| +# logilab-common is distributed in the hope that it will be useful, but WITHOUT
|
| +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
| +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
| +# details.
|
| +#
|
| +# You should have received a copy of the GNU Lesser General Public License along
|
| +# with logilab-common. If not, see <http://www.gnu.org/licenses/>.
|
| +""" A few useful function/method decorators. """
|
| +__docformat__ = "restructuredtext en"
|
| +
|
| +import sys
|
| +from time import clock, time
|
| +
|
| +from logilab.common.compat import callable, method_type
|
| +
|
| +# XXX rewrite so we can use the decorator syntax when keyarg has to be specified
|
| +
|
| +def _is_generator_function(callableobj):
|
| + return callableobj.func_code.co_flags & 0x20
|
| +
|
| +class cached_decorator(object):
|
| + def __init__(self, cacheattr=None, keyarg=None):
|
| + self.cacheattr = cacheattr
|
| + self.keyarg = keyarg
|
| + def __call__(self, callableobj=None):
|
| + assert not _is_generator_function(callableobj), \
|
| + 'cannot cache generator function: %s' % callableobj
|
| + if callableobj.func_code.co_argcount == 1 or self.keyarg == 0:
|
| + cache = _SingleValueCache(callableobj, self.cacheattr)
|
| + elif self.keyarg:
|
| + cache = _MultiValuesKeyArgCache(callableobj, self.keyarg, self.cacheattr)
|
| + else:
|
| + cache = _MultiValuesCache(callableobj, self.cacheattr)
|
| + return cache.closure()
|
| +
|
| +class _SingleValueCache(object):
|
| + def __init__(self, callableobj, cacheattr=None):
|
| + self.callable = callableobj
|
| + if cacheattr is None:
|
| + self.cacheattr = '_%s_cache_' % callableobj.__name__
|
| + else:
|
| + assert cacheattr != callableobj.__name__
|
| + self.cacheattr = cacheattr
|
| +
|
| + def __call__(__me, self, *args):
|
| + try:
|
| + return self.__dict__[__me.cacheattr]
|
| + except KeyError:
|
| + value = __me.callable(self, *args)
|
| + setattr(self, __me.cacheattr, value)
|
| + return value
|
| +
|
| + def closure(self):
|
| + def wrapped(*args, **kwargs):
|
| + return self.__call__(*args, **kwargs)
|
| + wrapped.cache_obj = self
|
| + try:
|
| + wrapped.__doc__ = self.callable.__doc__
|
| + wrapped.__name__ = self.callable.__name__
|
| + wrapped.func_name = self.callable.func_name
|
| + except:
|
| + pass
|
| + return wrapped
|
| +
|
| + def clear(self, holder):
|
| + holder.__dict__.pop(self.cacheattr, None)
|
| +
|
| +
|
| +class _MultiValuesCache(_SingleValueCache):
|
| + def _get_cache(self, holder):
|
| + try:
|
| + _cache = holder.__dict__[self.cacheattr]
|
| + except KeyError:
|
| + _cache = {}
|
| + setattr(holder, self.cacheattr, _cache)
|
| + return _cache
|
| +
|
| + def __call__(__me, self, *args, **kwargs):
|
| + _cache = __me._get_cache(self)
|
| + try:
|
| + return _cache[args]
|
| + except KeyError:
|
| + _cache[args] = __me.callable(self, *args)
|
| + return _cache[args]
|
| +
|
| +class _MultiValuesKeyArgCache(_MultiValuesCache):
|
| + def __init__(self, callableobj, keyarg, cacheattr=None):
|
| + super(_MultiValuesKeyArgCache, self).__init__(callableobj, cacheattr)
|
| + self.keyarg = keyarg
|
| +
|
| + def __call__(__me, self, *args, **kwargs):
|
| + _cache = __me._get_cache(self)
|
| + key = args[__me.keyarg-1]
|
| + try:
|
| + return _cache[key]
|
| + except KeyError:
|
| + _cache[key] = __me.callable(self, *args, **kwargs)
|
| + return _cache[key]
|
| +
|
| +
|
| +def cached(callableobj=None, keyarg=None, **kwargs):
|
| + """Simple decorator to cache result of method call."""
|
| + kwargs['keyarg'] = keyarg
|
| + decorator = cached_decorator(**kwargs)
|
| + if callableobj is None:
|
| + return decorator
|
| + else:
|
| + return decorator(callableobj)
|
| +
|
| +
|
| +class cachedproperty(object):
|
| + """ Provides a cached property equivalent to the stacking of
|
| + @cached and @property, but more efficient.
|
| +
|
| + After first usage, the <property_name> becomes part of the object's
|
| + __dict__. Doing:
|
| +
|
| + del obj.<property_name> empties the cache.
|
| +
|
| + Idea taken from the pyramid_ framework and the mercurial_ project.
|
| +
|
| + .. _pyramid: http://pypi.python.org/pypi/pyramid
|
| + .. _mercurial: http://pypi.python.org/pypi/Mercurial
|
| + """
|
| + __slots__ = ('wrapped',)
|
| +
|
| + def __init__(self, wrapped):
|
| + try:
|
| + wrapped.__name__
|
| + except AttributeError:
|
| + raise TypeError('%s must have a __name__ attribute' %
|
| + wrapped)
|
| + self.wrapped = wrapped
|
| +
|
| + @property
|
| + def __doc__(self):
|
| + doc = getattr(self.wrapped, '__doc__', None)
|
| + return ('<wrapped by the cachedproperty decorator>%s'
|
| + % ('\n%s' % doc if doc else ''))
|
| +
|
| + def __get__(self, inst, objtype=None):
|
| + if inst is None:
|
| + return self
|
| + val = self.wrapped(inst)
|
| + setattr(inst, self.wrapped.__name__, val)
|
| + return val
|
| +
|
| +
|
| +def get_cache_impl(obj, funcname):
|
| + cls = obj.__class__
|
| + member = getattr(cls, funcname)
|
| + if isinstance(member, property):
|
| + member = member.fget
|
| + return member.cache_obj
|
| +
|
| +def clear_cache(obj, funcname):
|
| + """Clear a cache handled by the :func:`cached` decorator. If 'x' class has
|
| + @cached on its method `foo`, type
|
| +
|
| + >>> clear_cache(x, 'foo')
|
| +
|
| + to purge this method's cache on the instance.
|
| + """
|
| + get_cache_impl(obj, funcname).clear(obj)
|
| +
|
| +def copy_cache(obj, funcname, cacheobj):
|
| + """Copy cache for <funcname> from cacheobj to obj."""
|
| + cacheattr = get_cache_impl(obj, funcname).cacheattr
|
| + try:
|
| + setattr(obj, cacheattr, cacheobj.__dict__[cacheattr])
|
| + except KeyError:
|
| + pass
|
| +
|
| +
|
| +class wproperty(object):
|
| + """Simple descriptor expecting to take a modifier function as first argument
|
| + and looking for a _<function name> to retrieve the attribute.
|
| + """
|
| + def __init__(self, setfunc):
|
| + self.setfunc = setfunc
|
| + self.attrname = '_%s' % setfunc.__name__
|
| +
|
| + def __set__(self, obj, value):
|
| + self.setfunc(obj, value)
|
| +
|
| + def __get__(self, obj, cls):
|
| + assert obj is not None
|
| + return getattr(obj, self.attrname)
|
| +
|
| +
|
| +class classproperty(object):
|
| + """this is a simple property-like class but for class attributes.
|
| + """
|
| + def __init__(self, get):
|
| + self.get = get
|
| + def __get__(self, inst, cls):
|
| + return self.get(cls)
|
| +
|
| +
|
| +class iclassmethod(object):
|
| + '''Descriptor for method which should be available as class method if called
|
| + on the class or instance method if called on an instance.
|
| + '''
|
| + def __init__(self, func):
|
| + self.func = func
|
| + def __get__(self, instance, objtype):
|
| + if instance is None:
|
| + return method_type(self.func, objtype, objtype.__class__)
|
| + return method_type(self.func, instance, objtype)
|
| + def __set__(self, instance, value):
|
| + raise AttributeError("can't set attribute")
|
| +
|
| +
|
| +def timed(f):
|
| + def wrap(*args, **kwargs):
|
| + t = time()
|
| + c = clock()
|
| + res = f(*args, **kwargs)
|
| + print '%s clock: %.9f / time: %.9f' % (f.__name__,
|
| + clock() - c, time() - t)
|
| + return res
|
| + return wrap
|
| +
|
| +
|
| +def locked(acquire, release):
|
| + """Decorator taking two methods to acquire/release a lock as argument,
|
| + returning a decorator function which will call the inner method after
|
| + having called acquire(self) et will call release(self) afterwards.
|
| + """
|
| + def decorator(f):
|
| + def wrapper(self, *args, **kwargs):
|
| + acquire(self)
|
| + try:
|
| + return f(self, *args, **kwargs)
|
| + finally:
|
| + release(self)
|
| + return wrapper
|
| + return decorator
|
| +
|
| +
|
| +def monkeypatch(klass, methodname=None):
|
| + """Decorator extending class with the decorated callable
|
| + >>> class A:
|
| + ... pass
|
| + >>> @monkeypatch(A)
|
| + ... def meth(self):
|
| + ... return 12
|
| + ...
|
| + >>> a = A()
|
| + >>> a.meth()
|
| + 12
|
| + >>> @monkeypatch(A, 'foo')
|
| + ... def meth(self):
|
| + ... return 12
|
| + ...
|
| + >>> a.foo()
|
| + 12
|
| + """
|
| + def decorator(func):
|
| + try:
|
| + name = methodname or func.__name__
|
| + except AttributeError:
|
| + raise AttributeError('%s has no __name__ attribute: '
|
| + 'you should provide an explicit `methodname`'
|
| + % func)
|
| + if callable(func) and sys.version_info < (3, 0):
|
| + setattr(klass, name, method_type(func, None, klass))
|
| + else:
|
| + # likely a property
|
| + # this is quite borderline but usage already in the wild ...
|
| + setattr(klass, name, func)
|
| + return func
|
| + return decorator
|
|
|