1# ######################### LICENSE ############################ # 2 3# Copyright (c) 2005-2017, Michele Simionato 4# All rights reserved. 5 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are 8# met: 9 10# Redistributions of source code must retain the above copyright 11# notice, this list of conditions and the following disclaimer. 12# Redistributions in bytecode form must reproduce the above copyright 13# notice, this list of conditions and the following disclaimer in 14# the documentation and/or other materials provided with the 15# distribution. 16 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21# HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 22# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 24# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 26# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 27# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 28# DAMAGE. 29 30""" 31Decorator module, see http://pypi.python.org/pypi/decorator 32for the documentation. 33""" 34from __future__ import print_function 35 36import re 37import sys 38import inspect 39import operator 40import itertools 41import collections 42 43__version__ = '4.1.2' 44 45if sys.version >= '3': 46 from inspect import getfullargspec 47 48 def get_init(cls): 49 return cls.__init__ 50else: 51 FullArgSpec = collections.namedtuple( 52 'FullArgSpec', 'args varargs varkw defaults ' 53 'kwonlyargs kwonlydefaults') 54 55 def getfullargspec(f): 56 "A quick and dirty replacement for getfullargspec for Python 2.X" 57 return FullArgSpec._make(inspect.getargspec(f) + ([], None)) 58 59 def get_init(cls): 60 return cls.__init__.__func__ 61 62try: 63 iscoroutinefunction = inspect.iscoroutinefunction 64except AttributeError: 65 # let's assume there are no coroutine functions in old Python 66 def iscoroutinefunction(f): 67 return False 68 69# getargspec has been deprecated in Python 3.5 70ArgSpec = collections.namedtuple( 71 'ArgSpec', 'args varargs varkw defaults') 72 73 74def getargspec(f): 75 """A replacement for inspect.getargspec""" 76 spec = getfullargspec(f) 77 return ArgSpec(spec.args, spec.varargs, spec.varkw, spec.defaults) 78 79 80DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(') 81 82 83# basic functionality 84class FunctionMaker(object): 85 """ 86 An object with the ability to create functions with a given signature. 87 It has attributes name, doc, module, signature, defaults, dict and 88 methods update and make. 89 """ 90 91 # Atomic get-and-increment provided by the GIL 92 _compile_count = itertools.count() 93 94 # make pylint happy 95 args = varargs = varkw = defaults = kwonlyargs = kwonlydefaults = () 96 97 def __init__(self, func=None, name=None, signature=None, 98 defaults=None, doc=None, module=None, funcdict=None): 99 self.shortsignature = signature 100 if func: 101 # func can be a class or a callable, but not an instance method 102 self.name = func.__name__ 103 if self.name == '<lambda>': # small hack for lambda functions 104 self.name = '_lambda_' 105 self.doc = func.__doc__ 106 self.module = func.__module__ 107 if inspect.isfunction(func): 108 argspec = getfullargspec(func) 109 self.annotations = getattr(func, '__annotations__', {}) 110 for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', 111 'kwonlydefaults'): 112 setattr(self, a, getattr(argspec, a)) 113 for i, arg in enumerate(self.args): 114 setattr(self, 'arg%d' % i, arg) 115 if sys.version < '3': # easy way 116 self.shortsignature = self.signature = ( 117 inspect.formatargspec( 118 formatvalue=lambda val: "", *argspec[:-2])[1:-1]) 119 else: # Python 3 way 120 allargs = list(self.args) 121 allshortargs = list(self.args) 122 if self.varargs: 123 allargs.append('*' + self.varargs) 124 allshortargs.append('*' + self.varargs) 125 elif self.kwonlyargs: 126 allargs.append('*') # single star syntax 127 for a in self.kwonlyargs: 128 allargs.append('%s=None' % a) 129 allshortargs.append('%s=%s' % (a, a)) 130 if self.varkw: 131 allargs.append('**' + self.varkw) 132 allshortargs.append('**' + self.varkw) 133 self.signature = ', '.join(allargs) 134 self.shortsignature = ', '.join(allshortargs) 135 self.dict = func.__dict__.copy() 136 # func=None happens when decorating a caller 137 if name: 138 self.name = name 139 if signature is not None: 140 self.signature = signature 141 if defaults: 142 self.defaults = defaults 143 if doc: 144 self.doc = doc 145 if module: 146 self.module = module 147 if funcdict: 148 self.dict = funcdict 149 # check existence required attributes 150 assert hasattr(self, 'name') 151 if not hasattr(self, 'signature'): 152 raise TypeError('You are decorating a non function: %s' % func) 153 154 def update(self, func, **kw): 155 "Update the signature of func with the data in self" 156 func.__name__ = self.name 157 func.__doc__ = getattr(self, 'doc', None) 158 func.__dict__ = getattr(self, 'dict', {}) 159 func.__defaults__ = self.defaults 160 func.__kwdefaults__ = self.kwonlydefaults or None 161 func.__annotations__ = getattr(self, 'annotations', None) 162 try: 163 frame = sys._getframe(3) 164 except AttributeError: # for IronPython and similar implementations 165 callermodule = '?' 166 else: 167 callermodule = frame.f_globals.get('__name__', '?') 168 func.__module__ = getattr(self, 'module', callermodule) 169 func.__dict__.update(kw) 170 171 def make(self, src_templ, evaldict=None, addsource=False, **attrs): 172 "Make a new function from a given template and update the signature" 173 src = src_templ % vars(self) # expand name and signature 174 evaldict = evaldict or {} 175 mo = DEF.search(src) 176 if mo is None: 177 raise SyntaxError('not a valid function template\n%s' % src) 178 name = mo.group(1) # extract the function name 179 names = set([name] + [arg.strip(' *') for arg in 180 self.shortsignature.split(',')]) 181 for n in names: 182 if n in ('_func_', '_call_'): 183 raise NameError('%s is overridden in\n%s' % (n, src)) 184 185 if not src.endswith('\n'): # add a newline for old Pythons 186 src += '\n' 187 188 # Ensure each generated function has a unique filename for profilers 189 # (such as cProfile) that depend on the tuple of (<filename>, 190 # <definition line>, <function name>) being unique. 191 filename = '<decorator-gen-%d>' % (next(self._compile_count),) 192 try: 193 code = compile(src, filename, 'single') 194 exec(code, evaldict) 195 except: 196 print('Error in generated code:', file=sys.stderr) 197 print(src, file=sys.stderr) 198 raise 199 func = evaldict[name] 200 if addsource: 201 attrs['__source__'] = src 202 self.update(func, **attrs) 203 return func 204 205 @classmethod 206 def create(cls, obj, body, evaldict, defaults=None, 207 doc=None, module=None, addsource=True, **attrs): 208 """ 209 Create a function from the strings name, signature and body. 210 evaldict is the evaluation dictionary. If addsource is true an 211 attribute __source__ is added to the result. The attributes attrs 212 are added, if any. 213 """ 214 if isinstance(obj, str): # "name(signature)" 215 name, rest = obj.strip().split('(', 1) 216 signature = rest[:-1] # strip a right parens 217 func = None 218 else: # a function 219 name = None 220 signature = None 221 func = obj 222 self = cls(func, name, signature, defaults, doc, module) 223 ibody = '\n'.join(' ' + line for line in body.splitlines()) 224 caller = evaldict.get('_call_') # when called from `decorate` 225 if caller and iscoroutinefunction(caller): 226 body = ('async def %(name)s(%(signature)s):\n' + ibody).replace( 227 'return', 'return await') 228 else: 229 body = 'def %(name)s(%(signature)s):\n' + ibody 230 return self.make(body, evaldict, addsource, **attrs) 231 232 233def decorate(func, caller): 234 """ 235 decorate(func, caller) decorates a function using a caller. 236 """ 237 evaldict = dict(_call_=caller, _func_=func) 238 fun = FunctionMaker.create( 239 func, "return _call_(_func_, %(shortsignature)s)", 240 evaldict, __wrapped__=func) 241 if hasattr(func, '__qualname__'): 242 fun.__qualname__ = func.__qualname__ 243 return fun 244 245 246def decorator(caller, _func=None): 247 """decorator(caller) converts a caller function into a decorator""" 248 if _func is not None: # return a decorated function 249 # this is obsolete behavior; you should use decorate instead 250 return decorate(_func, caller) 251 # else return a decorator function 252 if inspect.isclass(caller): 253 name = caller.__name__.lower() 254 doc = 'decorator(%s) converts functions/generators into ' \ 255 'factories of %s objects' % (caller.__name__, caller.__name__) 256 elif inspect.isfunction(caller): 257 if caller.__name__ == '<lambda>': 258 name = '_lambda_' 259 else: 260 name = caller.__name__ 261 doc = caller.__doc__ 262 else: # assume caller is an object with a __call__ method 263 name = caller.__class__.__name__.lower() 264 doc = caller.__call__.__doc__ 265 evaldict = dict(_call=caller, _decorate_=decorate) 266 return FunctionMaker.create( 267 '%s(func)' % name, 'return _decorate_(func, _call)', 268 evaldict, doc=doc, module=caller.__module__, 269 __wrapped__=caller) 270 271 272# ####################### contextmanager ####################### # 273 274try: # Python >= 3.2 275 from contextlib import _GeneratorContextManager 276except ImportError: # Python >= 2.5 277 from contextlib import GeneratorContextManager as _GeneratorContextManager 278 279 280class ContextManager(_GeneratorContextManager): 281 def __call__(self, func): 282 """Context manager decorator""" 283 return FunctionMaker.create( 284 func, "with _self_: return _func_(%(shortsignature)s)", 285 dict(_self_=self, _func_=func), __wrapped__=func) 286 287 288init = getfullargspec(_GeneratorContextManager.__init__) 289n_args = len(init.args) 290if n_args == 2 and not init.varargs: # (self, genobj) Python 2.7 291 def __init__(self, g, *a, **k): 292 return _GeneratorContextManager.__init__(self, g(*a, **k)) 293 ContextManager.__init__ = __init__ 294elif n_args == 2 and init.varargs: # (self, gen, *a, **k) Python 3.4 295 pass 296elif n_args == 4: # (self, gen, args, kwds) Python 3.5 297 def __init__(self, g, *a, **k): 298 return _GeneratorContextManager.__init__(self, g, a, k) 299 ContextManager.__init__ = __init__ 300 301contextmanager = decorator(ContextManager) 302 303 304# ############################ dispatch_on ############################ # 305 306def append(a, vancestors): 307 """ 308 Append ``a`` to the list of the virtual ancestors, unless it is already 309 included. 310 """ 311 add = True 312 for j, va in enumerate(vancestors): 313 if issubclass(va, a): 314 add = False 315 break 316 if issubclass(a, va): 317 vancestors[j] = a 318 add = False 319 if add: 320 vancestors.append(a) 321 322 323# inspired from simplegeneric by P.J. Eby and functools.singledispatch 324def dispatch_on(*dispatch_args): 325 """ 326 Factory of decorators turning a function into a generic function 327 dispatching on the given arguments. 328 """ 329 assert dispatch_args, 'No dispatch args passed' 330 dispatch_str = '(%s,)' % ', '.join(dispatch_args) 331 332 def check(arguments, wrong=operator.ne, msg=''): 333 """Make sure one passes the expected number of arguments""" 334 if wrong(len(arguments), len(dispatch_args)): 335 raise TypeError('Expected %d arguments, got %d%s' % 336 (len(dispatch_args), len(arguments), msg)) 337 338 def gen_func_dec(func): 339 """Decorator turning a function into a generic function""" 340 341 # first check the dispatch arguments 342 argset = set(getfullargspec(func).args) 343 if not set(dispatch_args) <= argset: 344 raise NameError('Unknown dispatch arguments %s' % dispatch_str) 345 346 typemap = {} 347 348 def vancestors(*types): 349 """ 350 Get a list of sets of virtual ancestors for the given types 351 """ 352 check(types) 353 ras = [[] for _ in range(len(dispatch_args))] 354 for types_ in typemap: 355 for t, type_, ra in zip(types, types_, ras): 356 if issubclass(t, type_) and type_ not in t.mro(): 357 append(type_, ra) 358 return [set(ra) for ra in ras] 359 360 def ancestors(*types): 361 """ 362 Get a list of virtual MROs, one for each type 363 """ 364 check(types) 365 lists = [] 366 for t, vas in zip(types, vancestors(*types)): 367 n_vas = len(vas) 368 if n_vas > 1: 369 raise RuntimeError( 370 'Ambiguous dispatch for %s: %s' % (t, vas)) 371 elif n_vas == 1: 372 va, = vas 373 mro = type('t', (t, va), {}).mro()[1:] 374 else: 375 mro = t.mro() 376 lists.append(mro[:-1]) # discard t and object 377 return lists 378 379 def register(*types): 380 """ 381 Decorator to register an implementation for the given types 382 """ 383 check(types) 384 385 def dec(f): 386 check(getfullargspec(f).args, operator.lt, ' in ' + f.__name__) 387 typemap[types] = f 388 return f 389 return dec 390 391 def dispatch_info(*types): 392 """ 393 An utility to introspect the dispatch algorithm 394 """ 395 check(types) 396 lst = [] 397 for anc in itertools.product(*ancestors(*types)): 398 lst.append(tuple(a.__name__ for a in anc)) 399 return lst 400 401 def _dispatch(dispatch_args, *args, **kw): 402 types = tuple(type(arg) for arg in dispatch_args) 403 try: # fast path 404 f = typemap[types] 405 except KeyError: 406 pass 407 else: 408 return f(*args, **kw) 409 combinations = itertools.product(*ancestors(*types)) 410 next(combinations) # the first one has been already tried 411 for types_ in combinations: 412 f = typemap.get(types_) 413 if f is not None: 414 return f(*args, **kw) 415 416 # else call the default implementation 417 return func(*args, **kw) 418 419 return FunctionMaker.create( 420 func, 'return _f_(%s, %%(shortsignature)s)' % dispatch_str, 421 dict(_f_=_dispatch), register=register, default=func, 422 typemap=typemap, vancestors=vancestors, ancestors=ancestors, 423 dispatch_info=dispatch_info, __wrapped__=func) 424 425 gen_func_dec.__name__ = 'dispatch_on' + dispatch_str 426 return gen_func_dec 427