#! /usr/bin/env python # -*- encoding: utf-8 -*- #sbpython.py #This file is part of sugarbot. #sugarbot is free software: you can redistribute it and/or modify #it under the terms of the GNU General Public License as published by #the Free Software Foundation, either version 3 of the License, or #(at your option) any later version. #sugarbot is distributed in the hope that it will be useful, #but WITHOUT ANY WARRANTY; without even the implied warranty of #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #GNU General Public License for more details. #You should have received a copy of the GNU General Public License #along with sugarbot. If not, see """ 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 def accepts(*types, **kw): """ Function decorator. Checks that inputs given to decorated function are of the expected type. 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 = 1 else: debug = kw['debug'] try: def decorator(f): def newf(*args): if debug == 0: return f(*args) assert len(args) == len(types) argtypes = tuple(map(type, args)) if argtypes != types: msg = info(f.__name__, types, argtypes, 0) if debug == 1: print >> sys.stderr, 'TypeWarning: ', msg elif debug == 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 returns(ret_type, **kw): """ Function decorator. Checks that return value of decorated function 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 == 0: return result res_type = type(result) if res_type != ret_type: msg = info(f.__name__, (ret_type,), (res_type,), 1) if debug == 1: print >> sys.stderr, 'TypeWarning: ', msg elif debug == 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]) expected, actual = format(expected), format(actual) msg = "'%s' method " % fname \ + ("accepts", "returns")[flag] + " (%s), but " % expected\ + ("was given", "result is")[flag] + " (%s)" % actual return msg