summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael E Brown <michael_e_brown@dell.com>2007-10-16 08:35:16 -0500
committerMichael E Brown <michael_e_brown@dell.com>2007-10-16 08:35:16 -0500
commit5bf740cc3ac93a1973ce36ddae0454f05a8ddac8 (patch)
tree962a15b9e144a4018250d3b1262abe3b9eb49973
parentb71f797652092af2f84d5a45693f1c2e3c478d82 (diff)
downloadmock-5bf740cc3ac93a1973ce36ddae0454f05a8ddac8.tar.gz
mock-5bf740cc3ac93a1973ce36ddae0454f05a8ddac8.tar.xz
mock-5bf740cc3ac93a1973ce36ddae0454f05a8ddac8.zip
add new python modules
-rw-r--r--Makefile.am7
-rw-r--r--Makefile.in8
-rwxr-xr-xsrc/mock/decorator.py124
-rwxr-xr-xsrc/mock/trace_decorator.py33
4 files changed, 170 insertions, 2 deletions
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)))
+