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