Package park :: Package util :: Module trace

Source Code for Module park.util.trace

  1  # This program is public domain 
  2  """ 
  3  Decorators for function signature checking and function tracing. 
  4  """ 
  5  # TODO: use @wraps(f) rather than update_wrapper(newf,f) 
  6  # TODO: others have already done this --- check the net 
  7  # TODO: python 3 allows type sig in the function prototype 
  8  # TODO: are we limited to runtime checking? 
  9  __all__ = ['trace', 'traceargs', 'tracereturn', 'traceall'] 
 10  import sys 
 11  try: 
 12      from functools import update_wrapper 
 13  except: 
14 - def update_wrapper(newf, f):
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
21 -def _calledby(fn,args,kw,showargs=False):
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 # Skip calledby frame 30 if showargs: 31 # Using 1: here to skip self 32 # Could pretty this up by mapping args to fn.func_code.co_varnames 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
43 -def trace(fn):
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
50 -def traceargs(fn):
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
57 -def tracereturn(fn):
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
70 -def traceall(fn):
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
83 -class Validator(object):
84 - def check(self,v):
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 """
91 -class InstanceValidator(Validator):
92 - def __init__(self, t):
93 self.type = t
94 - def _msg(self):
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
100 - def check(self,v):
101 if not isinstance(v,self.type): 102 raise TypeError(self._msg())
103
104 -def _formatmsg(argname,argvalue,error):
105 valstr = str(argvalue) 106 if len(valstr) > 20: 107 valstr = valstr[:8]+"..."+valstr[-8:] 108 return argname+":"+valstr+" "+str(error)
109 -def _sig_check(fn, args, argtypes, kw, kwtypes):
110 """ 111 Run all arguments for fn through validators. 112 113 Raises TypeError if any types do not match. 114 """ 115 #print "Args",args,argtypes 116 #print "kw",kw,kwtypes 117 118 # Check plain args 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 # Check kw args 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
137 -def _validator(t):
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() # No 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 #print "sig",argtypes,kwtypes 172 # Translate bare instances into validators 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 #print "decorator",fn 179 def wrapper(self, *args, **kw): 180 #print "wrapper" 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
192 -def demo():
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 # Type validator 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 # User defined validator 206 @traceargs 207 @sig(OddInt()) 208 def world(self,n): 209 pass 210 211 # Return validator 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 # Show that it works for the simple type test 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 # Show that it works for our own validators 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 # Show that return checking works 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