1
# Copyright (C) 2009 Stephan Peijnik (stephan@peijnik.at)
3
# This program is free software: you can redistribute it and/or modify
4
# it under the terms of the GNU Lesser General Public License as published by
5
# the Free Software Foundation, either version 3 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU Lesser General Public License for more details.
13
# You should have received a copy of the GNU Lesser General Public License
14
# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
__version__ = '0.9.5-dev'
22
from new import classobj
24
__all__ = ['ArgvalidateException',
25
'DecoratorNonKeyLengthException', 'DecoratorKeyUnspecifiedException',
26
'DecoratorStackingException', 'ArgumentTypeException',
27
'func_args', 'method_args', 'return_value',
28
'one_of', 'type_any', 'raises_exceptions', 'warns_kwarg_as_arg',
31
# Check for environment variables
33
if 'ARGVALIDATE_WARN' in os.environ:
34
argvalidate_warn_str = os.environ['ARGVALIDATE_WARN']
36
argvalidate_warn = int(argvalidate_warn_str)
40
argvalidate_warn_kwarg_as_arg = 0
41
if 'ARGVALIDATE_WARN_KWARG_AS_ARG' in os.environ:
42
argvalidate_warn_kwarg_as_arg_str =\
43
os.environ['ARGVALIDATE_WARN_KWARG_AS_ARG']
45
argvalidate_warn_kwarg_as_arg =\
46
int(argvalidate_warn_kwarg_as_arg_str)
50
class ArgvalidateException(Exception):
52
Base argvalidate exception.
54
Used as base for all exceptions.
61
class DecoratorNonKeyLengthException(ArgvalidateException):
63
Exception for invalid decorator non-keyword argument count.
65
This exception provides the following attributes:
68
Name of function that caused the exception to be raised
72
Number of arguments that were expected (int, read-only).
75
Number of arguments that were passed to the function (int, read-only).
78
def __init__(self, func_name, expected_count, passed_count):
79
self.func_name = func_name
80
self.expected_count = expected_count
81
self.passed_count = passed_count
82
msg = '%s: wrong number of non-keyword arguments specified in' %\
84
msg += ' decorator (expected %d, got %d).' %\
85
(expected_count, passed_count)
86
ArgvalidateException.__init__(self, msg)
88
class DecoratorKeyUnspecifiedException(ArgvalidateException):
90
Exception for unspecified decorator keyword argument.
92
This exception provides the following attributes:
95
Name of function that caused the exception to be raised
99
Name of the keyword argument passed to the function, but not specified
100
in the decorator (str, read-only).
103
def __init__(self, func_name, arg_name):
104
self.func_name = func_name
105
self.arg_name = arg_name
106
msg = '%s: keyword argument %s not specified in decorator.' %\
107
(func_name, arg_name)
108
ArgvalidateException.__init__(self, msg)
110
class DecoratorStackingException(ArgvalidateException):
112
Exception for stacking a decorator with itself.
114
This exception provides the following attributes:
117
Name of function that caused the exception to be raised
121
Name of the decorator that was stacked with itself (str, read-only).
124
def __init__(self, func_name, decorator_name):
125
self.func_name = func_name
126
self.decorator_name = decorator_name
127
msg = '%s: decorator %s stacked with itself.' %\
128
(func_name, decorator_name)
129
ArgvalidateException.__init__(self, msg)
131
class ArgumentTypeException(ArgvalidateException):
133
Exception for invalid argument type.
135
This exception provides the following attributes:
138
Name of function that caused the exception to be raised
142
Name of the keyword argument passed to the function, but not specified
143
in the decorator (str, read-only).
146
Argument type that was expected (type, read-only).
149
Argument type that was passed to the function (type, read-only).
152
def __init__(self, func_name, arg_name, expected_type, passed_type):
153
self.func_name = func_name
154
self.arg_name = arg_name
155
self.expected_type = expected_type
156
self.passed_type = passed_type
157
msg = '%s: invalid argument type for %r (expected %r, got %r).' %\
158
(func_name, arg_name, expected_type, passed_type)
159
ArgvalidateException.__init__(self, msg)
161
class ReturnValueTypeException(ArgvalidateException):
163
Exception for invalid return value type.
165
This exception provides the following attributes:
168
Name of function that caused the exception to be raised
172
Argument type that was expected (type, read-only).
175
Type of value returned by the function (type, read-only).
178
def __init__(self, func_name, expected_type, passed_type):
179
self.func_name = func_name
180
self.expected_type = expected_type
181
self.passed_type = passed_type
182
msg = '%s: invalid type for return value (expected %r, got %r).' %\
183
(func_name, expected_type, passed_type)
184
ArgvalidateException.__init__(self, msg)
186
class KWArgAsArgWarning(ArgvalidateException):
187
def __init__(self, func_name, arg_name):
188
msg = '%s: argument %s is a keyword argument and was passed as a '\
189
'non-keyword argument.' % (func_name, arg_name)
190
ArgvalidateException.__init__(self, msg)
192
def __raise(exception, stacklevel=3):
194
warnings.warn(exception, stacklevel=stacklevel)
198
def __check_return_value(func_name, expected_type, return_value):
199
return_value_type = type(return_value)
202
if expected_type is None:
205
elif isinstance(return_value, classobj):
206
if not isinstance(return_value, expected_type) and\
207
not issubclass(return_value.__class__, expected_type):
210
if not isinstance(return_value, expected_type):
214
__raise(ReturnValueTypeException(func_name, expected_type,\
215
return_value_type), stacklevel=3)
217
def __check_type(func_name, arg_name, expected_type, passed_value,\
219
passed_type = type(passed_value)
222
# None means the type is not checked
223
if expected_type is None:
227
elif isinstance(passed_value, classobj):
228
if not isinstance(passed_value, expected_type) and\
229
not issubclass(passed_value.__class__, expected_type):
234
if not isinstance(passed_value, expected_type):
238
__raise(ArgumentTypeException(func_name, arg_name, expected_type,\
239
passed_type), stacklevel=stacklevel)
241
def __check_args(type_args, type_kwargs, start=-1):
242
type_nonkey_argcount = len(type_args)
243
type_key_argcount = len(type_kwargs)
246
accepts_func = getattr(f, 'argvalidate_accepts_stacked_func', None)
250
raise DecoratorStackingException(accepts_func.func_name,\
253
raise DecoratorStackingException(accepts_func.func_name,\
256
raise DecoratorStackingException(accepts_func.func_name,\
259
raise DecoratorStackingException(accepts_func.func_name,\
260
'unknown; start=%d' % (start))
262
func = getattr(f, 'argvalidate_returns_stacked_func', f)
263
f_name = func.__name__
264
(func_args, func_varargs, func_varkw, func_defaults) =\
265
inspect.getargspec(func)
267
func_argcount = len(func_args)
270
# The original idea was to use inspect.ismethod here,
271
# but it seems as the decorator is called before the
272
# method is bound to a class, so this will always
274
# The new method follows the original idea of checking
275
# tha name of the first argument passed.
276
# self and cls indicate methods, everything else indicates
278
if start < 0 and func_argcount > 0 and func_args[0] in ['self', 'cls']:
280
func_args = func_args[1:]
283
func_args = func_args[1:]
288
func_args.remove(func_varargs)
291
func_args.remove(func_varkw)
294
func_key_argcount = 0
297
func_key_argcount = len(func_defaults)
298
tmp_key_args = zip(func_args[-func_key_argcount:], func_defaults)
300
for tmp_key_name, tmp_key_default in tmp_key_args:
301
func_key_args.update({tmp_key_name: tmp_key_default})
303
# Get rid of unused variables
308
func_nonkey_args = []
309
if func_key_argcount < func_argcount:
310
func_nonkey_args = func_args[:func_argcount-func_key_argcount]
311
func_nonkey_argcount = len(func_nonkey_args)
315
# Checking the lengths of type_args vs. func_args and type_kwargs vs.
316
# func_key_args should be done here.
318
# This means that the check is only performed when the decorator
319
# is actually invoked, not every time the target function is called.
320
if func_nonkey_argcount != type_nonkey_argcount:
321
__raise(DecoratorNonKeyLengthException(f_name,\
322
func_nonkey_argcount, type_nonkey_argcount))
324
if func_key_argcount != type_key_argcount:
325
__raise(DecoratorKeyLengthException(f_name,\
326
func_key_argcount, type_key_argcount))
330
# kwarg default value types.
332
tmp_kw_zip = zip(func_key_args, func_defaults)
333
for tmp_kw_name, tmp_kw_default in tmp_kw_zip:
334
if not tmp_kw_name in type_kwargs:
335
__raise(DecoratorKeyUnspecifiedException(f_name,\
338
tmp_kw_type = type_kwargs[tmp_kw_name]
339
__check_type(f_name, tmp_kw_name, tmp_kw_type, tmp_kw_default)
346
def __wrapper_func(*call_args, **call_kwargs):
347
call_nonkey_argcount = len(call_args)
348
call_key_argcount = len(call_kwargs)
349
call_nonkey_args = []
352
call_nonkey_args = call_args[1:]
354
call_nonkey_args = call_args[:]
359
# Non-keyword argument types.
360
if type_nonkey_argcount > 0:
361
tmp_zip = zip(call_nonkey_args, type_args,\
363
for tmp_call_value, tmp_type, tmp_arg_name in tmp_zip:
364
__check_type(f_name, tmp_arg_name, tmp_type, tmp_call_value)
369
# Keyword arguments passed as non-keyword arguments.
370
if type_nonkey_argcount < call_nonkey_argcount:
371
tmp_kwargs_as_args = zip(call_nonkey_args[type_nonkey_argcount:],\
372
func_key_args.keys())
374
for tmp_call_value, tmp_kwarg_name in tmp_kwargs_as_args:
375
tmp_type = type_kwargs[tmp_kwarg_name]
377
if argvalidate_warn_kwarg_as_arg:
378
warnings.warn(KWArgAsArgWarning(f_name, tmp_kwarg_name))
380
__check_type(f_name, tmp_kwarg_name, tmp_type,\
385
# Keyword argument types.
386
if call_key_argcount > 0:
387
for tmp_kwarg_name in call_kwargs:
388
if tmp_kwarg_name not in type_kwargs:
391
tmp_call_value = call_kwargs[tmp_kwarg_name]
392
tmp_type = type_kwargs[tmp_kwarg_name]
393
__check_type(f_name, tmp_kwarg_name, tmp_type,\
396
return func(*call_args, **call_kwargs)
399
__wrapper_func.func_name = func.__name__
400
__wrapper_func.__doc__ = func.__doc__
401
__wrapper_func.__dict__.update(func.__dict__)
403
__wrapper_func.argvalidate_accepts_stacked_func = func
404
return __wrapper_func
408
def accepts(*type_args, **type_kwargs):
410
Decorator used for checking arguments passed to a function or method.
412
:param start: method/function-detection override. The number of arguments
413
defined with start are ignored in all checks.
415
:param type_args: type definitions of non-keyword arguments.
416
:param type_kwargs: type definitions of keyword arguments.
418
:raises DecoratorNonKeyLengthException: Raised if the number of non-keyword
419
arguments specified in the decorator does not match the number of
420
non-keyword arguments the function accepts.
422
:raises DecoratorKeyLengthException: Raised if the number of keyword
423
arguments specified in the decorator does not match the number of
424
non-keyword arguments the function accepts.
426
:raises DecoratorKeyUnspecifiedException: Raised if a keyword argument's
427
type has not been specified in the decorator.
429
:raises ArgumentTypeException: Raised if an argument type passed to the
430
function does not match the type specified in the decorator.
436
def my_method(self, x_is_int, y_is_str):
440
def my_function(x_is_int, y_is_str):
444
return __check_args(type_args, type_kwargs, start=-1)
446
def returns(expected_type):
448
Decorator used for checking the return value of a function or method.
450
:param expected_type: expected type or return value
452
:raises ReturnValueTypeException: Raised if the return value's type does not
453
match the definition in the decorator's `expected_type` parameter.
464
returns_func = getattr(f, 'argvalidate_returns_stacked_func', None)
466
raise DecoratorStackingException(returns_func.func_name,'returns')
468
func = getattr(f, 'argvalidate_accepts_stacked_func', f)
470
def __wrapper_func(*args, **kwargs):
471
result = func(*args, **kwargs)
472
__check_return_value(func.func_name, expected_type, result)
475
__wrapper_func.func_name = func.__name__
476
__wrapper_func.__doc__ = func.__doc__
477
__wrapper_func.__dict__.update(func.__dict__)
479
__wrapper_func.argvalidate_returns_stacked_func = func
480
return __wrapper_func
484
# Wrappers for old decorators
485
def return_value(expected_type):
487
Wrapper for backwards-compatibility.
489
:deprecated: This decorator has been replaced with :func:`returns`.
492
warnings.warn(DeprecationWarning('The return_value decorator has been '\
493
'deprecated. Please use the returns decorator instead.'))
494
return returns(expected_type)
497
def method_args(*type_args, **type_kwargs):
499
Wrapper for backwards-compatibility.
501
:deprecated: This decorator has been replaced with :func:`accepts`.
504
warnings.warn(DeprecationWarning('The method_args decorator has been '\
505
'deprecated. Please use the accepts decorator instead.'))
506
return __check_args(type_args, type_kwargs, start=1)
508
def func_args(*type_args, **type_kwargs):
510
Wrapper for backwards-compatibility.
512
:deprecated: This decorator has been replaced with :func:`accepts`.
514
warnings.warn(DeprecationWarning('The func_args decorator has been '\
515
'deprecated. Please use the accepts decorator instead.'))
516
return __check_args(type_args, type_kwargs, start=0)
519
class __OneOfTuple(tuple):
521
return 'one of %r' % (tuple.__repr__(self))
523
# Used for readability, using a tuple alone would be sufficient.
526
Simple helper function to create a tuple from every argument passed to it.
528
:param args: type definitions
530
A tuple can be used instead of calling this function, however, the tuple
531
returned by this function contains a customized __repr__ method, which
532
makes Exceptions easier to read.
536
@func_check_args(one_of(int, str, float))
541
return __OneOfTuple(args)
543
def raises_exceptions():
545
Returns True if argvalidate raises exceptions, False if argvalidate
546
creates warnings instead.
548
This behaviour can be controlled via the environment variable
549
:envvar:`ARGVALIDATE_WARN`.
551
return not argvalidate_warn
553
def warns_kwarg_as_arg():
555
Returns True if argvalidate generates warnings for keyword arguments
558
This behaviour can be controlled via the environment variable
559
:envvar:`ARGVALIDATE_WARN_KWARG_AS_ARG`.
561
return argvalidate_kwarg_as_arg
563
# Used for readbility, using None alone would be sufficient