Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/Experior.Activity/sbdecorators.py
diff options
context:
space:
mode:
Diffstat (limited to 'Experior.Activity/sbdecorators.py')
-rwxr-xr-xExperior.Activity/sbdecorators.py148
1 files changed, 148 insertions, 0 deletions
diff --git a/Experior.Activity/sbdecorators.py b/Experior.Activity/sbdecorators.py
new file mode 100755
index 0000000..fcc85c9
--- /dev/null
+++ b/Experior.Activity/sbdecorators.py
@@ -0,0 +1,148 @@
+#!/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 <http://www.gnu.org/licenses/>.
+"""
+
+"""
+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