#!/usr/bin/env python
#############################################################################
##
# This file is part of Taurus
##
# http://taurus-scada.org
##
# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
##
# Taurus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
##
# Taurus 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 Lesser General Public License for more details.
##
# You should have received a copy of the GNU Lesser General Public License
# along with Taurus. 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)
"""
from __future__ import print_function
import sys
__all__ = ["accepts", "returns"]
__docformat__ = "restructuredtext"
[docs]def accepts(*types, **kw):
"""
Function decorator. Checks that inputs given to decorated function
are of the expected type.
:param types: The expected type of the decorated function's return value
:param debug: Optional specification of 'debug' level (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('TypeWarning: ', msg, file=sys.stderr)
elif debug == 2:
raise TypeError(msg)
return f(*args)
newf.__name__ = f.__name__
return newf
return decorator
except KeyError as key:
raise KeyError(key + "is not a valid keyword argument")
except TypeError as msg:
raise TypeError(msg)
[docs]def returns(ret_type, **kw):
"""
Function decorator. Checks that return value of decorated function
is of the expected type.
:param ret_type: The expected type of the decorated function's return value.
:param debug: Optional specification of 'debug' level (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('TypeWarning: ', msg, file=sys.stderr)
elif debug == 2:
raise TypeError(msg)
return result
newf.__name__ = f.__name__
return newf
return decorator
except KeyError as key:
raise KeyError(key + "is not a valid keyword argument")
except TypeError as msg:
raise TypeError(msg)
[docs]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