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)
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)
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)