diff options
Diffstat (limited to 'src/mock/decorator.py')
-rwxr-xr-x | src/mock/decorator.py | 124 |
1 files changed, 124 insertions, 0 deletions
diff --git a/src/mock/decorator.py b/src/mock/decorator.py new file mode 100755 index 0000000..4958a52 --- /dev/null +++ b/src/mock/decorator.py @@ -0,0 +1,124 @@ +## The basic trick is to generate the source code for the decorated function +## with the right signature and to evaluate it. +## Uncomment the statement 'print >> sys.stderr, func_src' in _decorate +## to understand what is going on. + +__all__ = ["decorator", "update_wrapper", "getinfo"] + +import inspect, sys + +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) + +def update_wrapper(wrapper, wrapped, create=False): + """ + An improvement over functools.update_wrapper. By default it works the + same, but if the 'create' flag is set, generates a copy of the wrapper + with the right signature and update the copy, not the original. + Moreovoer, 'wrapped' can be a dictionary with keys 'name', 'doc', 'module', + 'dict', 'defaults'. + """ + if isinstance(wrapped, dict): + infodict = wrapped + else: # assume wrapped is a function + infodict = getinfo(wrapped) + assert not '_wrapper_' in infodict["argnames"], \ + '"_wrapper_" is a reserved argument name!' + if create: # create a brand new wrapper with the right signature + src = "lambda %(signature)s: _wrapper_(%(signature)s)" % infodict + # import sys; print >> sys.stderr, src # for debugging purposes + wrapper = eval(src, dict(_wrapper_=wrapper)) + try: + wrapper.__name__ = infodict['name'] + except: # Python version < 2.4 + pass + wrapper.__doc__ = infodict['doc'] + wrapper.__module__ = infodict['module'] + wrapper.__dict__.update(infodict['dict']) + wrapper.func_defaults = infodict['defaults'] + return wrapper + +# the real meat is here +def _decorator(caller, func): + infodict = getinfo(func) + argnames = infodict['argnames'] + assert not ('_call_' in argnames or '_func_' in argnames), \ + 'You cannot use _call_ or _func_ as argument names!' + src = "lambda %(signature)s: _call_(_func_, %(signature)s)" % infodict + dec_func = eval(src, dict(_func_=func, _call_=caller)) + return update_wrapper(dec_func, func) + +def decorator(caller, func=None): + """ + General purpose decorator factory: takes a caller function as + input and returns a decorator with the same attributes. + A caller function is any function like this:: + + def caller(func, *args, **kw): + # do something + return func(*args, **kw) + + Here is an example of usage: + + >>> @decorator + ... def chatty(f, *args, **kw): + ... print "Calling %r" % f.__name__ + ... return f(*args, **kw) + + >>> chatty.__name__ + 'chatty' + + >>> @chatty + ... def f(): pass + ... + >>> f() + Calling 'f' + + For sake of convenience, the decorator factory can also be called with + two arguments. In this casem ``decorator(caller, func)`` is just a + shortcut for ``decorator(caller)(func)``. + """ + if func is None: # return a decorator function + return update_wrapper(lambda f : _decorator(caller, f), caller) + else: # return a decorated function + return _decorator(caller, func) + +if __name__ == "__main__": + import doctest; doctest.testmod() |