File indexing completed on 2025-02-16 10:43:06
0001 # -*- coding: utf-8 -*- 0002 # Copyright (C) 2009 Stephan Peijnik (stephan@peijnik.at) 0003 # 0004 # This program is free software: you can redistribute it and/or modify 0005 # it under the terms of the GNU Lesser General Public License as published by 0006 # the Free Software Foundation, either version 3 of the License, or 0007 # (at your option) any later version. 0008 # 0009 # This program is distributed in the hope that it will be useful, 0010 # but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 # GNU Lesser General Public License for more details. 0013 # 0014 # You should have received a copy of the GNU Lesser General Public License 0015 # along with this program. If not, see <http://www.gnu.org/licenses/>. 0016 0017 __version__ = '0.9.0' 0018 0019 import inspect 0020 import os 0021 import warnings 0022 0023 from new import classobj 0024 0025 __all__ = ['ArgvalidateException', 0026 'DecoratorNonKeyLengthException', 'DecoratorKeyUnspecifiedException', 0027 'DecoratorStackingException', 'ArgumentTypeException', 0028 'func_args', 'method_args', 'return_value', 0029 'one_of', 'type_any', 'raises_exceptions', 'warns_kwarg_as_arg', 0030 'accepts', 'returns'] 0031 0032 # Check for environment variables 0033 argvalidate_warn = 0 0034 if 'ARGVALIDATE_WARN' in os.environ: 0035 argvalidate_warn_str = os.environ['ARGVALIDATE_WARN'] 0036 try: 0037 argvalidate_warn = int(argvalidate_warn_str) 0038 except ValueError: 0039 pass 0040 0041 argvalidate_warn_kwarg_as_arg = 0 0042 if 'ARGVALIDATE_WARN_KWARG_AS_ARG' in os.environ: 0043 argvalidate_warn_kwarg_as_arg_str =\ 0044 os.environ['ARGVALIDATE_WARN_KWARG_AS_ARG'] 0045 try: 0046 argvalidate_warn_kwarg_as_arg =\ 0047 int(argvalidate_warn_kwarg_as_arg_str) 0048 except ValueError: 0049 pass 0050 0051 class ArgvalidateException(Exception): 0052 """ 0053 Base argvalidate exception. 0054 0055 Used as base for all exceptions. 0056 0057 """ 0058 pass 0059 0060 0061 0062 class DecoratorNonKeyLengthException(ArgvalidateException): 0063 """ 0064 Exception for invalid decorator non-keyword argument count. 0065 0066 This exception provides the following attributes: 0067 0068 * func_name 0069 Name of function that caused the exception to be raised 0070 (str, read-only). 0071 0072 * expected_count 0073 Number of arguments that were expected (int, read-only). 0074 0075 * passed_count 0076 Number of arguments that were passed to the function (int, read-only). 0077 0078 """ 0079 def __init__(self, func_name, expected_count, passed_count): 0080 self.func_name = func_name 0081 self.expected_count = expected_count 0082 self.passed_count = passed_count 0083 msg = '%s: wrong number of non-keyword arguments specified in' %\ 0084 (func_name) 0085 msg += ' decorator (expected %d, got %d).' %\ 0086 (expected_count, passed_count) 0087 ArgvalidateException.__init__(self, msg) 0088 0089 class DecoratorKeyUnspecifiedException(ArgvalidateException): 0090 """ 0091 Exception for unspecified decorator keyword argument. 0092 0093 This exception provides the following attributes: 0094 0095 * func_name 0096 Name of function that caused the exception to be raised 0097 (str, read-only). 0098 0099 * arg_name 0100 Name of the keyword argument passed to the function, but not specified 0101 in the decorator (str, read-only). 0102 0103 """ 0104 def __init__(self, func_name, arg_name): 0105 self.func_name = func_name 0106 self.arg_name = arg_name 0107 msg = '%s: keyword argument %s not specified in decorator.' %\ 0108 (func_name, arg_name) 0109 ArgvalidateException.__init__(self, msg) 0110 0111 class DecoratorStackingException(ArgvalidateException): 0112 """ 0113 Exception for stacking a decorator with itself. 0114 0115 This exception provides the following attributes: 0116 0117 * func_name 0118 Name of function that caused the exception to be raised 0119 (str, read-only). 0120 0121 * decorator_name 0122 Name of the decorator that was stacked with itself (str, read-only). 0123 0124 """ 0125 def __init__(self, func_name, decorator_name): 0126 self.func_name = func_name 0127 self.decorator_name = decorator_name 0128 msg = '%s: decorator %s stacked with itself.' %\ 0129 (func_name, decorator_name) 0130 ArgvalidateException.__init__(self, msg) 0131 0132 class ArgumentTypeException(ArgvalidateException): 0133 """ 0134 Exception for invalid argument type. 0135 0136 This exception provides the following attributes: 0137 0138 * func_name 0139 Name of function that caused the exception to be raised 0140 (str, read-only). 0141 0142 * arg_name 0143 Name of the keyword argument passed to the function, but not specified 0144 in the decorator (str, read-only). 0145 0146 * expected_type 0147 Argument type that was expected (type, read-only). 0148 0149 * passed_type 0150 Argument type that was passed to the function (type, read-only). 0151 0152 """ 0153 def __init__(self, func_name, arg_name, expected_type, passed_type): 0154 self.func_name = func_name 0155 self.arg_name = arg_name 0156 self.expected_type = expected_type 0157 self.passed_type = passed_type 0158 msg = '%s: invalid argument type for %r (expected %r, got %r).' %\ 0159 (func_name, arg_name, expected_type, passed_type) 0160 ArgvalidateException.__init__(self, msg) 0161 0162 class ReturnValueTypeException(ArgvalidateException): 0163 """ 0164 Exception for invalid return value type. 0165 0166 This exception provides the following attributes: 0167 0168 * func_name 0169 Name of function that caused the exception to be raised 0170 (string, read-only). 0171 0172 * expected_type 0173 Argument type that was expected (type, read-only). 0174 0175 * passed_type 0176 Type of value returned by the function (type, read-only). 0177 0178 """ 0179 def __init__(self, func_name, expected_type, passed_type): 0180 self.func_name = func_name 0181 self.expected_type = expected_type 0182 self.passed_type = passed_type 0183 msg = '%s: invalid type for return value (expected %r, got %r).' %\ 0184 (func_name, expected_type, passed_type) 0185 ArgvalidateException.__init__(self, msg) 0186 0187 class KWArgAsArgWarning(ArgvalidateException): 0188 def __init__(self, func_name, arg_name): 0189 msg = '%s: argument %s is a keyword argument and was passed as a '\ 0190 'non-keyword argument.' % (func_name, arg_name) 0191 ArgvalidateException.__init__(self, msg) 0192 0193 def __raise(exception, stacklevel=3): 0194 if argvalidate_warn: 0195 warnings.warn(exception, stacklevel=stacklevel) 0196 else: 0197 raise exception 0198 0199 def __check_return_value(func_name, expected_type, return_value): 0200 return_value_type = type(return_value) 0201 error = False 0202 0203 if expected_type is None: 0204 error = False 0205 0206 elif isinstance(return_value, classobj): 0207 if not isinstance(return_value, expected_type) and\ 0208 not issubclass(return_value.__class__, expected_type): 0209 error=True 0210 else: 0211 if not isinstance(return_value, expected_type): 0212 error=True 0213 0214 if error: 0215 __raise(ReturnValueTypeException(func_name, expected_type,\ 0216 return_value_type), stacklevel=3) 0217 0218 def __check_type(func_name, arg_name, expected_type, passed_value,\ 0219 stacklevel=4): 0220 passed_type = type(passed_value) 0221 error=False 0222 0223 # None means the type is not checked 0224 if expected_type is None: 0225 error=False 0226 0227 # Check a class 0228 elif isinstance(passed_value, classobj): 0229 if not isinstance(passed_value, expected_type) and\ 0230 not issubclass(passed_value.__class__, expected_type): 0231 error=True 0232 0233 # Check a type 0234 else: 0235 if not isinstance(passed_value, expected_type): 0236 error=True 0237 0238 if error: 0239 __raise(ArgumentTypeException(func_name, arg_name, expected_type,\ 0240 passed_type), stacklevel=stacklevel) 0241 0242 def __check_args(type_args, type_kwargs, start=-1): 0243 type_nonkey_argcount = len(type_args) 0244 type_key_argcount = len(type_kwargs) 0245 0246 def validate(f): 0247 accepts_func = getattr(f, 'argvalidate_accepts_stacked_func', None) 0248 0249 if accepts_func: 0250 if start == -1: 0251 raise DecoratorStackingException(accepts_func.func_name,\ 0252 'accepts') 0253 if start == 0: 0254 raise DecoratorStackingException(accepts_func.func_name,\ 0255 'function_accepts') 0256 elif start == 1: 0257 raise DecoratorStackingException(accepts_func.func_name,\ 0258 'method_accepts') 0259 else: 0260 raise DecoratorStackingException(accepts_func.func_name,\ 0261 'unknown; start=%d' % (start)) 0262 0263 func = getattr(f, 'argvalidate_returns_stacked_func', f) 0264 f_name = func.__name__ 0265 (func_args, func_varargs, func_varkw, func_defaults) =\ 0266 inspect.getargspec(func) 0267 0268 func_argcount = len(func_args) 0269 is_method = True 0270 0271 # The original idea was to use inspect.ismethod here, 0272 # but it seems as the decorator is called before the 0273 # method is bound to a class, so this will always 0274 # return False. 0275 # The new method follows the original idea of checking 0276 # tha name of the first argument passed. 0277 # self and cls indicate methods, everything else indicates 0278 # a function. 0279 if start < 0 and func_argcount > 0 and func_args[0] in ['self', 'cls']: 0280 func_argcount -= 1 0281 func_args = func_args[1:] 0282 elif start == 1: 0283 func_argcount -=1 0284 func_args = func_args[1:] 0285 else: 0286 is_method = False 0287 0288 if func_varargs: 0289 func_args.remove(func_varargs) 0290 0291 if func_varkw: 0292 func_args.remove(func_varkw) 0293 0294 func_key_args = {} 0295 func_key_argcount = 0 0296 0297 if func_defaults: 0298 func_key_argcount = len(func_defaults) 0299 tmp_key_args = zip(func_args[-func_key_argcount:], func_defaults) 0300 0301 for tmp_key_name, tmp_key_default in tmp_key_args: 0302 func_key_args.update({tmp_key_name: tmp_key_default}) 0303 0304 # Get rid of unused variables 0305 del tmp_key_args 0306 del tmp_key_name 0307 del tmp_key_default 0308 0309 func_nonkey_args = [] 0310 if func_key_argcount < func_argcount: 0311 func_nonkey_args = func_args[:func_argcount-func_key_argcount] 0312 func_nonkey_argcount = len(func_nonkey_args) 0313 0314 # Static check #0: 0315 # 0316 # Checking the lengths of type_args vs. func_args and type_kwargs vs. 0317 # func_key_args should be done here. 0318 # 0319 # This means that the check is only performed when the decorator 0320 # is actually invoked, not every time the target function is called. 0321 if func_nonkey_argcount != type_nonkey_argcount: 0322 __raise(DecoratorNonKeyLengthException(f_name,\ 0323 func_nonkey_argcount, type_nonkey_argcount)) 0324 0325 if func_key_argcount != type_key_argcount: 0326 __raise(DecoratorKeyLengthException(f_name,\ 0327 func_key_argcount, type_key_argcount)) 0328 0329 # Static check #1: 0330 # 0331 # kwarg default value types. 0332 if func_defaults: 0333 tmp_kw_zip = zip(func_args[-func_key_argcount:], func_defaults) 0334 for tmp_kw_name, tmp_kw_default in tmp_kw_zip: 0335 if not tmp_kw_name in type_kwargs: 0336 __raise(DecoratorKeyUnspecifiedException(f_name,\ 0337 tmp_kw_name)) 0338 0339 tmp_kw_type = type_kwargs[tmp_kw_name] 0340 __check_type(f_name, tmp_kw_name, tmp_kw_type, tmp_kw_default) 0341 0342 del tmp_kw_type 0343 del tmp_kw_name 0344 del tmp_kw_default 0345 del tmp_kw_zip 0346 0347 def __wrapper_func(*call_args, **call_kwargs): 0348 call_nonkey_argcount = len(call_args) 0349 call_key_argcount = len(call_kwargs) 0350 call_nonkey_args = [] 0351 0352 if is_method: 0353 call_nonkey_args = call_args[1:] 0354 else: 0355 call_nonkey_args = call_args[:] 0356 0357 # Dynamic check #1: 0358 # 0359 # 0360 # Non-keyword argument types. 0361 if type_nonkey_argcount > 0: 0362 tmp_zip = zip(call_nonkey_args, type_args,\ 0363 func_nonkey_args) 0364 for tmp_call_value, tmp_type, tmp_arg_name in tmp_zip: 0365 __check_type(f_name, tmp_arg_name, tmp_type, tmp_call_value) 0366 0367 0368 # Dynamic check #2: 0369 # 0370 # Keyword arguments passed as non-keyword arguments. 0371 if type_nonkey_argcount < call_nonkey_argcount: 0372 tmp_kwargs_as_args = zip(call_nonkey_args[type_nonkey_argcount:],\ 0373 func_args[-func_key_argcount:]) 0374 0375 for tmp_call_value, tmp_kwarg_name in tmp_kwargs_as_args: 0376 tmp_type = type_kwargs[tmp_kwarg_name] 0377 0378 if argvalidate_warn_kwarg_as_arg: 0379 warnings.warn(KWArgAsArgWarning(f_name, tmp_kwarg_name)) 0380 0381 __check_type(f_name, tmp_kwarg_name, tmp_type,\ 0382 tmp_call_value) 0383 0384 # Dynamic check #3: 0385 # 0386 # Keyword argument types. 0387 if call_key_argcount > 0: 0388 for tmp_kwarg_name in call_kwargs: 0389 if tmp_kwarg_name not in type_kwargs: 0390 continue 0391 0392 tmp_call_value = call_kwargs[tmp_kwarg_name] 0393 tmp_type = type_kwargs[tmp_kwarg_name] 0394 __check_type(f_name, tmp_kwarg_name, tmp_type,\ 0395 tmp_call_value) 0396 0397 return func(*call_args, **call_kwargs) 0398 0399 0400 __wrapper_func.func_name = func.__name__ 0401 __wrapper_func.__doc__ = func.__doc__ 0402 __wrapper_func.__dict__.update(func.__dict__) 0403 0404 __wrapper_func.argvalidate_accepts_stacked_func = func 0405 return __wrapper_func 0406 0407 return validate 0408 0409 def accepts(*type_args, **type_kwargs): 0410 """ 0411 Decorator used for checking arguments passed to a function or method. 0412 0413 :param start: method/function-detection override. The number of arguments 0414 defined with start are ignored in all checks. 0415 0416 :param type_args: type definitions of non-keyword arguments. 0417 :param type_kwargs: type definitions of keyword arguments. 0418 0419 :raises DecoratorNonKeyLengthException: Raised if the number of non-keyword 0420 arguments specified in the decorator does not match the number of 0421 non-keyword arguments the function accepts. 0422 0423 :raises DecoratorKeyLengthException: Raised if the number of keyword 0424 arguments specified in the decorator does not match the number of 0425 non-keyword arguments the function accepts. 0426 0427 :raises DecoratorKeyUnspecifiedException: Raised if a keyword argument's 0428 type has not been specified in the decorator. 0429 0430 :raises ArgumentTypeException: Raised if an argument type passed to the 0431 function does not match the type specified in the decorator. 0432 0433 Example:: 0434 0435 class MyClass: 0436 @accepts(int, str) 0437 def my_method(self, x_is_int, y_is_str): 0438 [...] 0439 0440 @accepts(int, str) 0441 def my_function(x_is_int, y_is_str): 0442 [....] 0443 0444 """ 0445 return __check_args(type_args, type_kwargs, start=-1) 0446 0447 def returns(expected_type): 0448 """ 0449 Decorator used for checking the return value of a function or method. 0450 0451 :param expected_type: expected type or return value 0452 0453 :raises ReturnValueTypeException: Raised if the return value's type does not 0454 match the definition in the decorator's `expected_type` parameter. 0455 0456 Example:: 0457 0458 @return_value(int) 0459 def my_func(): 0460 return 5 0461 0462 """ 0463 def validate(f): 0464 0465 returns_func = getattr(f, 'argvalidate_returns_stacked_func', None) 0466 if returns_func: 0467 raise DecoratorStackingException(returns_func.func_name,'returns') 0468 0469 func = getattr(f, 'argvalidate_accepts_stacked_func', f) 0470 0471 def __wrapper_func(*args, **kwargs): 0472 result = func(*args, **kwargs) 0473 __check_return_value(func.func_name, expected_type, result) 0474 return result 0475 0476 __wrapper_func.func_name = func.__name__ 0477 __wrapper_func.__doc__ = func.__doc__ 0478 __wrapper_func.__dict__.update(func.__dict__) 0479 0480 __wrapper_func.argvalidate_returns_stacked_func = func 0481 return __wrapper_func 0482 0483 return validate 0484 0485 # Wrappers for old decorators 0486 def return_value(expected_type): 0487 """ 0488 Wrapper for backwards-compatibility. 0489 0490 :deprecated: This decorator has been replaced with :func:`returns`. 0491 0492 """ 0493 warnings.warn(DeprecationWarning('The return_value decorator has been '\ 0494 'deprecated. Please use the returns decorator instead.')) 0495 return returns(expected_type) 0496 0497 0498 def method_args(*type_args, **type_kwargs): 0499 """ 0500 Wrapper for backwards-compatibility. 0501 0502 :deprecated: This decorator has been replaced with :func:`accepts`. 0503 0504 """ 0505 warnings.warn(DeprecationWarning('The method_args decorator has been '\ 0506 'deprecated. Please use the accepts decorator instead.')) 0507 return __check_args(type_args, type_kwargs, start=1) 0508 0509 def func_args(*type_args, **type_kwargs): 0510 """ 0511 Wrapper for backwards-compatibility. 0512 0513 :deprecated: This decorator has been replaced with :func:`accepts`. 0514 """ 0515 warnings.warn(DeprecationWarning('The func_args decorator has been '\ 0516 'deprecated. Please use the accepts decorator instead.')) 0517 return __check_args(type_args, type_kwargs, start=0) 0518 0519 0520 class __OneOfTuple(tuple): 0521 def __repr__(self): 0522 return 'one of %r' % (tuple.__repr__(self)) 0523 0524 # Used for readability, using a tuple alone would be sufficient. 0525 def one_of(*args): 0526 """ 0527 Simple helper function to create a tuple from every argument passed to it. 0528 0529 :param args: type definitions 0530 0531 A tuple can be used instead of calling this function, however, the tuple 0532 returned by this function contains a customized __repr__ method, which 0533 makes Exceptions easier to read. 0534 0535 Example:: 0536 0537 @func_check_args(one_of(int, str, float)) 0538 def my_func(x): 0539 pass 0540 0541 """ 0542 return __OneOfTuple(args) 0543 0544 def raises_exceptions(): 0545 """ 0546 Returns True if argvalidate raises exceptions, False if argvalidate 0547 creates warnings instead. 0548 0549 This behaviour can be controlled via the environment variable 0550 :envvar:`ARGVALIDATE_WARN`. 0551 """ 0552 return not argvalidate_warn 0553 0554 def warns_kwarg_as_arg(): 0555 """ 0556 Returns True if argvalidate generates warnings for keyword arguments 0557 passed as arguments. 0558 0559 This behaviour can be controlled via the environment variable 0560 :envvar:`ARGVALIDATE_WARN_KWARG_AS_ARG`. 0561 """ 0562 return argvalidate_kwarg_as_arg 0563 0564 # Used for readbility, using None alone would be sufficient 0565 type_any = None