Package common :: Module decorators
[frames] | no frames]

Source Code for Module common.decorators

  1  # copyright 2003-2011 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  __docformat__ = "restructuredtext en" 
 20   
 21  import sys 
 22  from time import clock, time 
 23   
 24  from logilab.common.compat import callable, method_type 
 25  import collections 
26 27 # XXX rewrite so we can use the decorator syntax when keyarg has to be specified 28 29 -def _is_generator_function(callableobj):
30 return callableobj.__code__.co_flags & 0x20
31
32 -class cached_decorator(object):
33 - def __init__(self, cacheattr=None, keyarg=None):
34 self.cacheattr = cacheattr 35 self.keyarg = keyarg
36 - def __call__(self, callableobj=None):
37 assert not _is_generator_function(callableobj), \ 38 'cannot cache generator function: %s' % callableobj 39 if callableobj.__code__.co_argcount == 1 or self.keyarg == 0: 40 cache = _SingleValueCache(callableobj, self.cacheattr) 41 elif self.keyarg: 42 cache = _MultiValuesKeyArgCache(callableobj, self.keyarg, self.cacheattr) 43 else: 44 cache = _MultiValuesCache(callableobj, self.cacheattr) 45 return cache.closure()
46
47 -class _SingleValueCache(object):
48 - def __init__(self, callableobj, cacheattr=None):
49 self.callable = callableobj 50 if cacheattr is None: 51 self.cacheattr = '_%s_cache_' % callableobj.__name__ 52 else: 53 assert cacheattr != callableobj.__name__ 54 self.cacheattr = cacheattr
55
56 - def __call__(__me, self, *args):
57 try: 58 return self.__dict__[__me.cacheattr] 59 except KeyError: 60 value = __me.callable(self, *args) 61 setattr(self, __me.cacheattr, value) 62 return value
63
64 - def closure(self):
65 def wrapped(*args, **kwargs): 66 return self.__call__(*args, **kwargs)
67 wrapped.cache_obj = self 68 try: 69 wrapped.__doc__ = self.callable.__doc__ 70 wrapped.__name__ = self.callable.__name__ 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 254 >>> class A: 255 ... pass 256 >>> @monkeypatch(A) 257 ... def meth(self): 258 ... return 12 259 ... 260 >>> a = A() 261 >>> a.meth() 262 12 263 >>> @monkeypatch(A, 'foo') 264 ... def meth(self): 265 ... return 12 266 ... 267 >>> a.foo() 268 12 269 """ 270 def decorator(func): 271 try: 272 name = methodname or func.__name__ 273 except AttributeError: 274 raise AttributeError('%s has no __name__ attribute: ' 275 'you should provide an explicit `methodname`' 276 % func) 277 if isinstance(func, collections.Callable) and sys.version_info < (3, 0): 278 setattr(klass, name, method_type(func, None, klass)) 279 else: 280 # likely a property 281 # this is quite borderline but usage already in the wild ... 282 setattr(klass, name, func) 283 return func
284 return decorator 285