From 5bf740cc3ac93a1973ce36ddae0454f05a8ddac8 Mon Sep 17 00:00:00 2001 From: Michael E Brown Date: Tue, 16 Oct 2007 08:35:16 -0500 Subject: add new python modules --- Makefile.am | 7 ++- Makefile.in | 8 ++- src/mock/decorator.py | 124 ++++++++++++++++++++++++++++++++++++++++++++ src/mock/trace_decorator.py | 33 ++++++++++++ 4 files changed, 170 insertions(+), 2 deletions(-) create mode 100755 src/mock/decorator.py create mode 100755 src/mock/trace_decorator.py diff --git a/Makefile.am b/Makefile.am index 5c21de5..f341d7a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -48,7 +48,12 @@ EXTRA_DIST += docs buildsys-build.spec dist_libexec_SCRIPTS = src/mock.py man_MANS = docs/mock.1 -pkgpython_PYTHON = src/mock/util.py src/mock/__init__.py src/mock/backend.py +pkgpython_PYTHON = \ + src/mock/__init__.py \ + src/mock/util.py \ + src/mock/decorator.py \ + src/mock/trace_decorator.py \ + src/mock/backend.py src/mock.py: configure.ac Makefile.am src/mock.py echo Updating $@, current pwd: $$(pwd) diff --git a/Makefile.in b/Makefile.in index 9969d5e..286587f 100644 --- a/Makefile.in +++ b/Makefile.in @@ -275,7 +275,13 @@ mocketcdir = $(sysconfdir)/mock dist_mocketc_DATA = $(wildcard etc/*.cfg) etc/logging.ini dist_libexec_SCRIPTS = src/mock.py man_MANS = docs/mock.1 -pkgpython_PYTHON = src/mock/util.py src/mock/__init__.py src/mock/backend.py +pkgpython_PYTHON = \ + src/mock/__init__.py \ + src/mock/util.py \ + src/mock/decorator.py \ + src/mock/trace_decorator.py \ + src/mock/backend.py + all: all-recursive .SUFFIXES: 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() diff --git a/src/mock/trace_decorator.py b/src/mock/trace_decorator.py new file mode 100755 index 0000000..9e9b691 --- /dev/null +++ b/src/mock/trace_decorator.py @@ -0,0 +1,33 @@ +# vim:expandtab:autoindent:tabstop=4:shiftwidth=4:filetype=python:textwidth=0: + +import types +from decorator import decorator + +import logging +log = logging.getLogger("function_tracing") + +#@decorator +@decorator +def trace(f, *args, **kw): + log.debug("ENTER: %s(%s, %s)\n" % (f.func_name, args, kw)) + try: + result = "Bad exception raised: Exception was not a derived class of 'Exception'" + try: + result = f(*args, **kw) + except Exception, e: + result = "EXCEPTION RAISED" + log.debug( "EXCEPTION: %s\n" % e, exc_info=1) + raise + finally: + log.debug( "LEAVE %s --> %s\n\n" % (f.func_name, result)) + + return result + +# helper function so we can use back-compat format but not be ugly +def decorateAllFunctions(module): + methods = [ method for method in dir(module) + if isinstance(getattr(module, method), types.FunctionType) + ] + for i in methods: + setattr(module, i, trace(getattr(module,i))) + -- cgit