--- /dev/null
+# http://wiki.python.org/moin/PythonDecoratorLibrary#Type_Enforcement_.28accepts.2Freturns.29
+'''
+One of three degrees of enforcement may be specified by passing
+the 'debug' keyword argument to the decorator:
+ 0 -- NONE: No type-checking. Decorators disabled.
+ 1 -- MEDIUM: Print warning message to stderr. (Default)
+ 2 -- STRONG: Raise TypeError with message.
+If 'debug' is not passed to the decorator, the default level is used.
+
+Example usage:
+ >>> NONE, MEDIUM, STRONG = 0, 1, 2
+ >>>
+ >>> @accepts(int, int, int)
+ ... @returns(float)
+ ... def average(x, y, z):
+ ... return (x + y + z) / 2
+ ...
+ >>> average(5.5, 10, 15.0)
+ TypeWarning: 'average' method accepts (int, int, int), but was given
+ (float, int, float)
+ 15.25
+ >>> average(5, 10, 15)
+ TypeWarning: 'average' method returns (float), but result is (int)
+ 15
+
+Needed to cast params as floats in function def (or simply divide by 2.0).
+
+ >>> TYPE_CHECK = STRONG
+ >>> @accepts(int, debug=TYPE_CHECK)
+ ... @returns(int, debug=TYPE_CHECK)
+ ... def fib(n):
+ ... if n in (0, 1): return n
+ ... return fib(n-1) + fib(n-2)
+ ...
+ >>> fib(5.3)
+ Traceback (most recent call last):
+ ...
+ TypeError: 'fib' method accepts (int), but was given (float)
+
+'''
+import sys
+from itertools import izip
+
+def accepts(*types, **kw):
+ '''Function decorator. Checks decorated function's arguments are
+ of the expected types.
+
+ Parameters:
+ types -- The expected types of the inputs to the decorated function.
+ Must specify type for each parameter.
+ kw -- Optional specification of 'debug' level (this is the only valid
+ keyword argument, no other should be given).
+ debug = ( 0 | 1 | 2 )
+
+ '''
+ if not kw:
+ # default level: MEDIUM
+ debug = 2
+ else:
+ debug = kw['debug']
+ try:
+ def decorator(f):
+ def newf(*args):
+ if debug is 0:
+ return f(*args)
+ assert len(args) == len(types)
+ argtypes = tuple(map(type, args))
+ if not compare_types(types, argtypes):
+ # if argtypes != types:
+ msg = info(f.__name__, types, argtypes, 0)
+ if debug is 1:
+ print >> sys.stderr, 'TypeWarning: ', msg
+ elif debug is 2:
+ raise TypeError, msg
+ return f(*args)
+ newf.__name__ = f.__name__
+ return newf
+ return decorator
+ except KeyError, key:
+ raise KeyError, key + "is not a valid keyword argument"
+ except TypeError, msg:
+ raise TypeError, msg
+
+def compare_types(expected, actual):
+ if isinstance(expected, tuple):
+ if isinstance(actual, tuple):
+ for x, y in izip(expected, actual):
+ if not compare_types(x ,y):
+ return False
+ return True
+ else:
+ return actual == type(None) or actual in expected
+ else:
+ return actual == type(None) or actual == expected or issubclass(actual, expected)
+
+def returns(ret_type, **kw):
+ '''Function decorator. Checks decorated function's return value
+ is of the expected type.
+
+ Parameters:
+ ret_type -- The expected type of the decorated function's return value.
+ Must specify type for each parameter.
+ kw -- Optional specification of 'debug' level (this is the only valid
+ keyword argument, no other should be given).
+ debug=(0 | 1 | 2)
+ '''
+ try:
+ if not kw:
+ # default level: MEDIUM
+ debug = 1
+ else:
+ debug = kw['debug']
+ def decorator(f):
+ def newf(*args):
+ result = f(*args)
+ if debug is 0:
+ return result
+ res_type = type(result)
+ if not compare_types(ret_type, res_type):
+ # if res_type != ret_type: # JORDAN: fix to allow for # StringTypes = (str, unicode)
+ # XXX note that this check should be recursive
+ msg = info(f.__name__, (ret_type,), (res_type,), 1)
+ if debug is 1:
+ print >> sys.stderr, 'TypeWarning: ', msg
+ elif debug is 2:
+ raise TypeError, msg
+ return result
+ newf.__name__ = f.__name__
+ return newf
+ return decorator
+ except KeyError, key:
+ raise KeyError, key + "is not a valid keyword argument"
+ except TypeError, msg:
+ raise TypeError, msg
+
+def info(fname, expected, actual, flag):
+ '''Convenience function returns nicely formatted error/warning msg.'''
+ format = lambda types: ', '.join([str(t).split("'")[1] for t in types])
+ msg = "'{}' method ".format( fname )\
+ + ("accepts", "returns")[flag] + " ({}), but ".format(expected)\
+ + ("was given", "result is")[flag] + " ({})".format(actual)
+ return msg
+