/compat/pytils/third/aspn426123.py
Python | 412 lines | 155 code | 98 blank | 159 comment | 28 complexity | 9a51cf7c17224a5a4f99c67a4ea7e310 MD5 | raw file
Possible License(s): BSD-3-Clause
- #!/usr/bin/env python
- # -*- coding: iso-8859-1 -*-
- ################################################################################
- #
- # Method call parameters/return value type checking decorators.
- # (c) 2006-2007, Dmitry Dvoinikov <dmitry@targeted.org>
- # Distributed under BSD license.
- #
- # Samples:
- #
- # from typecheck import *
- #
- # @takes(int, str) # takes int, str, upon a problem throws InputParameterError
- # @returns(int) # returns int, upon a problem throws ReturnValueError
- # def foo(i, s):
- # return i + len(s)
- #
- # @takes((int, long), by_regex("^[0-9]+$")) # int or long, numerical string
- # def foo(i, s, anything): # and the third parameter is not checked
- # ...
- #
- # @takes(int, int, foo = int, bar = optional(int)) # keyword argument foo must be int
- # def foo(a, b, **kwargs): # bar may be int or missing
- # ...
- #
- # Note: @takes for positional arguments, @takes for keyword arguments and @returns
- # all support the same checker syntax, for example for the following declaration
- #
- # @takes(C)
- # def foo(x):
- # ...
- #
- # then C may be one of the simple checkers:
- #
- # --------- C --------- ------------- semantics -------------
- # typename ==> ok if x is is an instance of typename
- # "typename" ==> ok if x is is an instance of typename
- # with_attr("a", "b") ==> ok if x has specific attributes
- # some_callable ==> ok if some_callable(x) is True
- # one_of(1, "2") ==> ok if x is one of the literal values
- # by_regex("^foo$") ==> ok if x is a matching basestring
- # nothing ==> ok if x is None
- # anything ==> always ok
- #
- # simple checkers can further be combined with OR semantics using tuples:
- #
- # --------- C --------- ------------- semantics -------------
- # (checker1, checker2) ==> ok if x conforms with either checker
- #
- # be optional:
- #
- # --------- C --------- ------------- semantics -------------
- # optional(checker) ==> ok if x is checker-conformant or None
- #
- # or nested recursively into one of the following checkers
- #
- # --------- C --------- ------------- semantics -------------
- # list_of(checker) ==> ok if x is a list of checker-conformant values
- # tuple_of(checker) ==> ok if x is a tuple of checker-conformant values
- # dict_of(key_checker, value_checker) ==> ok if x is a dict mapping key_checker-
- # conformant keys to value_checker-conformant values
- #
- # More samples:
- #
- # class foo(object):
- # @takes("foo", optional(int)) # foo, maybe int, but foo is yet incomplete
- # def __init__(self, i = None): # and is thus specified by name
- # ...
- # @takes("foo", int) # foo, and int if presents in args,
- # def bar(self, *args): # if args is empty, the check passes ok
- # ...
- # @takes("foo")
- # @returns(object) # returns foo which is fine, because
- # def biz(self): # foo is an object
- # return self
- # @classmethod # classmethod's and staticmethod's
- # @takes(type) # go same way
- # def baz(cls):
- # ...
- #
- # @takes(int)
- # @returns(optional("int", foo)) # returns either int, foo or NoneType
- # def bar(i): # "int" (rather than just int) is for fun
- # if i > 0:
- # return i
- # elif i == 0:
- # return foo() # otherwise returns NoneType
- #
- # @takes(callable) # built-in functions are treated as predicates
- # @returns(lambda x: x == 123) # and so do user-defined functions or lambdas
- # def execute(f, *args, **kwargs):
- # return f(*args, **kwargs)
- #
- # assert execute(execute, execute, execute, lambda x: x, 123) == 123
- #
- # def readable(x): # user-defined type-checking predicate
- # return hasattr(x, "read")
- #
- # anything is an alias for predicate lambda: True,
- # nothing is an alias for NoneType, as in:
- #
- # @takes(callable, readable, optional(anything), optional(int))
- # @returns(nothing)
- # def foo(f, r, x = None, i = None):
- # ...
- #
- # @takes(with_attr("read", "write")) # another way of protocol checking
- # def foo(pipe):
- # ...
- #
- # @takes(list_of(int)) # list of ints
- # def foo(x):
- # print x[0]
- #
- # @takes(tuple_of(callable)) # tuple of callables
- # def foo(x):
- # print x[0]()
- #
- # @takes(dict_of(str, list_of(int))) # dict mapping strs to lists of int
- # def foo(x):
- # print sum(x["foo"])
- #
- # @takes(by_regex("^[0-9]{1,8}$")) # integer-as-a-string regex
- # def foo(x):
- # i = int(x)
- #
- # @takes(one_of(1, 2)) # must be equal to either one
- # def set_version(version):
- # ...
- #
- # The (3 times longer) source code with self-tests is available from:
- # http://www.targeted.org/python/recipes/typecheck.py
- #
- ################################################################################
- __all__ = [ "takes", "InputParameterError", "returns", "ReturnValueError",
- "optional", "nothing", "anything", "list_of", "tuple_of", "dict_of",
- "by_regex", "with_attr", "one_of" ]
- no_check = False # set this to True to turn all checks off
- ################################################################################
- from inspect import getargspec, isfunction, isbuiltin, isclass
- from types import NoneType
- from re import compile as regex
- ################################################################################
- def base_names(C):
- "Returns list of base class names for a given class"
- return [ x.__name__ for x in C.__mro__ ]
-
- ################################################################################
- def type_name(v):
- "Returns the name of the passed value's type"
- return type(v).__name__
- ################################################################################
- class Checker(object):
- def __init__(self, reference):
- self.reference = reference
- def check(self, value): # abstract
- pass
- _registered = [] # a list of registered descendant class factories
- @staticmethod
- def create(value): # static factory method
- for f, t in Checker._registered:
- if f(value):
- return t(value)
- else:
- return None
- ################################################################################
- class TypeChecker(Checker):
- def check(self, value):
- return isinstance(value, self.reference)
- Checker._registered.append((isclass, TypeChecker))
- nothing = NoneType
- ################################################################################
- class StrChecker(Checker):
- def check(self, value):
- value_base_names = base_names(type(value))
- return self.reference in value_base_names or "instance" in value_base_names
-
- Checker._registered.append((lambda x: isinstance(x, str), StrChecker))
- ################################################################################
- class TupleChecker(Checker):
- def __init__(self, reference):
- self.reference = map(Checker.create, reference)
- def check(self, value):
- return reduce(lambda r, c: r or c.check(value), self.reference, False)
- Checker._registered.append((lambda x: isinstance(x, tuple) and not
- filter(lambda y: Checker.create(y) is None,
- x),
- TupleChecker))
- optional = lambda *args: args + (NoneType, )
- ################################################################################
- class FunctionChecker(Checker):
- def check(self, value):
- return self.reference(value)
- Checker._registered.append((lambda x: isfunction(x) or isbuiltin(x),
- FunctionChecker))
- anything = lambda *args: True
- ################################################################################
- class ListOfChecker(Checker):
- def __init__(self, reference):
- self.reference = Checker.create(reference)
- def check(self, value):
- return isinstance(value, list) and \
- not filter(lambda e: not self.reference.check(e), value)
- list_of = lambda *args: lambda value: ListOfChecker(*args).check(value)
- ################################################################################
- class TupleOfChecker(Checker):
- def __init__(self, reference):
- self.reference = Checker.create(reference)
- def check(self, value):
- return isinstance(value, tuple) and \
- not filter(lambda e: not self.reference.check(e), value)
- tuple_of = lambda *args: lambda value: TupleOfChecker(*args).check(value)
- ################################################################################
- class DictOfChecker(Checker):
- def __init__(self, key_reference, value_reference):
- self.key_reference = Checker.create(key_reference)
- self.value_reference = Checker.create(value_reference)
- def check(self, value):
- return isinstance(value, dict) and \
- not filter(lambda e: not self.key_reference.check(e), value.iterkeys()) and \
- not filter(lambda e: not self.value_reference.check(e), value.itervalues())
- dict_of = lambda *args: lambda value: DictOfChecker(*args).check(value)
- ################################################################################
- class RegexChecker(Checker):
- def __init__(self, reference):
- self.reference = regex(reference)
- def check(self, value):
- return isinstance(value, basestring) and self.reference.match(value)
- by_regex = lambda *args: lambda value: RegexChecker(*args).check(value)
- ################################################################################
- class AttrChecker(Checker):
- def __init__(self, *attrs):
- self.attrs = attrs
- def check(self, value):
- return reduce(lambda r, c: r and c, map(lambda a: hasattr(value, a), self.attrs), True)
- with_attr = lambda *args: lambda value: AttrChecker(*args).check(value)
- ################################################################################
- class OneOfChecker(Checker):
- def __init__(self, *values):
- self.values = values
- def check(self, value):
- return value in self.values
- one_of = lambda *args: lambda value: OneOfChecker(*args).check(value)
- ################################################################################
- def takes(*args, **kwargs):
- "Method signature checking decorator"
- # convert decorator arguments into a list of checkers
- checkers = []
- for i, arg in enumerate(args):
- checker = Checker.create(arg)
- if checker is None:
- raise TypeError("@takes decorator got parameter %d of unsupported "
- "type %s" % (i + 1, type_name(arg)))
- checkers.append(checker)
- kwcheckers = {}
- for kwname, kwarg in kwargs.iteritems():
- checker = Checker.create(kwarg)
- if checker is None:
- raise TypeError("@takes decorator got parameter %s of unsupported "
- "type %s" % (kwname, type_name(kwarg)))
- kwcheckers[kwname] = checker
- if no_check: # no type checking is performed, return decorated method itself
- def takes_proxy(method):
- return method
- else:
- def takes_proxy(method):
-
- method_args, method_defaults = getargspec(method)[0::3]
- def takes_invocation_proxy(*args, **kwargs):
- ## do not append the default parameters // 'DONT' by Pythy
- # if method_defaults is not None and len(method_defaults) > 0 \
- # and len(method_args) - len(method_defaults) <= len(args) < len(method_args):
- # args += method_defaults[len(args) - len(method_args):]
- # check the types of the actual call parameters
- for i, (arg, checker) in enumerate(zip(args, checkers)):
- if not checker.check(arg):
- raise InputParameterError("%s() got invalid parameter "
- "%d of type %s" %
- (method.__name__, i + 1,
- type_name(arg)))
- for kwname, checker in kwcheckers.iteritems():
- if not checker.check(kwargs.get(kwname, None)):
- raise InputParameterError("%s() got invalid parameter "
- "%s of type %s" %
- (method.__name__, kwname,
- type_name(kwargs.get(kwname, None))))
- return method(*args, **kwargs)
- takes_invocation_proxy.__name__ = method.__name__
- return takes_invocation_proxy
-
- return takes_proxy
- class InputParameterError(TypeError): pass
- ################################################################################
- def returns(sometype):
- "Return type checking decorator"
- # convert decorator argument into a checker
- checker = Checker.create(sometype)
- if checker is None:
- raise TypeError("@returns decorator got parameter of unsupported "
- "type %s" % type_name(sometype))
- if no_check: # no type checking is performed, return decorated method itself
- def returns_proxy(method):
- return method
- else:
- def returns_proxy(method):
-
- def returns_invocation_proxy(*args, **kwargs):
-
- result = method(*args, **kwargs)
-
- if not checker.check(result):
- raise ReturnValueError("%s() has returned an invalid "
- "value of type %s" %
- (method.__name__, type_name(result)))
- return result
-
- returns_invocation_proxy.__name__ = method.__name__
- return returns_invocation_proxy
-
- return returns_proxy
- class ReturnValueError(TypeError): pass
- ################################################################################
- # EOF