|
| 1 | +########################## LICENCE ############################### |
| 2 | + |
| 3 | +# Copyright (c) 2005-2012, 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 | +""" |
| 31 | +Decorator module, see http://pypi.python.org/pypi/decorator |
| 32 | +for the documentation. |
| 33 | +""" |
| 34 | + |
| 35 | +__version__ = '3.4.0' |
| 36 | + |
| 37 | +__all__ = ["decorator", "FunctionMaker", "contextmanager"] |
| 38 | + |
| 39 | +import sys, re, inspect |
| 40 | +if sys.version >= '3': |
| 41 | + from inspect import getfullargspec |
| 42 | + def get_init(cls): |
| 43 | + return cls.__init__ |
| 44 | +else: |
| 45 | + class getfullargspec(object): |
| 46 | + "A quick and dirty replacement for getfullargspec for Python 2.X" |
| 47 | + def __init__(self, f): |
| 48 | + self.args, self.varargs, self.varkw, self.defaults = \ |
| 49 | + inspect.getargspec(f) |
| 50 | + self.kwonlyargs = [] |
| 51 | + self.kwonlydefaults = None |
| 52 | + def __iter__(self): |
| 53 | + yield self.args |
| 54 | + yield self.varargs |
| 55 | + yield self.varkw |
| 56 | + yield self.defaults |
| 57 | + def get_init(cls): |
| 58 | + return cls.__init__.im_func |
| 59 | + |
| 60 | +DEF = re.compile('\s*def\s*([_\w][_\w\d]*)\s*\(') |
| 61 | + |
| 62 | +# basic functionality |
| 63 | +class FunctionMaker(object): |
| 64 | + """ |
| 65 | + An object with the ability to create functions with a given signature. |
| 66 | + It has attributes name, doc, module, signature, defaults, dict and |
| 67 | + methods update and make. |
| 68 | + """ |
| 69 | + def __init__(self, func=None, name=None, signature=None, |
| 70 | + defaults=None, doc=None, module=None, funcdict=None): |
| 71 | + self.shortsignature = signature |
| 72 | + if func: |
| 73 | + # func can be a class or a callable, but not an instance method |
| 74 | + self.name = func.__name__ |
| 75 | + if self.name == '<lambda>': # small hack for lambda functions |
| 76 | + self.name = '_lambda_' |
| 77 | + self.doc = func.__doc__ |
| 78 | + self.module = func.__module__ |
| 79 | + if inspect.isfunction(func): |
| 80 | + argspec = getfullargspec(func) |
| 81 | + self.annotations = getattr(func, '__annotations__', {}) |
| 82 | + for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', |
| 83 | + 'kwonlydefaults'): |
| 84 | + setattr(self, a, getattr(argspec, a)) |
| 85 | + for i, arg in enumerate(self.args): |
| 86 | + setattr(self, 'arg%d' % i, arg) |
| 87 | + if sys.version < '3': # easy way |
| 88 | + self.shortsignature = self.signature = \ |
| 89 | + inspect.formatargspec( |
| 90 | + formatvalue=lambda val: "", *argspec)[1:-1] |
| 91 | + else: # Python 3 way |
| 92 | + allargs = list(self.args) |
| 93 | + allshortargs = list(self.args) |
| 94 | + if self.varargs: |
| 95 | + allargs.append('*' + self.varargs) |
| 96 | + allshortargs.append('*' + self.varargs) |
| 97 | + elif self.kwonlyargs: |
| 98 | + allargs.append('*') # single star syntax |
| 99 | + for a in self.kwonlyargs: |
| 100 | + allargs.append('%s=None' % a) |
| 101 | + allshortargs.append('%s=%s' % (a, a)) |
| 102 | + if self.varkw: |
| 103 | + allargs.append('**' + self.varkw) |
| 104 | + allshortargs.append('**' + self.varkw) |
| 105 | + self.signature = ', '.join(allargs) |
| 106 | + self.shortsignature = ', '.join(allshortargs) |
| 107 | + self.dict = func.__dict__.copy() |
| 108 | + # func=None happens when decorating a caller |
| 109 | + if name: |
| 110 | + self.name = name |
| 111 | + if signature is not None: |
| 112 | + self.signature = signature |
| 113 | + if defaults: |
| 114 | + self.defaults = defaults |
| 115 | + if doc: |
| 116 | + self.doc = doc |
| 117 | + if module: |
| 118 | + self.module = module |
| 119 | + if funcdict: |
| 120 | + self.dict = funcdict |
| 121 | + # check existence required attributes |
| 122 | + assert hasattr(self, 'name') |
| 123 | + if not hasattr(self, 'signature'): |
| 124 | + raise TypeError('You are decorating a non function: %s' % func) |
| 125 | + |
| 126 | + def update(self, func, **kw): |
| 127 | + "Update the signature of func with the data in self" |
| 128 | + func.__name__ = self.name |
| 129 | + func.__doc__ = getattr(self, 'doc', None) |
| 130 | + func.__dict__ = getattr(self, 'dict', {}) |
| 131 | + func.func_defaults = getattr(self, 'defaults', ()) |
| 132 | + func.__kwdefaults__ = getattr(self, 'kwonlydefaults', None) |
| 133 | + func.__annotations__ = getattr(self, 'annotations', None) |
| 134 | + callermodule = sys._getframe(3).f_globals.get('__name__', '?') |
| 135 | + func.__module__ = getattr(self, 'module', callermodule) |
| 136 | + func.__dict__.update(kw) |
| 137 | + |
| 138 | + def make(self, src_templ, evaldict=None, addsource=False, **attrs): |
| 139 | + "Make a new function from a given template and update the signature" |
| 140 | + src = src_templ % vars(self) # expand name and signature |
| 141 | + evaldict = evaldict or {} |
| 142 | + mo = DEF.match(src) |
| 143 | + if mo is None: |
| 144 | + raise SyntaxError('not a valid function template\n%s' % src) |
| 145 | + name = mo.group(1) # extract the function name |
| 146 | + names = set([name] + [arg.strip(' *') for arg in |
| 147 | + self.shortsignature.split(',')]) |
| 148 | + for n in names: |
| 149 | + if n in ('_func_', '_call_'): |
| 150 | + raise NameError('%s is overridden in\n%s' % (n, src)) |
| 151 | + if not src.endswith('\n'): # add a newline just for safety |
| 152 | + src += '\n' # this is needed in old versions of Python |
| 153 | + try: |
| 154 | + code = compile(src, '<string>', 'single') |
| 155 | + # print >> sys.stderr, 'Compiling %s' % src |
| 156 | + exec code in evaldict |
| 157 | + except: |
| 158 | + print >> sys.stderr, 'Error in generated code:' |
| 159 | + print >> sys.stderr, src |
| 160 | + raise |
| 161 | + func = evaldict[name] |
| 162 | + if addsource: |
| 163 | + attrs['__source__'] = src |
| 164 | + self.update(func, **attrs) |
| 165 | + return func |
| 166 | + |
| 167 | + @classmethod |
| 168 | + def create(cls, obj, body, evaldict, defaults=None, |
| 169 | + doc=None, module=None, addsource=True, **attrs): |
| 170 | + """ |
| 171 | + Create a function from the strings name, signature and body. |
| 172 | + evaldict is the evaluation dictionary. If addsource is true an attribute |
| 173 | + __source__ is added to the result. The attributes attrs are added, |
| 174 | + if any. |
| 175 | + """ |
| 176 | + if isinstance(obj, str): # "name(signature)" |
| 177 | + name, rest = obj.strip().split('(', 1) |
| 178 | + signature = rest[:-1] #strip a right parens |
| 179 | + func = None |
| 180 | + else: # a function |
| 181 | + name = None |
| 182 | + signature = None |
| 183 | + func = obj |
| 184 | + self = cls(func, name, signature, defaults, doc, module) |
| 185 | + ibody = '\n'.join(' ' + line for line in body.splitlines()) |
| 186 | + return self.make('def %(name)s(%(signature)s):\n' + ibody, |
| 187 | + evaldict, addsource, **attrs) |
| 188 | + |
| 189 | +def decorator(caller, func=None): |
| 190 | + """ |
| 191 | + decorator(caller) converts a caller function into a decorator; |
| 192 | + decorator(caller, func) decorates a function using a caller. |
| 193 | + """ |
| 194 | + if func is not None: # returns a decorated function |
| 195 | + evaldict = func.func_globals.copy() |
| 196 | + evaldict['_call_'] = caller |
| 197 | + evaldict['_func_'] = func |
| 198 | + return FunctionMaker.create( |
| 199 | + func, "return _call_(_func_, %(shortsignature)s)", |
| 200 | + evaldict, undecorated=func, __wrapped__=func) |
| 201 | + else: # returns a decorator |
| 202 | + if inspect.isclass(caller): |
| 203 | + name = caller.__name__.lower() |
| 204 | + callerfunc = get_init(caller) |
| 205 | + doc = 'decorator(%s) converts functions/generators into ' \ |
| 206 | + 'factories of %s objects' % (caller.__name__, caller.__name__) |
| 207 | + fun = getfullargspec(callerfunc).args[1] # second arg |
| 208 | + elif inspect.isfunction(caller): |
| 209 | + name = '_lambda_' if caller.__name__ == '<lambda>' \ |
| 210 | + else caller.__name__ |
| 211 | + callerfunc = caller |
| 212 | + doc = caller.__doc__ |
| 213 | + fun = getfullargspec(callerfunc).args[0] # first arg |
| 214 | + else: # assume caller is an object with a __call__ method |
| 215 | + name = caller.__class__.__name__.lower() |
| 216 | + callerfunc = caller.__call__.im_func |
| 217 | + doc = caller.__call__.__doc__ |
| 218 | + fun = getfullargspec(callerfunc).args[1] # second arg |
| 219 | + evaldict = callerfunc.func_globals.copy() |
| 220 | + evaldict['_call_'] = caller |
| 221 | + evaldict['decorator'] = decorator |
| 222 | + return FunctionMaker.create( |
| 223 | + '%s(%s)' % (name, fun), |
| 224 | + 'return decorator(_call_, %s)' % fun, |
| 225 | + evaldict, undecorated=caller, __wrapped__=caller, |
| 226 | + doc=doc, module=caller.__module__) |
| 227 | + |
| 228 | +######################### contextmanager ######################## |
| 229 | + |
| 230 | +def __call__(self, func): |
| 231 | + 'Context manager decorator' |
| 232 | + return FunctionMaker.create( |
| 233 | + func, "with _self_: return _func_(%(shortsignature)s)", |
| 234 | + dict(_self_=self, _func_=func), __wrapped__=func) |
| 235 | + |
| 236 | +try: # Python >= 3.2 |
| 237 | + |
| 238 | + from contextlib import _GeneratorContextManager |
| 239 | + ContextManager = type( |
| 240 | + 'ContextManager', (_GeneratorContextManager,), dict(__call__=__call__)) |
| 241 | + |
| 242 | +except ImportError: # Python >= 2.5 |
| 243 | + |
| 244 | + from contextlib import GeneratorContextManager |
| 245 | + def __init__(self, f, *a, **k): |
| 246 | + return GeneratorContextManager.__init__(self, f(*a, **k)) |
| 247 | + ContextManager = type( |
| 248 | + 'ContextManager', (GeneratorContextManager,), |
| 249 | + dict(__call__=__call__, __init__=__init__)) |
| 250 | + |
| 251 | +contextmanager = decorator(ContextManager) |
0 commit comments