Module pedantic.decorators.fn_deco_validate.fn_deco_validate
Expand source code
import inspect
from enum import Enum
from functools import wraps
from typing import Any, Callable, List, Dict
from pedantic.decorators.fn_deco_validate.exceptions import ValidateException, TooManyArguments
from pedantic.decorators.fn_deco_validate.parameters import Parameter, ExternalParameter
from pedantic.decorators.fn_deco_validate.parameters.abstract_parameter import NoValue
try:
from flask import request
from pedantic.decorators.fn_deco_validate.parameters.flask_parameters import FlaskJsonParameter
IS_FLASK_INSTALLED = True
except ImportError:
IS_FLASK_INSTALLED = False
class ReturnAs(Enum):
ARGS = 'ARGS'
KWARGS_WITH_NONE = 'KWARGS_WITH_NONE'
KWARGS_WITHOUT_NONE = 'KWARGS_WITHOUT_NONE'
def validate(
*parameters: Parameter,
return_as: ReturnAs = ReturnAs.ARGS,
strict: bool = True,
ignore_input: bool = False,
) -> Callable:
"""
Validates the values that are passed to the function by using the validators in the given parameters.
The decorated function could also be async or an instance method as well as a normal function.
Args:
parameters (multiple Parameter): The parameters that will be validated.
return_as (ReturnAs): Pass the arguments as kwargs to the decorated function if ReturnAs.KWARGS.
Positional arguments are used otherwise.
strict (bool): If strict is true, you have to define a Parameter for each of the
arguments the decorated function takes.
ignore_input (bool): If True, all given arguments passed to this decorator are ignored.
This can be useful if you use only ExternalParameters.
Returns:
Callable: The decorated function.
"""
def validator(func: Callable) -> Callable:
is_coroutine = inspect.iscoroutinefunction(func)
@wraps(func)
def wrapper(*args, **kwargs) -> Any:
result = _wrapper_content(*args, **kwargs)
if return_as == ReturnAs.ARGS:
if 'self' in result:
return func(result.pop('self'), **result)
return func(*result.values())
if return_as == ReturnAs.KWARGS_WITHOUT_NONE:
result = {k: v for k, v in result.items() if v is not None}
if 'self' in result:
return func(result.pop('self'), **result)
return func(**result)
@wraps(func)
async def async_wrapper(*args, **kwargs) -> Any:
result = _wrapper_content(*args, **kwargs)
if return_as == ReturnAs.ARGS:
if 'self' in result:
return await func(result.pop('self'), **result)
return await func(*result.values())
if return_as == ReturnAs.KWARGS_WITHOUT_NONE:
result = {k: v for k, v in result.items() if v is not None}
if 'self' in result:
return await func(result.pop('self'), **result)
return await func(**result)
def _wrapper_content(*args, **kwargs) -> Dict[str, Any]:
result = {}
parameter_dict = {parameter.name: parameter for parameter in parameters}
used_parameter_names: List[str] = []
signature = inspect.signature(func)
if not ignore_input:
for k, v in kwargs.items():
if k in parameter_dict:
parameter = parameter_dict[k]
result[k] = parameter.validate(value=v)
used_parameter_names.append(parameter.name)
else:
if strict:
raise TooManyArguments(f'Got more arguments expected: No parameter found for argument {k}')
else:
result[k] = v
wants_args = '*args' in str(signature)
used_args = []
try:
bound_args = signature.bind_partial(*args).arguments
except TypeError as ex:
raise ValidateException(str(ex))
for k in bound_args:
if k == 'args' and wants_args:
for arg, parameter in zip(
[a for a in args if a not in used_args],
[p for p in parameters if p.name not in used_parameter_names]
):
print(f'Validate value {arg} with {parameter}')
result[parameter.name] = parameter.validate(arg)
used_parameter_names.append(parameter.name)
elif k in parameter_dict:
parameter = parameter_dict[k]
result[k] = parameter.validate(value=bound_args[k])
used_parameter_names.append(parameter.name)
used_args.append(bound_args[k])
else:
if strict and k != 'self':
raise TooManyArguments(f'Got more arguments expected: No parameter found for argument {k}')
else:
result[k] = bound_args[k]
unused_parameters = [parameter for parameter in parameters if parameter.name not in used_parameter_names]
for parameter in unused_parameters:
if isinstance(parameter, ExternalParameter):
if parameter.has_value():
v = parameter.load_value()
result[parameter.name] = parameter.validate(value=v)
continue
if parameter.is_required:
return parameter.raise_exception(msg=f'Value for parameter {parameter.name} is required.')
elif parameter.default_value == NoValue:
if parameter.name in signature.parameters and \
signature.parameters[parameter.name].default is not signature.empty:
value = signature.parameters[parameter.name].default
else:
raise ValidateException(f'Got neither value nor default value for parameter {parameter.name}')
else:
value = parameter.default_value
result[parameter.name] = value
# this is ugly, but I really want this behavior
if strict and IS_FLASK_INSTALLED:
if all([isinstance(p, FlaskJsonParameter) for p in parameter_dict.values()]) and request.is_json:
unexpected_args = [k for k in request.json if k not in parameter_dict]
if unexpected_args:
raise TooManyArguments(f'Got unexpected arguments: {unexpected_args}')
return result
if is_coroutine:
return async_wrapper
else:
return wrapper
return validator
Functions
def validate(*parameters: Parameter, return_as: ReturnAs = ReturnAs.ARGS, strict: bool = True, ignore_input: bool = False) ‑> Callable
-
Validates the values that are passed to the function by using the validators in the given parameters. The decorated function could also be async or an instance method as well as a normal function.
Args
parameters
:multiple Parameter
- The parameters that will be validated.
return_as
:ReturnAs
- Pass the arguments as kwargs to the decorated function if ReturnAs.KWARGS. Positional arguments are used otherwise.
strict
:bool
- If strict is true, you have to define a Parameter for each of the arguments the decorated function takes.
ignore_input
:bool
- If True, all given arguments passed to this decorator are ignored. This can be useful if you use only ExternalParameters.
Returns
Callable
- The decorated function.
Expand source code
def validate( *parameters: Parameter, return_as: ReturnAs = ReturnAs.ARGS, strict: bool = True, ignore_input: bool = False, ) -> Callable: """ Validates the values that are passed to the function by using the validators in the given parameters. The decorated function could also be async or an instance method as well as a normal function. Args: parameters (multiple Parameter): The parameters that will be validated. return_as (ReturnAs): Pass the arguments as kwargs to the decorated function if ReturnAs.KWARGS. Positional arguments are used otherwise. strict (bool): If strict is true, you have to define a Parameter for each of the arguments the decorated function takes. ignore_input (bool): If True, all given arguments passed to this decorator are ignored. This can be useful if you use only ExternalParameters. Returns: Callable: The decorated function. """ def validator(func: Callable) -> Callable: is_coroutine = inspect.iscoroutinefunction(func) @wraps(func) def wrapper(*args, **kwargs) -> Any: result = _wrapper_content(*args, **kwargs) if return_as == ReturnAs.ARGS: if 'self' in result: return func(result.pop('self'), **result) return func(*result.values()) if return_as == ReturnAs.KWARGS_WITHOUT_NONE: result = {k: v for k, v in result.items() if v is not None} if 'self' in result: return func(result.pop('self'), **result) return func(**result) @wraps(func) async def async_wrapper(*args, **kwargs) -> Any: result = _wrapper_content(*args, **kwargs) if return_as == ReturnAs.ARGS: if 'self' in result: return await func(result.pop('self'), **result) return await func(*result.values()) if return_as == ReturnAs.KWARGS_WITHOUT_NONE: result = {k: v for k, v in result.items() if v is not None} if 'self' in result: return await func(result.pop('self'), **result) return await func(**result) def _wrapper_content(*args, **kwargs) -> Dict[str, Any]: result = {} parameter_dict = {parameter.name: parameter for parameter in parameters} used_parameter_names: List[str] = [] signature = inspect.signature(func) if not ignore_input: for k, v in kwargs.items(): if k in parameter_dict: parameter = parameter_dict[k] result[k] = parameter.validate(value=v) used_parameter_names.append(parameter.name) else: if strict: raise TooManyArguments(f'Got more arguments expected: No parameter found for argument {k}') else: result[k] = v wants_args = '*args' in str(signature) used_args = [] try: bound_args = signature.bind_partial(*args).arguments except TypeError as ex: raise ValidateException(str(ex)) for k in bound_args: if k == 'args' and wants_args: for arg, parameter in zip( [a for a in args if a not in used_args], [p for p in parameters if p.name not in used_parameter_names] ): print(f'Validate value {arg} with {parameter}') result[parameter.name] = parameter.validate(arg) used_parameter_names.append(parameter.name) elif k in parameter_dict: parameter = parameter_dict[k] result[k] = parameter.validate(value=bound_args[k]) used_parameter_names.append(parameter.name) used_args.append(bound_args[k]) else: if strict and k != 'self': raise TooManyArguments(f'Got more arguments expected: No parameter found for argument {k}') else: result[k] = bound_args[k] unused_parameters = [parameter for parameter in parameters if parameter.name not in used_parameter_names] for parameter in unused_parameters: if isinstance(parameter, ExternalParameter): if parameter.has_value(): v = parameter.load_value() result[parameter.name] = parameter.validate(value=v) continue if parameter.is_required: return parameter.raise_exception(msg=f'Value for parameter {parameter.name} is required.') elif parameter.default_value == NoValue: if parameter.name in signature.parameters and \ signature.parameters[parameter.name].default is not signature.empty: value = signature.parameters[parameter.name].default else: raise ValidateException(f'Got neither value nor default value for parameter {parameter.name}') else: value = parameter.default_value result[parameter.name] = value # this is ugly, but I really want this behavior if strict and IS_FLASK_INSTALLED: if all([isinstance(p, FlaskJsonParameter) for p in parameter_dict.values()]) and request.is_json: unexpected_args = [k for k in request.json if k not in parameter_dict] if unexpected_args: raise TooManyArguments(f'Got unexpected arguments: {unexpected_args}') return result if is_coroutine: return async_wrapper else: return wrapper return validator
Classes
class ReturnAs (*args, **kwds)
-
Create a collection of name/value pairs.
Example enumeration:
>>> class Color(Enum): ... RED = 1 ... BLUE = 2 ... GREEN = 3
Access them by:
- attribute access::
>>> Color.RED <Color.RED: 1>
- value lookup:
>>> Color(1) <Color.RED: 1>
- name lookup:
>>> Color['RED'] <Color.RED: 1>
Enumerations can be iterated over, and know how many members they have:
>>> len(Color) 3
>>> list(Color) [<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]
Methods can be added to enumerations, and members can have their own attributes – see the documentation for details.
Expand source code
class ReturnAs(Enum): ARGS = 'ARGS' KWARGS_WITH_NONE = 'KWARGS_WITH_NONE' KWARGS_WITHOUT_NONE = 'KWARGS_WITHOUT_NONE'
Ancestors
- enum.Enum
Class variables
var ARGS
var KWARGS_WITHOUT_NONE
var KWARGS_WITH_NONE