Advertisement
Guest User

Untitled

a guest
Jul 1st, 2015
155
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.04 KB | None | 0 0
  1. from collections import Callable
  2. from types import MethodType
  3. import functools
  4. import sys
  5. if sys.version_info < (3, 0, 0):
  6. from inspect import getargspec
  7. else:
  8. from inspect import getfullargspec as getargspec
  9. from pyvalid.__exceptions import InvalidArgumentNumberError, \
  10. ArgumentValidationError
  11. from pyvalid.switch import is_enabled
  12.  
  13.  
  14. class Accepts(Callable):
  15. """A decorator to validate types of input parameters for a given function.
  16. """
  17.  
  18. def __init__(self, *accepted_arg_values, **accepted_kwargs_values):
  19. self.accepted_arg_values = accepted_arg_values
  20. self.accepted_kwargs_values = accepted_kwargs_values
  21. self.accepted_args = list()
  22. self.optional_args = list()
  23.  
  24. def __call__(self, func):
  25. @functools.wraps(func)
  26. def decorator_wrapper(*func_args, **func_kwargs):
  27. perform_validation = all((
  28. is_enabled(),
  29. self.accepted_arg_values or self.accepted_kwargs_values
  30. ))
  31. if perform_validation:
  32. # Forget all information about function arguments.
  33. self.accepted_args[:] = list()
  34. self.optional_args[:] = list()
  35. # Collect information about fresh arguments.
  36. args_info = getargspec(func)
  37. self.__scan_func(args_info)
  38. # Validate function arguments.
  39. self.__validate_args(func.__name__, func_args, func_kwargs)
  40. # Call function.
  41. return func(*func_args, **func_kwargs)
  42. return decorator_wrapper
  43.  
  44. def __wrap_accepted_val(self, value):
  45. """Wrap accepted value in the list if yet not wrapped.
  46. """
  47. if isinstance(value, tuple):
  48. value = list(value)
  49. elif not isinstance(value, list):
  50. value = [value]
  51. return value
  52.  
  53. def __scan_func(self, args_info):
  54. """Collect information about accepted arguments in following format:
  55. (
  56. (<argument name>, <accepted types and values>),
  57. (<argument name>, <accepted types and values>),
  58. ...
  59. )
  60. Args:
  61. args_info (inspect.FullArgSpec): Information about function
  62. arguments.
  63. """
  64. # Process args.
  65. for i, accepted_arg_vals in enumerate(self.accepted_arg_values):
  66. # Wrap each accepted value in the list if yet not wrapped.
  67. accepted_arg_vals = self.__wrap_accepted_val(accepted_arg_vals)
  68. # Add default value (if exists) in list of accepted values.
  69. if args_info.defaults:
  70. def_range = len(args_info.defaults) - len(args_info.args[i:])
  71. if def_range >= 0:
  72. self.optional_args.append(i)
  73. accepted_value = args_info.defaults[def_range]
  74. accepted_arg_vals.append(accepted_value)
  75. # Try to detect current argument name.
  76. if len(args_info.args) > i:
  77. arg_name = args_info.args[i]
  78. else:
  79. arg_name = None
  80. self.optional_args.append(i)
  81. # Save info about current argument and his accepted values.
  82. self.accepted_args.append((arg_name, accepted_arg_vals))
  83. # Process kwargs.
  84. for arg_name, accepted_arg_vals in self.accepted_kwargs_values.items():
  85. # Wrap each accepted value in the list if yet not wrapped.
  86. accepted_arg_vals = self.__wrap_accepted_val(accepted_arg_vals)
  87. # Mark current argument as optional.
  88. i = len(self.accepted_args)
  89. self.optional_args.append(i)
  90. # Save info about current argument and his accepted values.
  91. self.accepted_args.append((arg_name, accepted_arg_vals))
  92.  
  93. def __validate_args(self, func_name, args, kwargs):
  94. """Compare value of each required argument with list of
  95. accepted values.
  96. Args:
  97. func_name (str): Function name.
  98. args (list): Collection of the position arguments.
  99. kwargs (dict): Collection of the keyword arguments.
  100. Raises:
  101. InvalidArgumentNumberError: When position or count of the arguments
  102. is incorrect.
  103. ArgumentValidationError: When encountered unexpected argument
  104. value.
  105. """
  106. from pyvalid.validators import Validator
  107. for i, (arg_name, accepted_values) in enumerate(self.accepted_args):
  108. if i < len(args):
  109. value = args[i]
  110. else:
  111. if arg_name in kwargs:
  112. value = kwargs[arg_name]
  113. elif i in self.optional_args:
  114. continue
  115. else:
  116. raise InvalidArgumentNumberError(func_name)
  117. is_valid = False
  118. for accepted_val in accepted_values:
  119. is_validator = (
  120. isinstance(accepted_val, Validator) or
  121. (
  122. isinstance(accepted_val, MethodType) and
  123. hasattr(accepted_val, '__func__') and
  124. isinstance(accepted_val.__func__, Validator)
  125. )
  126. )
  127. if is_validator:
  128. is_valid = accepted_val(value)
  129. elif isinstance(accepted_val, type):
  130. is_valid = isinstance(value, accepted_val)
  131. else:
  132. is_valid = value == accepted_val
  133. if is_valid:
  134. break
  135. if not is_valid:
  136. ord_num = self.__ordinal(i + 1)
  137. raise ArgumentValidationError(
  138. ord_num,
  139. func_name,
  140. accepted_values
  141. )
  142.  
  143. def __ordinal(self, num):
  144. """Returns the ordinal number of a given integer, as a string.
  145. eg. 1 -> 1st, 2 -> 2nd, 3 -> 3rd, etc.
  146. """
  147. if 10 <= num % 100 < 20:
  148. return str(num) + 'th'
  149. else:
  150. ord_info = {1: 'st', 2: 'nd', 3: 'rd'}.get(num % 10, 'th')
  151. return '{}{}'.format(num, ord_info)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement