1
2 """
3 Decorators for function signature checking and function tracing.
4 """
5
6
7
8
9 __all__ = ['trace', 'traceargs', 'tracereturn', 'traceall']
10 import sys
11 try:
12 from functools import update_wrapper
13 except:
15 newf.__name__ = f.__name__
16 newf.__dict__.update(f.__dict__.update)
17 newf.__doc__ = f.__doc__
18 newf.__module__ = f.__module__
19 return newf
20
22 """
23 Print the name of the caller and the name of the method called.
24
25 If a frame number is given then jump back that many frames before
26 reporting (this is handy for decorators).
27 """
28 frame = sys._getframe()
29 frame = frame.f_back
30 if showargs:
31
32
33 more = "with "+str(args[1:])+" "+str(kw)
34 else:
35 more = ""
36 callee = fn.__name__
37 try:
38 caller = frame.f_back.f_code.co_name
39 print ">",callee,"called from",caller,more
40 except:
41 print ">",callee,"called from __main__",more
42
44 "Function trace decorator"
45 def wrapper(*args, **kw):
46 _calledby(fn, args, kw, showargs=False)
47 return fn(*args, **kw)
48 return update_wrapper(wrapper, fn)
49
51 "Function trace decorator, shows args"
52 def wrapper(*args, **kw):
53 _calledby(fn, args, kw, showargs=True)
54 return fn(*args, **kw)
55 return update_wrapper(wrapper, fn)
56
58 "Function trace decorator, shows return value"
59 def wrapper(*args, **kw):
60 _calledby(fn, args, kw, showargs=False)
61 try:
62 retval = fn(*args, **kw)
63 print ">",fn.__name__,"returns",retval
64 except:
65 print ">",fn.__name__,"raised an exception"
66 raise
67 return retval
68 return update_wrapper(wrapper, fn)
69
71 "Function trace decorator, shows args and return value"
72 def wrapper(*args, **kw):
73 _calledby(fn, args, kw, showargs=True)
74 try:
75 retval = fn(*args, **kw)
76 print ">",fn.__name__,"returns",retval
77 except:
78 print ">",fn.__name__,"failed"
79 raise
80 return retval
81 return update_wrapper(wrapper, fn)
82
85 """
86 Raise type error if v is invalid.
87
88 The error message should be something like "is not an int"
89 so that we can report something like "p1: 'a' is not an int"
90 """
95 if isinstance(self.type,tuple):
96 msg = "is not in ("+" ".join([ti.__name__ for ti in self.type])+")"
97 else:
98 msg = "is not "+self.type.__name__
99 return msg
101 if not isinstance(v,self.type):
102 raise TypeError(self._msg())
103
110 """
111 Run all arguments for fn through validators.
112
113 Raises TypeError if any types do not match.
114 """
115
116
117
118
119 for k in range(len(args)):
120 v,t = args[k],argtypes[k]
121 trace = None
122 try:
123 t.check(v)
124 except TypeError,msg:
125 _,_,trace = sys.exc_info()
126 raise TypeError,_formatmsg(fn.func_code.co_varnames[k+1], v, msg),trace.tb_next.tb_next
127 finally:
128 del trace
129
130
131 for k,v in kw.items():
132 try:
133 kwtypes[k].check(v)
134 except TypeError,msg:
135 raise TypeError(formatmsg(t.__name__,v,msg))
136
138 """
139 Make sure t is a validator or a type.
140 """
141 if hasattr(t,'check'):
142 return t
143 elif isinstance(t,type) or (isinstance(t,tuple) and all(isinstance(k,type) for k in t)):
144 return InstanceValidator(t)
145 elif t == False:
146 return Validator()
147 else:
148 raise TypeError("improper validator")
149
150 -def sig(*argtypes,**kwtypes):
151 """
152 Decorator for type checking class methods.
153
154 The following creates a class with method hello that takes an integer::
155
156 class A:
157 @sig(int)
158 def hello(n): pass
159
160 The following should raise and error since hello is called with a string::
161
162 A().hello('h')
163
164 You can have a tuple instead of a single type, or you can provide your
165 own validator with a check method.
166
167 Keyword arguments can be checked with kw=validator.
168 Use returns=validator to check the return value.
169 Use a tuple of types to check if a value is from a list of types.
170 """
171
172
173 argtypes = [_validator(t) for t in argtypes]
174 returns = _validator(kwtypes.pop('returns',False))
175 kwtypes = dict([(k,_validator(t)) for k,t in kwtypes.items()])
176
177 def decorator(fn):
178
179 def wrapper(self, *args, **kw):
180
181 _sig_check(fn, args, argtypes, kw, kwtypes)
182 retval = fn(self, *args, **kw)
183 try:
184 returns.check(retval)
185 except TypeError,msg:
186 raise TypeError(_formatmsg('returns', retval, msg))
187 return retval
188
189 return update_wrapper(wrapper, fn)
190 return decorator
191
193 class OddInt(Validator):
194 def check(self,v):
195 if not isinstance(v,int) or v%2 != 1:
196 raise TypeError("is not an odd integer")
197 class Test(object):
198
199 @trace
200 @sig(int,(float,str),name=str)
201 def hello(self, n, x, name=None):
202 print "hello",n,x,name
203 pass
204
205
206 @traceargs
207 @sig(OddInt())
208 def world(self,n):
209 pass
210
211
212 @tracereturn
213 @sig(float,returns=complex)
214 def sqrt(self, a):
215 import math, cmath
216 if a < 0:
217 return cmath.sqrt(a)
218 else:
219 return math.sqrt(a)
220
221
222 Test().hello(3,5.,name='frank')
223 Test().hello(3,"5 mm",name='frank')
224 try: Test().hello(3.5,5.,name='frank')
225 except TypeError,msg: print msg
226
227
228 Test().world(3)
229 try: Test().world(4)
230 except TypeError,msg: print msg
231 try: Test().world('123456789112345678921234567893123456789')
232 except TypeError,msg: print msg
233
234
235 Test().sqrt(-1.)
236 try: Test().sqrt(1.)
237 except TypeError,msg: print msg
238
239 Test().sqrt(1.)
240
241 if __name__ == "__main__":
242 demo()
243