diff --git a/IPython/external/decorator/_decorator.py b/IPython/external/decorator/_decorator.py index 2791c1d..2751ab1 100644 --- a/IPython/external/decorator/_decorator.py +++ b/IPython/external/decorator/_decorator.py @@ -1,37 +1,43 @@ ########################## LICENCE ############################### -## -## Copyright (c) 2005, Michele Simionato -## All rights reserved. -## -## Redistributions of source code must retain the above copyright -## notice, this list of conditions and the following disclaimer. -## Redistributions in bytecode form must reproduce the above copyright -## notice, this list of conditions and the following disclaimer in -## the documentation and/or other materials provided with the -## distribution. - -## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -## HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -## BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -## OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -## TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -## USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -## DAMAGE. + +# Copyright (c) 2005-2012, Michele Simionato +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: + +# Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# Redistributions in bytecode form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. """ Decorator module, see http://pypi.python.org/pypi/decorator for the documentation. """ -__all__ = ["decorator", "FunctionMaker", "partial", - "deprecated", "getinfo", "new_wrapper"] +__version__ = '3.3.3' + +__all__ = ["decorator", "FunctionMaker", "partial"] + +import sys, re, inspect -import os, sys, re, inspect, string, warnings try: from functools import partial except ImportError: # for Python version < 2.5 @@ -46,6 +52,22 @@ except ImportError: # for Python version < 2.5 kw.update(otherkw) return self.func(*(self.args + otherargs), **kw) +if sys.version >= '3': + from inspect import getfullargspec +else: + class getfullargspec(object): + "A quick and dirty replacement for getfullargspec for Python 2.X" + def __init__(self, f): + self.args, self.varargs, self.varkw, self.defaults = \ + inspect.getargspec(f) + self.kwonlyargs = [] + self.kwonlydefaults = None + def __iter__(self): + yield self.args + yield self.varargs + yield self.varkw + yield self.defaults + DEF = re.compile('\s*def\s*([_\w][_\w\d]*)\s*\(') # basic functionality @@ -57,6 +79,7 @@ class FunctionMaker(object): """ def __init__(self, func=None, name=None, signature=None, defaults=None, doc=None, module=None, funcdict=None): + self.shortsignature = signature if func: # func can be a class or a callable, but not an instance method self.name = func.__name__ @@ -65,13 +88,31 @@ class FunctionMaker(object): self.doc = func.__doc__ self.module = func.__module__ if inspect.isfunction(func): - argspec = inspect.getargspec(func) - self.args, self.varargs, self.keywords, self.defaults = argspec + argspec = getfullargspec(func) + self.annotations = getattr(func, '__annotations__', {}) + for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', + 'kwonlydefaults'): + setattr(self, a, getattr(argspec, a)) for i, arg in enumerate(self.args): setattr(self, 'arg%d' % i, arg) - self.signature = inspect.formatargspec( - formatvalue=lambda val: "", *argspec)[1:-1] + if sys.version < '3': # easy way + self.shortsignature = self.signature = \ + inspect.formatargspec( + formatvalue=lambda val: "", *argspec)[1:-1] + else: # Python 3 way + self.signature = self.shortsignature = ', '.join(self.args) + if self.varargs: + self.signature += ', *' + self.varargs + self.shortsignature += ', *' + self.varargs + if self.kwonlyargs: + for a in self.kwonlyargs: + self.signature += ', %s=None' % a + self.shortsignature += ', %s=%s' % (a, a) + if self.varkw: + self.signature += ', **' + self.varkw + self.shortsignature += ', **' + self.varkw self.dict = func.__dict__.copy() + # func=None happens when decorating a caller if name: self.name = name if signature is not None: @@ -95,6 +136,8 @@ class FunctionMaker(object): func.__doc__ = getattr(self, 'doc', None) func.__dict__ = getattr(self, 'dict', {}) func.func_defaults = getattr(self, 'defaults', ()) + func.__kwdefaults__ = getattr(self, 'kwonlydefaults', None) + func.__annotations__ = getattr(self, 'annotations', None) callermodule = sys._getframe(3).f_globals.get('__name__', '?') func.__module__ = getattr(self, 'module', callermodule) func.__dict__.update(kw) @@ -107,15 +150,16 @@ class FunctionMaker(object): if mo is None: raise SyntaxError('not a valid function template\n%s' % src) name = mo.group(1) # extract the function name - reserved_names = set([name] + [ - arg.strip(' *') for arg in self.signature.split(',')]) - for n, v in evaldict.iteritems(): - if n in reserved_names: + names = set([name] + [arg.strip(' *') for arg in + self.shortsignature.split(',')]) + for n in names: + if n in ('_func_', '_call_'): raise NameError('%s is overridden in\n%s' % (n, src)) if not src.endswith('\n'): # add a newline just for safety - src += '\n' + src += '\n' # this is needed in old versions of Python try: code = compile(src, '', 'single') + # print >> sys.stderr, 'Compiling %s' % src exec code in evaldict except: print >> sys.stderr, 'Error in generated code:' @@ -129,7 +173,7 @@ class FunctionMaker(object): @classmethod def create(cls, obj, body, evaldict, defaults=None, - doc=None, module=None, addsource=True,**attrs): + doc=None, module=None, addsource=True, **attrs): """ Create a function from the strings name, signature and body. evaldict is the evaluation dictionary. If addsource is true an attribute @@ -144,9 +188,9 @@ class FunctionMaker(object): name = None signature = None func = obj - fun = cls(func, name, signature, defaults, doc, module) + self = cls(func, name, signature, defaults, doc, module) ibody = '\n'.join(' ' + line for line in body.splitlines()) - return fun.make('def %(name)s(%(signature)s):\n' + ibody, + return self.make('def %(name)s(%(signature)s):\n' + ibody, evaldict, addsource, **attrs) def decorator(caller, func=None): @@ -155,100 +199,22 @@ def decorator(caller, func=None): decorator(caller, func) decorates a function using a caller. """ if func is not None: # returns a decorated function + evaldict = func.func_globals.copy() + evaldict['_call_'] = caller + evaldict['_func_'] = func return FunctionMaker.create( - func, "return _call_(_func_, %(signature)s)", - dict(_call_=caller, _func_=func), undecorated=func) + func, "return _call_(_func_, %(shortsignature)s)", + evaldict, undecorated=func, __wrapped__=func) else: # returns a decorator if isinstance(caller, partial): return partial(decorator, caller) # otherwise assume caller is a function - f = inspect.getargspec(caller)[0][0] # first arg + first = inspect.getargspec(caller)[0][0] # first arg + evaldict = caller.func_globals.copy() + evaldict['_call_'] = caller + evaldict['decorator'] = decorator return FunctionMaker.create( - '%s(%s)' % (caller.__name__, f), - 'return decorator(_call_, %s)' % f, - dict(_call_=caller, decorator=decorator), undecorated=caller, + '%s(%s)' % (caller.__name__, first), + 'return decorator(_call_, %s)' % first, + evaldict, undecorated=caller, __wrapped__=caller, doc=caller.__doc__, module=caller.__module__) - -###################### deprecated functionality ######################### - -@decorator -def deprecated(func, *args, **kw): - "A decorator for deprecated functions" - warnings.warn( - ('Calling the deprecated function %r\n' - 'Downgrade to decorator 2.3 if you want to use this functionality') - % func.__name__, DeprecationWarning, stacklevel=3) - return func(*args, **kw) - -@deprecated -def getinfo(func): - """ - Returns an info dictionary containing: - - name (the name of the function : str) - - argnames (the names of the arguments : list) - - defaults (the values of the default arguments : tuple) - - signature (the signature : str) - - doc (the docstring : str) - - module (the module name : str) - - dict (the function __dict__ : str) - - >>> def f(self, x=1, y=2, *args, **kw): pass - - >>> info = getinfo(f) - - >>> info["name"] - 'f' - >>> info["argnames"] - ['self', 'x', 'y', 'args', 'kw'] - - >>> info["defaults"] - (1, 2) - - >>> info["signature"] - 'self, x, y, *args, **kw' - """ - assert inspect.ismethod(func) or inspect.isfunction(func) - regargs, varargs, varkwargs, defaults = inspect.getargspec(func) - argnames = list(regargs) - if varargs: - argnames.append(varargs) - if varkwargs: - argnames.append(varkwargs) - signature = inspect.formatargspec(regargs, varargs, varkwargs, defaults, - formatvalue=lambda value: "")[1:-1] - return dict(name=func.__name__, argnames=argnames, signature=signature, - defaults = func.func_defaults, doc=func.__doc__, - module=func.__module__, dict=func.__dict__, - globals=func.func_globals, closure=func.func_closure) - -@deprecated -def update_wrapper(wrapper, model, infodict=None): - "A replacement for functools.update_wrapper" - infodict = infodict or getinfo(model) - wrapper.__name__ = infodict['name'] - wrapper.__doc__ = infodict['doc'] - wrapper.__module__ = infodict['module'] - wrapper.__dict__.update(infodict['dict']) - wrapper.func_defaults = infodict['defaults'] - wrapper.undecorated = model - return wrapper - -@deprecated -def new_wrapper(wrapper, model): - """ - An improvement over functools.update_wrapper. The wrapper is a generic - callable object. It works by generating a copy of the wrapper with the - right signature and by updating the copy, not the original. - Moreovoer, 'model' can be a dictionary with keys 'name', 'doc', 'module', - 'dict', 'defaults'. - """ - if isinstance(model, dict): - infodict = model - else: # assume model is a function - infodict = getinfo(model) - assert not '_wrapper_' in infodict["argnames"], ( - '"_wrapper_" is a reserved argument name!') - src = "lambda %(signature)s: _wrapper_(%(signature)s)" % infodict - funcopy = eval(src, dict(_wrapper_=wrapper)) - return update_wrapper(funcopy, model, infodict) -