def make_validator_callable(validation_callable,  # type: ValidationFunc
                                    help_msg=None,        # type: str
                                    failure_type=None,    # type: Type[ValidationFailure]
                                    **kw_context_args):
            """

            :param validation_callable:
            :param help_msg: custom help message for failures to raise
            :param failure_type: type of failures to raise
            :param kw_context_args: contextual arguments for failures to raise
            :return:
            """
            if is_mini_lambda(validation_callable):
                validation_callable = validation_callable.as_function()

            # support several cases for the validation function signature
            # `f(val)`, `f(obj, val)` or `f(obj, field, val)`
            # the validation function has two or three (or more but optional) arguments.
            # valid8 requires only 1.
            try:
                args, varargs, varkwargs, defaults = v8_getfullargspec(validation_callable, skip_bound_arg=True)[0:4]

                nb_args = len(args) if args is not None else 0
                nbvarargs = 1 if varargs is not None else 0
                # nbkwargs = 1 if varkwargs is not None else 0
                # nbdefaults = len(defaults) if defaults is not None else 0
            except IsBuiltInError:
                # built-ins: TypeError: <built-in function isinstance> is not a Python function
                # assume signature with a single positional argument
                nb_args = 1
                nbvarargs = 0
                # nbkwargs = 0
                # nbdefaults = 0

            if nb_args == 0 and nbvarargs == 0:
                raise ValueError(
                    "validation function should accept 1, 2, or 3 arguments at least. `f(val)`, `f(obj, val)` or "
                    "`f(obj, field, val)`")
            elif nb_args == 1 or (nb_args == 0 and nbvarargs >= 1):  # varargs default to one argument (compliance with old mini lambda)
                # `f(val)`
                def new_validation_callable(val, **ctx):
                    return validation_callable(val)
            elif nb_args == 2:
                # `f(obj, val)`
                def new_validation_callable(val, **ctx):
                    return validation_callable(ctx['obj'], val)
            else:
                # `f(obj, field, val, *opt_args, **ctx)`
                def new_validation_callable(val, **ctx):
                    # note: field is available both from **ctx and self. Use the "fastest" way
                    return validation_callable(ctx['obj'], self.validated_field, val)

            # preserve the name
            new_validation_callable.__name__ = get_callable_name(validation_callable)

            return failure_raiser(new_validation_callable, help_msg=help_msg, failure_type=failure_type,
                                  **kw_context_args)
Beispiel #2
0
    def create_from_fun(
        cls,
        converter_fun,  # type: ConverterFuncOrLambda
        validation_fun=None  # type: ValidationFuncOrLambda
    ):
        # type: (...) -> Converter
        """
        Creates an instance of `Converter` where the `accepts` method is bound to the provided `validation_fun` and the
        `convert` method bound to the provided `converter_fun`.

        If these methods have less than 3 parameters, the mapping is done acccordingly.

        :param converter_fun:
        :param validation_fun:
        :return:
        """
        # Mandatory conversion callable
        if is_mini_lambda(converter_fun):
            is_mini = True
            converter_fun = converter_fun.as_function()
        else:
            is_mini = False
        converter_fun_3params = make_3params_callable(converter_fun,
                                                      is_mini_lambda=is_mini)

        # Optional acceptance callable
        if validation_fun is not None:
            if is_mini_lambda(validation_fun):
                is_mini = True
                validation_fun = validation_fun.as_function()
            else:
                is_mini = False
            validation_fun_3params = make_3params_callable(
                validation_fun, is_mini_lambda=is_mini)
        else:
            validation_fun_3params = None

        # Finally create the converter instance
        return ConverterWithFuncs(name=converter_fun_3params.__name__,
                                  accepts_fun=validation_fun_3params,
                                  convert_fun=converter_fun_3params)
def make_converter(converter_def  # type: ConverterFuncDefinition
                   ):
    # type: (...) -> Converter
    """
    Makes a `Converter` from the provided converter object. Supported formats:

     - a `Converter`
     - a `<conversion_callable>` with possible signatures `(value)`, `(obj, value)`, `(obj, field, value)`.
     - a tuple `(<validation_callable>, <conversion_callable>)`
     - a tuple `(<valid_type>, <conversion_callable>)`

    If no name is provided and a `<conversion_callable>` is present, the callable name will be used as the converter
    name.

    The name of the conversion callable will be used in that case

    :param converter_def:
    :return:
    """
    try:
        nb_elts = len(converter_def)
    except (TypeError, FunctionDefinitionError):
        # -- single element
        # handle the special case of a LambdaExpression: automatically convert to a function
        if not is_mini_lambda(converter_def):
            if isinstance(converter_def, Converter):
                # already a converter
                return converter_def
            elif not callable(converter_def):
                raise ValueError('base converter function(s) not compliant with the allowed syntax. Base validation'
                                 ' function(s) can be %s Found %s.' % (supported_syntax, converter_def))
        # single element.
        return Converter.create_from_fun(converter_def)
    else:
        # -- a tuple
        if nb_elts == 1:
            converter_fun, validation_fun = converter_def[0], None
        elif nb_elts == 2:
            validation_fun, converter_fun = converter_def
            if validation_fun is not None:
                if isinstance(validation_fun, type):
                    # a type can be provided to denote accept "instances of <type>"
                    validation_fun = instance_of(validation_fun)
                elif validation_fun == JOKER_STR:
                    validation_fun = None
                else:
                    if not is_mini_lambda(validation_fun) and not callable(validation_fun):
                        raise ValueError('base converter function(s) not compliant with the allowed syntax. Validator '
                                         'is incorrect. Base converter function(s) can be %s Found %s.'
                                         % (supported_syntax, converter_def))
        else:
            raise ValueError(
                'tuple in converter_fun definition should have length 1, or 2. Found: %s' % (converter_def,))

        # check that the definition is valid
        if not is_mini_lambda(converter_fun) and not callable(converter_fun):
            raise ValueError('base converter function(s) not compliant with the allowed syntax. Base converter'
                             ' function(s) can be %s Found %s.' % (supported_syntax, converter_def))

        # finally create the failure raising callable
        return Converter.create_from_fun(converter_fun, validation_fun)
Beispiel #4
0
def make_validation_func_callable(
    vf_definition,  # type: ValidationFuncDefinition
    callable_creator=failure_raiser  # type: Callable
):
    # type: (...) -> ValidationCallable
    """
    Creates a validation callable for usage in valid8, from a "validation function" callable optionally completed with
    an associated error message and failure type to be used in case validation fails.

    If `vf_definition` is a single <validation_func> callable, a failure raiser is created around it

    >>> import sys, pytest
    >>> if sys.version_info < (3, 0):
    ...     pytest.skip('doctest skipped in python 2 because exception namespace is different but details matter')

    >>> def vf(x): return x + 1 == 0
    >>> assert make_validation_func_callable(vf) is not vf  # TODO better assert here ?

    If `vf_definition` is a tuple such as (<validation_func>, <err_msg>), (<validation_func>, <failure_type>),
    or (<validation_func>, <err_msg>, <failure_type>), a `failure_raiser` is created.

    >>> class MyFailure(ValidationFailure):
    ...     pass
    >>> vf_with_details = make_validation_func_callable((vf, 'blah', MyFailure))
    >>> vf_with_details('hello')
    Traceback (most recent call last):
    ...
    valid8.common_syntax.MyFailure: blah. Function [vf] raised TypeError: ...

    Notes:

     - `<validation_func>` should be a `callable` with one argument: `f(val)`. It should return `True` or `None`
       in case of success. If it is a mini-lambda expression it will automatically be transformed into a function using
       `mini_lambda.as_function`. See `ValidationCallable` type hint.
     - `<err_msg>` should be a string
     - `<failure_type>` should be a subclass of `ValidationFailure`

    :param vf_definition: the definition for a validation function. One of <validation_func>,
        (<validation_func>, <err_msg>), (<validation_func>, <err_type>), or (<validation_func>, <err_msg>, <err_type>)
        where <validation_func> is a callable taking a single input and returning `True` or `None` in case of success.
        mini-lambda expressions are supported too and automatically converted into callables.
    :param callable_creator: method to be called to finally create the callable. Can be used by extensions to specify
        a custom way to create callables. Default is `failure_raiser`.
    :return: a validation callable that is either directly the provided callable, or a `failure_raiser` wrapping this
        callable using the additional details (err_msg, failure_type) provided.
    """
    try:
        nb_elts = len(vf_definition)
    except (TypeError, FunctionDefinitionError):
        # -- single element
        # handle the special case of a LambdaExpression: automatically convert to a function
        if not is_mini_lambda(vf_definition) and not callable(vf_definition):
            raise ValueError(
                'base validation function(s) not compliant with the allowed syntax. Base validation'
                ' function(s) can be %s Found %s.' %
                (supported_syntax, vf_definition))
        else:
            # single element.
            return callable_creator(vf_definition)
    else:
        # -- a tuple
        if nb_elts == 1:
            validation_func, help_msg, failure_type = vf_definition[
                0], None, None
        elif nb_elts == 2:
            if isinstance(vf_definition[1], str):
                validation_func, help_msg, failure_type = vf_definition[
                    0], vf_definition[1], None
            else:
                validation_func, help_msg, failure_type = vf_definition[
                    0], None, vf_definition[1]
        elif nb_elts == 3:
            validation_func, help_msg, failure_type = vf_definition[:]
        else:
            raise ValueError(
                'tuple in validator definition should have length 1, 2, or 3. Found: %s'
                % (vf_definition, ))

        # check help msg and failure type
        if failure_type is not None:
            failure_type_ok = False
            # noinspection PyBroadException
            try:
                # noinspection PyTypeChecker
                if issubclass(failure_type, ValidationFailure):
                    failure_type_ok = True
            except:  # noqa: E722
                pass
        else:
            failure_type_ok = True

        if help_msg is not None:
            help_msg_ok = False
            try:
                if isinstance(help_msg, str):
                    help_msg_ok = True
            except:
                pass
        else:
            help_msg_ok = True

        # check that the definition is valid
        if (not failure_type_ok) or (
                not help_msg_ok) or (not is_mini_lambda(validation_func)
                                     and not callable(validation_func)):
            raise ValueError(
                'base validation function(s) not compliant with the allowed syntax. Base validation'
                ' function(s) can be %s Found %s.' %
                (supported_syntax, vf_definition))

        # finally create the failure raising callable
        return callable_creator(validation_func,
                                help_msg=help_msg,
                                failure_type=failure_type)