summaryrefslogtreecommitdiff
path: root/pypers/oxford/decorators.py
blob: d39365876755173fd16131a0483ee5977122becd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# decorators.py

import inspect, itertools

def getinfo(func):
    """Return an info dictionary containing:
    - name (the name of the function : str)
    - argnames (the names of the arguments : list)
    - defarg (the values of the default arguments : list)
    - fullsign (the full signature : str)
    - shortsign (the short signature : str)
    - arg0 ... argn (shortcuts for the names of the arguments)

    >> def f(self, x=1, y=2, *args, **kw): pass

    >> info = getinfo(f)

    >> info["name"]
    'f'
    >> info["argnames"]
    ['self', 'x', 'y', 'args', 'kw']
    
    >> info["defarg"]
    (1, 2)

    >> info["shortsign"]
    'self, x, y, *args, **kw'
    
    >> info["fullsign"]
    'self, x=defarg[0], y=defarg[1], *args, **kw'

    >> info["arg0"], info["arg1"], info["arg2"], info["arg3"], info["arg4"]
    ('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)
    counter = itertools.count()
    fullsign = inspect.formatargspec(
        regargs, varargs, varkwargs, defaults,
        formatvalue=lambda value: "=defarg[%i]" % counter.next())[1:-1]
    shortsign = inspect.formatargspec(
        regargs, varargs, varkwargs, defaults,
        formatvalue=lambda value: "")[1:-1]
    dic = dict(("arg%s" % n, name) for n, name in enumerate(argnames))
    dic.update(name=func.__name__, argnames=argnames, shortsign=shortsign,
        fullsign = fullsign, defarg = func.func_defaults or ())
    return dic

def _contains_reserved_names(dic): # helper
    return "_call_" in dic or "_func_" in dic

def _decorate(func, caller):
    """Takes a function and a caller and returns the function
    decorated with that caller. The decorated function is obtained
    by evaluating a lambda function with the correct signature.
    """
    infodict = getinfo(func)
    assert not _contains_reserved_names(infodict["argnames"]), \
           "You cannot use _call_ or _func_ as argument names!"
    execdict=dict(_func_=func, _call_=caller, defarg=func.func_defaults or ())
    if func.__name__ == "<lambda>":
        lambda_src = "lambda %(fullsign)s: _call_(_func_, %(shortsign)s)" \
                     % infodict
        dec_func = eval(lambda_src, execdict)
    else:
        func_src = """def %(name)s(%(fullsign)s):
        return _call_(_func_, %(shortsign)s)""" % infodict
        exec func_src in execdict 
        dec_func = execdict[func.__name__]
    dec_func.__doc__ = func.__doc__
    dec_func.__dict__ = func.__dict__
    return dec_func

class decorator(object):
    """General purpose decorator factory: takes a caller function as
input and returns a decorator. 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
    .. def f(): pass
    ..
    >> f()
    Calling 'f'
    """
    def __init__(self, caller):
        self.caller = caller
    def __call__(self, func):
        return _decorate(func, self.caller)