Source code for kiwi.argcheck

#
# Kiwi: a Framework and Enhanced Widgets for Python
#
# Copyright (C) 2005,2006 Async Open Source
#
# This library 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 2.1 of the License, or (at your option) any later version.
#
# This library 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 this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
# USA
#
# Author(s): Johan Dahlin <jdahlin@async.com.br>
#

"""
Argument checking decorator and support
"""

import inspect
from types import ClassType

from kiwi.datatypes import number as number_type

[docs]class CustomType(type):
[docs] def value_check(mcs, name, value): pass
value_check = classmethod(value_check)
[docs]class number(CustomType): """ Custom type that verifies that the type is a number (eg float or int) """ type = number_type
[docs]class percent(CustomType): """ Custom type that verifies that the value is a percentage """ type = number_type
[docs] def value_check(mcs, name, value): if (value < 0) or (value > 100): raise ValueError("%s must be between 0 and 100" % name)
value_check = classmethod(value_check)
_NoValue = object()
[docs]class argcheck(object): """ Decorator to check type and value of arguments. Usage: >>> @argcheck(types...) ... def function(args..) or >>> class Class: ... @argcheck(types..) ... def method(self, args) You can customize the checks by subclassing your type from CustomType, there are two builtin types: number which is a float/int combined check and a percent which verifis that the value is a percentage """ __enabled__ = True def __init__(self, *types): for argtype in types: if not isinstance(argtype, (type, ClassType)): raise TypeError("must be a type or class instance, not %r" % argtype) self.types = types
[docs] def enable(cls): """ Enable argcheck globally """ cls.__enabled__ = True
enable = classmethod(enable)
[docs] def disable(cls): """ Disable argcheck globally """ cls.__enabled__ = False
disable = classmethod(disable) def __call__(self, func): if not callable(func): raise TypeError("%r must be callable" % func) # Useful for optimized runs if not self.__enabled__: return func spec = inspect.getargspec(func) arg_names, is_varargs, is_kwargs, default_values = spec if not default_values: default_values = [] else: default_values = list(default_values) # Set all the remaining default values to _NoValue default_values = ([_NoValue] * (len(arg_names) - len(default_values)) + default_values) # TODO: Is there another way of doing this? # Not trivial since func is not attached to the class at # this point. Nor is the class attached to the namespace. if arg_names and arg_names[0] in ('self', 'cls'): arg_names = arg_names[1:] default_values = default_values[1:] is_method = True else: is_method = False types = self.types if is_kwargs and not is_varargs and self.types: raise TypeError("argcheck cannot be used with only keywords") elif not is_varargs: if len(types) != len(arg_names): raise TypeError("%s has wrong number of arguments, " "%d specified in decorator, " "but function has %d" % (func.__name__, len(types), len(arg_names))) kwarg_types = {} kwarg_defaults = {} for i, arg_name in enumerate(arg_names): kwarg_types[arg_name] = types[i] value = default_values[i] kwarg_defaults[arg_name] = value if value is None or value is _NoValue: continue arg_type = types[i] try: self._type_check(value, arg_type, arg_name) except TypeError: raise TypeError("default value for %s must be of type %s " "and not %s" % (arg_name, arg_type.__name__, type(value).__name__)) kwarg_defaults[arg_name] = value def wrapper(*args, **kwargs): if self.__enabled__: cargs = args if is_method: cargs = cargs[1:] # Positional arguments for arg, type, name, default in zip(cargs, types, arg_names, default_values): self._type_check(arg, type, name, default) # Keyword arguments for name, arg in kwargs.items(): if not name in kwarg_types: raise TypeError( "%s() got an unexpected keyword argument '%s'" % (func.__name__, name)) self._type_check(arg, kwarg_types[name], name, kwarg_defaults[name]) self.extra_check(arg_names, types, args, kwargs) return func(*args, **kwargs) # Python 2.3 does not support assignments to __name__ try: wrapper.__name__ = func.__name__ except TypeError: pass return wrapper
[docs] def extra_check(self, names, types, args, kwargs): pass
def _type_check(self, value, argument_type, name, default=_NoValue): if default is not _NoValue and value == default: return if issubclass(argument_type, CustomType): custom = True check_type = argument_type.type else: custom = False check_type = argument_type type_name = argument_type.__name__ if not isinstance(value, check_type): raise TypeError( "%s must be %s, not %s" % (name, type_name, type(value).__name__)) if custom: argument_type.value_check(name, value)