~ubuntu-branches/debian/sid/python-decorator/sid

« back to all changes in this revision

Viewing changes to decorator.py

  • Committer: Bazaar Package Importer
  • Author(s): Piotr Ożarowski
  • Date: 2010-05-25 21:55:03 UTC
  • mfrom: (1.1.5 upstream)
  • Revision ID: james.westby@ubuntu.com-20100525215503-4xq1iyhjijh0t8o6
Tags: 3.2.0-1
* New upstream release
  - changelog no longer available
* python added to Build-Depends (clean rule needs it)
* debian/watch file points to PyPI now

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
##########################     LICENCE     ###############################
2
 
##
3
 
##   Copyright (c) 2005, Michele Simionato
4
 
##   All rights reserved.
5
 
##
6
 
##   Redistributions of source code must retain the above copyright 
7
 
##   notice, this list of conditions and the following disclaimer.
8
 
##   Redistributions in bytecode form must reproduce the above copyright
9
 
##   notice, this list of conditions and the following disclaimer in
10
 
##   the documentation and/or other materials provided with the
11
 
##   distribution. 
12
 
 
13
 
##   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
14
 
##   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
15
 
##   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
16
 
##   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
17
 
##   HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
18
 
##   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19
 
##   BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
20
 
##   OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21
 
##   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
22
 
##   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
23
 
##   USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
24
 
##   DAMAGE.
25
 
 
26
 
"""
27
 
Decorator module, see http://pypi.python.org/pypi/decorator
28
 
for the documentation.
29
 
"""
30
 
 
31
 
__all__ = ["decorator", "FunctionMaker", "partial",
32
 
           "deprecated", "getinfo", "new_wrapper"]
33
 
 
34
 
import os, sys, re, inspect, string, warnings
35
 
try:
36
 
    from functools import partial
37
 
except ImportError: # for Python version < 2.5
38
 
    class partial(object):
39
 
        "A simple replacement of functools.partial"
40
 
        def __init__(self, func, *args, **kw):
41
 
            self.func = func
42
 
            self.args = args                
43
 
            self.keywords = kw
44
 
        def __call__(self, *otherargs, **otherkw):
45
 
            kw = self.keywords.copy()
46
 
            kw.update(otherkw)
47
 
            return self.func(*(self.args + otherargs), **kw)
48
 
 
49
 
DEF = re.compile('\s*def\s*([_\w][_\w\d]*)\s*\(')
50
 
 
51
 
# basic functionality
52
 
class FunctionMaker(object):
53
 
    """
54
 
    An object with the ability to create functions with a given signature.
55
 
    It has attributes name, doc, module, signature, defaults, dict and
56
 
    methods update and make.
57
 
    """
58
 
    def __init__(self, func=None, name=None, signature=None,
59
 
                 defaults=None, doc=None, module=None, funcdict=None):
60
 
        if func:
61
 
            # func can be a class or a callable, but not an instance method
62
 
            self.name = func.__name__
63
 
            if self.name == '<lambda>': # small hack for lambda functions
64
 
                self.name = '_lambda_' 
65
 
            self.doc = func.__doc__
66
 
            self.module = func.__module__
67
 
            if inspect.isfunction(func):
68
 
                argspec = inspect.getargspec(func)
69
 
                self.args, self.varargs, self.keywords, self.defaults = argspec
70
 
                for i, arg in enumerate(self.args):
71
 
                    setattr(self, 'arg%d' % i, arg)
72
 
                self.signature = inspect.formatargspec(
73
 
                    formatvalue=lambda val: "", *argspec)[1:-1]
74
 
                self.dict = func.__dict__.copy()
75
 
        if name:
76
 
            self.name = name
77
 
        if signature is not None:
78
 
            self.signature = signature
79
 
        if defaults:
80
 
            self.defaults = defaults
81
 
        if doc:
82
 
            self.doc = doc
83
 
        if module:
84
 
            self.module = module
85
 
        if funcdict:
86
 
            self.dict = funcdict
87
 
        # check existence required attributes
88
 
        assert hasattr(self, 'name')
89
 
        if not hasattr(self, 'signature'):
90
 
            raise TypeError('You are decorating a non function: %s' % func)
91
 
 
92
 
    def update(self, func, **kw):
93
 
        "Update the signature of func with the data in self"
94
 
        func.__name__ = self.name
95
 
        func.__doc__ = getattr(self, 'doc', None)
96
 
        func.__dict__ = getattr(self, 'dict', {})
97
 
        func.func_defaults = getattr(self, 'defaults', ())
98
 
        callermodule = sys._getframe(3).f_globals.get('__name__', '?')
99
 
        func.__module__ = getattr(self, 'module', callermodule)
100
 
        func.__dict__.update(kw)
101
 
 
102
 
    def make(self, src_templ, evaldict=None, addsource=False, **attrs):
103
 
        "Make a new function from a given template and update the signature"
104
 
        src = src_templ % vars(self) # expand name and signature
105
 
        evaldict = evaldict or {}
106
 
        mo = DEF.match(src)
107
 
        if mo is None:
108
 
            raise SyntaxError('not a valid function template\n%s' % src)
109
 
        name = mo.group(1) # extract the function name
110
 
        reserved_names = set([name] + [
111
 
            arg.strip(' *') for arg in self.signature.split(',')])
112
 
        for n, v in evaldict.iteritems():
113
 
            if n in reserved_names:
114
 
                raise NameError('%s is overridden in\n%s' % (n, src))
115
 
        if not src.endswith('\n'): # add a newline just for safety
116
 
            src += '\n'
117
 
        try:
118
 
            code = compile(src, '<string>', 'single')
119
 
            exec code in evaldict
120
 
        except:
121
 
            print >> sys.stderr, 'Error in generated code:'
122
 
            print >> sys.stderr, src
123
 
            raise
124
 
        func = evaldict[name]
125
 
        if addsource:
126
 
            attrs['__source__'] = src
127
 
        self.update(func, **attrs)
128
 
        return func
129
 
 
130
 
    @classmethod
131
 
    def create(cls, obj, body, evaldict, defaults=None,
132
 
               doc=None, module=None, addsource=True,**attrs):
133
 
        """
134
 
        Create a function from the strings name, signature and body.
135
 
        evaldict is the evaluation dictionary. If addsource is true an attribute
136
 
        __source__ is added to the result. The attributes attrs are added,
137
 
        if any.
138
 
        """
139
 
        if isinstance(obj, str): # "name(signature)"
140
 
            name, rest = obj.strip().split('(', 1)
141
 
            signature = rest[:-1] #strip a right parens            
142
 
            func = None
143
 
        else: # a function
144
 
            name = None
145
 
            signature = None
146
 
            func = obj
147
 
        fun = cls(func, name, signature, defaults, doc, module)
148
 
        ibody = '\n'.join('    ' + line for line in body.splitlines())
149
 
        return fun.make('def %(name)s(%(signature)s):\n' + ibody, 
150
 
                        evaldict, addsource, **attrs)
151
 
  
152
 
def decorator(caller, func=None):
153
 
    """
154
 
    decorator(caller) converts a caller function into a decorator;
155
 
    decorator(caller, func) decorates a function using a caller.
156
 
    """
157
 
    if func is not None: # returns a decorated function
158
 
        return FunctionMaker.create(
159
 
            func, "return _call_(_func_, %(signature)s)",
160
 
            dict(_call_=caller, _func_=func), undecorated=func)
161
 
    else: # returns a decorator
162
 
        if isinstance(caller, partial):
163
 
            return partial(decorator, caller)
164
 
        # otherwise assume caller is a function
165
 
        f = inspect.getargspec(caller)[0][0] # first arg
166
 
        return FunctionMaker.create(
167
 
            '%s(%s)' % (caller.__name__, f), 
168
 
            'return decorator(_call_, %s)' % f,
169
 
            dict(_call_=caller, decorator=decorator), undecorated=caller,
170
 
            doc=caller.__doc__, module=caller.__module__)
171
 
 
172
 
###################### deprecated functionality #########################
173
 
 
174
 
@decorator
175
 
def deprecated(func, *args, **kw):
176
 
    "A decorator for deprecated functions"
177
 
    warnings.warn(
178
 
        ('Calling the deprecated function %r\n'
179
 
         'Downgrade to decorator 2.3 if you want to use this functionality')
180
 
        % func.__name__, DeprecationWarning, stacklevel=3)
181
 
    return func(*args, **kw)
182
 
 
183
 
@deprecated
184
 
def getinfo(func):
185
 
    """
186
 
    Returns an info dictionary containing:
187
 
    - name (the name of the function : str)
188
 
    - argnames (the names of the arguments : list)
189
 
    - defaults (the values of the default arguments : tuple)
190
 
    - signature (the signature : str)
191
 
    - doc (the docstring : str)
192
 
    - module (the module name : str)
193
 
    - dict (the function __dict__ : str)
194
 
    
195
 
    >>> def f(self, x=1, y=2, *args, **kw): pass
196
 
 
197
 
    >>> info = getinfo(f)
198
 
 
199
 
    >>> info["name"]
200
 
    'f'
201
 
    >>> info["argnames"]
202
 
    ['self', 'x', 'y', 'args', 'kw']
203
 
    
204
 
    >>> info["defaults"]
205
 
    (1, 2)
206
 
 
207
 
    >>> info["signature"]
208
 
    'self, x, y, *args, **kw'
209
 
    """
210
 
    assert inspect.ismethod(func) or inspect.isfunction(func)
211
 
    regargs, varargs, varkwargs, defaults = inspect.getargspec(func)
212
 
    argnames = list(regargs)
213
 
    if varargs:
214
 
        argnames.append(varargs)
215
 
    if varkwargs:
216
 
        argnames.append(varkwargs)
217
 
    signature = inspect.formatargspec(regargs, varargs, varkwargs, defaults,
218
 
                                      formatvalue=lambda value: "")[1:-1]
219
 
    return dict(name=func.__name__, argnames=argnames, signature=signature,
220
 
                defaults = func.func_defaults, doc=func.__doc__,
221
 
                module=func.__module__, dict=func.__dict__,
222
 
                globals=func.func_globals, closure=func.func_closure)
223
 
 
224
 
@deprecated
225
 
def update_wrapper(wrapper, model, infodict=None):
226
 
    "A replacement for functools.update_wrapper"
227
 
    infodict = infodict or getinfo(model)
228
 
    wrapper.__name__ = infodict['name']
229
 
    wrapper.__doc__ = infodict['doc']
230
 
    wrapper.__module__ = infodict['module']
231
 
    wrapper.__dict__.update(infodict['dict'])
232
 
    wrapper.func_defaults = infodict['defaults']
233
 
    wrapper.undecorated = model
234
 
    return wrapper
235
 
 
236
 
@deprecated
237
 
def new_wrapper(wrapper, model):
238
 
    """
239
 
    An improvement over functools.update_wrapper. The wrapper is a generic
240
 
    callable object. It works by generating a copy of the wrapper with the 
241
 
    right signature and by updating the copy, not the original.
242
 
    Moreovoer, 'model' can be a dictionary with keys 'name', 'doc', 'module',
243
 
    'dict', 'defaults'.
244
 
    """
245
 
    if isinstance(model, dict):
246
 
        infodict = model
247
 
    else: # assume model is a function
248
 
        infodict = getinfo(model)
249
 
    assert not '_wrapper_' in infodict["argnames"], (
250
 
        '"_wrapper_" is a reserved argument name!')
251
 
    src = "lambda %(signature)s: _wrapper_(%(signature)s)" % infodict
252
 
    funcopy = eval(src, dict(_wrapper_=wrapper))
253
 
    return update_wrapper(funcopy, model, infodict)