##// END OF EJS Templates
Updated Michele Simionato's decorator.py module to 3.1.2....
Brian Granger -
Show More
@@ -0,0 +1,254 b''
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)
254
@@ -1,254 +1,254 b''
1 1 """Decorators for labeling test objects.
2 2
3 3 Decorators that merely return a modified version of the original function
4 4 object are straightforward. Decorators that return a new function object need
5 5 to use nose.tools.make_decorator(original_function)(decorator) in returning the
6 6 decorator, in order to preserve metadata such as function name, setup and
7 7 teardown functions and so on - see nose.tools for more information.
8 8
9 9 This module provides a set of useful decorators meant to be ready to use in
10 10 your own tests. See the bottom of the file for the ready-made ones, and if you
11 11 find yourself writing a new one that may be of generic use, add it here.
12 12
13 13 NOTE: This file contains IPython-specific decorators and imports the
14 14 numpy.testing.decorators file, which we've copied verbatim. Any of our own
15 15 code will be added at the bottom if we end up extending this.
16 16 """
17 17
18 18 # Stdlib imports
19 19 import inspect
20 20 import sys
21 21
22 22 # Third-party imports
23 23
24 24 # This is Michele Simionato's decorator module, also kept verbatim.
25 from decorator_msim import decorator, update_wrapper
25 from IPython.external.decorator import decorator, update_wrapper
26 26
27 27 # Grab the numpy-specific decorators which we keep in a file that we
28 28 # occasionally update from upstream: decorators_numpy.py is an IDENTICAL copy
29 29 # of numpy.testing.decorators.
30 30 from decorators_numpy import *
31 31
32 32 ##############################################################################
33 33 # Local code begins
34 34
35 35 # Utility functions
36 36
37 37 def apply_wrapper(wrapper,func):
38 38 """Apply a wrapper to a function for decoration.
39 39
40 40 This mixes Michele Simionato's decorator tool with nose's make_decorator,
41 41 to apply a wrapper in a decorator so that all nose attributes, as well as
42 42 function signature and other properties, survive the decoration cleanly.
43 43 This will ensure that wrapped functions can still be well introspected via
44 44 IPython, for example.
45 45 """
46 46 import nose.tools
47 47
48 48 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
49 49
50 50
51 51 def make_label_dec(label,ds=None):
52 52 """Factory function to create a decorator that applies one or more labels.
53 53
54 54 :Parameters:
55 55 label : string or sequence
56 56 One or more labels that will be applied by the decorator to the functions
57 57 it decorates. Labels are attributes of the decorated function with their
58 58 value set to True.
59 59
60 60 :Keywords:
61 61 ds : string
62 62 An optional docstring for the resulting decorator. If not given, a
63 63 default docstring is auto-generated.
64 64
65 65 :Returns:
66 66 A decorator.
67 67
68 68 :Examples:
69 69
70 70 A simple labeling decorator:
71 71 >>> slow = make_label_dec('slow')
72 72 >>> print slow.__doc__
73 73 Labels a test as 'slow'.
74 74
75 75 And one that uses multiple labels and a custom docstring:
76 76 >>> rare = make_label_dec(['slow','hard'],
77 77 ... "Mix labels 'slow' and 'hard' for rare tests.")
78 78 >>> print rare.__doc__
79 79 Mix labels 'slow' and 'hard' for rare tests.
80 80
81 81 Now, let's test using this one:
82 82 >>> @rare
83 83 ... def f(): pass
84 84 ...
85 85 >>>
86 86 >>> f.slow
87 87 True
88 88 >>> f.hard
89 89 True
90 90 """
91 91
92 92 if isinstance(label,basestring):
93 93 labels = [label]
94 94 else:
95 95 labels = label
96 96
97 97 # Validate that the given label(s) are OK for use in setattr() by doing a
98 98 # dry run on a dummy function.
99 99 tmp = lambda : None
100 100 for label in labels:
101 101 setattr(tmp,label,True)
102 102
103 103 # This is the actual decorator we'll return
104 104 def decor(f):
105 105 for label in labels:
106 106 setattr(f,label,True)
107 107 return f
108 108
109 109 # Apply the user's docstring, or autogenerate a basic one
110 110 if ds is None:
111 111 ds = "Labels a test as %r." % label
112 112 decor.__doc__ = ds
113 113
114 114 return decor
115 115
116 116
117 117 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
118 118 # preserve function metadata better and allows the skip condition to be a
119 119 # callable.
120 120 def skipif(skip_condition, msg=None):
121 121 ''' Make function raise SkipTest exception if skip_condition is true
122 122
123 123 Parameters
124 124 ----------
125 125 skip_condition : bool or callable.
126 126 Flag to determine whether to skip test. If the condition is a
127 127 callable, it is used at runtime to dynamically make the decision. This
128 128 is useful for tests that may require costly imports, to delay the cost
129 129 until the test suite is actually executed.
130 130 msg : string
131 131 Message to give on raising a SkipTest exception
132 132
133 133 Returns
134 134 -------
135 135 decorator : function
136 136 Decorator, which, when applied to a function, causes SkipTest
137 137 to be raised when the skip_condition was True, and the function
138 138 to be called normally otherwise.
139 139
140 140 Notes
141 141 -----
142 142 You will see from the code that we had to further decorate the
143 143 decorator with the nose.tools.make_decorator function in order to
144 144 transmit function name, and various other metadata.
145 145 '''
146 146
147 147 def skip_decorator(f):
148 148 # Local import to avoid a hard nose dependency and only incur the
149 149 # import time overhead at actual test-time.
150 150 import nose
151 151
152 152 # Allow for both boolean or callable skip conditions.
153 153 if callable(skip_condition):
154 154 skip_val = lambda : skip_condition()
155 155 else:
156 156 skip_val = lambda : skip_condition
157 157
158 158 def get_msg(func,msg=None):
159 159 """Skip message with information about function being skipped."""
160 160 if msg is None: out = 'Test skipped due to test condition.'
161 161 else: out = msg
162 162 return "Skipping test: %s. %s" % (func.__name__,out)
163 163
164 164 # We need to define *two* skippers because Python doesn't allow both
165 165 # return with value and yield inside the same function.
166 166 def skipper_func(*args, **kwargs):
167 167 """Skipper for normal test functions."""
168 168 if skip_val():
169 169 raise nose.SkipTest(get_msg(f,msg))
170 170 else:
171 171 return f(*args, **kwargs)
172 172
173 173 def skipper_gen(*args, **kwargs):
174 174 """Skipper for test generators."""
175 175 if skip_val():
176 176 raise nose.SkipTest(get_msg(f,msg))
177 177 else:
178 178 for x in f(*args, **kwargs):
179 179 yield x
180 180
181 181 # Choose the right skipper to use when building the actual generator.
182 182 if nose.util.isgenerator(f):
183 183 skipper = skipper_gen
184 184 else:
185 185 skipper = skipper_func
186 186
187 187 return nose.tools.make_decorator(f)(skipper)
188 188
189 189 return skip_decorator
190 190
191 191 # A version with the condition set to true, common case just to attacha message
192 192 # to a skip decorator
193 193 def skip(msg=None):
194 194 """Decorator factory - mark a test function for skipping from test suite.
195 195
196 196 :Parameters:
197 197 msg : string
198 198 Optional message to be added.
199 199
200 200 :Returns:
201 201 decorator : function
202 202 Decorator, which, when applied to a function, causes SkipTest
203 203 to be raised, with the optional message added.
204 204 """
205 205
206 206 return skipif(True,msg)
207 207
208 208
209 209 #-----------------------------------------------------------------------------
210 210 # Utility functions for decorators
211 211 def numpy_not_available():
212 212 """Can numpy be imported? Returns true if numpy does NOT import.
213 213
214 214 This is used to make a decorator to skip tests that require numpy to be
215 215 available, but delay the 'import numpy' to test execution time.
216 216 """
217 217 try:
218 218 import numpy
219 219 np_not_avail = False
220 220 except ImportError:
221 221 np_not_avail = True
222 222
223 223 return np_not_avail
224 224
225 225 #-----------------------------------------------------------------------------
226 226 # Decorators for public use
227 227
228 228 skip_doctest = make_label_dec('skip_doctest',
229 229 """Decorator - mark a function or method for skipping its doctest.
230 230
231 231 This decorator allows you to mark a function whose docstring you wish to
232 232 omit from testing, while preserving the docstring for introspection, help,
233 233 etc.""")
234 234
235 235 # Decorators to skip certain tests on specific platforms.
236 236 skip_win32 = skipif(sys.platform == 'win32',
237 237 "This test does not run under Windows")
238 238 skip_linux = skipif(sys.platform == 'linux2',
239 239 "This test does not run under Linux")
240 240 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
241 241
242 242
243 243 # Decorators to skip tests if not on specific platforms.
244 244 skip_if_not_win32 = skipif(sys.platform != 'win32',
245 245 "This test only runs under Windows")
246 246 skip_if_not_linux = skipif(sys.platform != 'linux2',
247 247 "This test only runs under Linux")
248 248 skip_if_not_osx = skipif(sys.platform != 'darwin',
249 249 "This test only runs under OSX")
250 250
251 251 # Other skip decorators
252 252 skipif_not_numpy = skipif(numpy_not_available,"This test requires numpy")
253 253
254 254 skipknownfailure = skip('This test is known to fail')
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now