Source code for xotl.tools.validators

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ---------------------------------------------------------------------
# Copyright (c) Merchise Autrement [~º/~] and Contributors
# All rights reserved.
#
# This is free software; you can do what the LICENCE file allows you to.
#

"""Some generic value validators and regular expressions and validation
functions for several identifiers.

"""


# TODO: Check next import, it looks like one of the modules must be deprecated
from xotl.tools.validators.identifiers import (  # noqa
    check_identifier,
    is_valid_full_identifier,
    is_valid_identifier,
    is_valid_public_identifier,
    is_valid_slug,
)


def _adorn_checker_name(name):
    """Make more attractive or legible a checker name."""
    res = name.replace("_AND_", " & ")
    res = res.replace("_OR_", " | ")
    return res.replace("<lambda>", "<λ>")


def _get_checker_name(checker):
    """Return a nice name for a `checker`.

    A `checker` could be a type, a tuple of types, a callable or a list of
    other checkers.

    """
    l = lambda o: str("(%s)" % o.join(_get_checker_name(c) for c in checker))
    if isinstance(checker, list):
        return l("_AND_")
    elif isinstance(checker, tuple):
        return l("_OR_")
    else:
        from xotl.tools.future.inspect import safe_name

        res = safe_name(checker, affirm=True)
        if not isinstance(checker, type):
            assert callable(checker)
            if "lambda" in res:
                from inspect import getargspec

                args = getargspec(checker).args
                assert len(args) == 1
                res = str("%s(%s)" % (res, args[0]))
        return res


[docs]def is_type(cls): """Return a validator with the same name as the type given as argument `value`. :param cls: Class or type or tuple of several types. """ def inner(obj): """Check is a value object is a valid instance of (%s).""" return isinstance(obj, cls) name = _get_checker_name(cls) inner.__name__ = name inner.__doc__ = inner.__doc__ % name return inner
# TODO: With this new function, `is_type` could be deprecated # TODO: Migrate to a class
[docs]def predicate(*checkers, **kwargs): """Return a validation checker for types and simple conditions. :param checkers: A variable number of checkers; each one could be: - A type, or tuple of types, to test valid values with ``isinstance(value, checker)`` - A set or mapping of valid values, the value is valid if contained in the checker. - A tuple of other inner checkers, if any of the checkers validates a value, the value is valid (OR). - A list of other inner checkers, all checkers must validate the value (AND). - A callable that receives the value and returns True if the value is valid. - ``True`` and ``False`` could be used as checkers always validating or invalidating the value. An empty list or no checker is synonym of ``True``, an empty tuple, set or mapping is synonym of ``False``. :param name: Keyword argument to be used in case of error; will be the argument of `ValueError` exception; could contain the placeholders ``{value}`` and ``{type}``; a default value is used if this argument is not given. :param force_name: Keyword argument to force a name if not given. In order to obtain good documentations, use proper names for functions and lambda arguments. With this function could be built real type checkers, for example:: >>> is_valid_age = predicate((int, float), lambda age: 0 < age <= 120) >>> is_valid_age(100) True >>> is_valid_age(130) False >>> always_true = predicate(True) >>> always_true(False) True >>> always_false = predicate(False) >>> always_false(True) False >>> always_true = predicate() >>> always_true(1) True >>> always_true('any string') True >>> always_false = predicate(()) >>> always_false(1) False >>> always_false('any string') False """ from xotl.tools.future.collections import Mapping, Set from xotl.tools.symbols import boolean def inner(obj): """Check is `obj` is a valid instance for a set of checkers.""" def valid(chk): if isinstance(chk, boolean): res = bool(chk) elif isinstance(chk, type): res = isinstance(obj, chk) elif isinstance(chk, tuple): if all(isinstance(c, type) for c in chk): res = isinstance(obj, chk) else: res = any(valid(c) for c in chk) elif isinstance(chk, list): res = all(valid(c) for c in chk) elif isinstance(chk, (Set, Mapping)): res = obj in chk else: res = chk(obj) return res # XXX: WTF, must be ``all(valid(chk) for chk in checkers)`` return next((chk for chk in checkers if not valid(chk)), None) is None name = kwargs.get("name") if name is None and kwargs.get("force_name"): name = _get_checker_name(list(checkers)) if name is not None: inner.__name__ = name return inner
[docs]def check(value, validator, msg=None): """Check a `value` with a `validator`. Argument `validator` could be a callable, a type, or a tuple of types. Return True if the value is valid. Examples:: >>> check(1, int) True >>> check(10, lambda x: x <= 100, 'must be less than or equal to 100') True >>> check(11/2, (int, float)) True """ if isinstance(validator, (type, tuple)): checker = is_type(validator) else: checker = validator if checker(value): return True else: from xotl.tools.future.inspect import safe_name if not msg: # TODO: Use the name of validator with `inspect.getattr_static` # when `xotl.tools.future` is ready msg = 'Invalid value "%s" of type "%s"' msg = msg.format(value=value, type=safe_name(value, affirm=True)) raise ValueError(msg)
# TODO: deprecate `check` in favor of `ok`.
[docs]def ok(value, *checkers, **kwargs): """Validate a value with several checkers. Return the value if it is Ok, or raises an `ValueError` exception if not. Arguments: :param value: the value to validate :param checkers: a variable number of checkers (at least one), each one could be a type, a tuple of types of a callable that receives the value and returns if the value is valid or not. In order the value is considered valid, all checkers must validate the value. :param message: keyword argument to be used in case of error; will be the argument of `ValueError` exception; could contain the placeholders ``{value}`` and ``{type}``; a default value is used if this argument is not given. :param msg: an alias for "message" :param extra_checkers: In order to create validators using `partial`. Must be a tuple. Keyword arguments are not validated to be correct. This function could be used with type-definitions for arguments, see `xotl.tools.fp.prove.semantic.TypeCheck`:class:. Examples:: >>> ok(1, int) 1 >>> ok(10, int, lambda x: x < 100, message='Must be integer under 100') 10 >>> ok(11/2, (int, float)) 5.5 >>> ok(11/2, int, float) 5.5 >>> try: ... res = ok(11/2, int) ... except ValueError: ... res = '---' >>> res '---' """ extra_checkers = kwargs.get("extra_checkers", ()) pred = predicate(*(checkers + extra_checkers)) if pred(value): return value else: from xotl.tools.future.inspect import safe_name from xotl.tools.future.itertools import multi_get as get msg = next(get(kwargs, "message", "msg"), "Invalid {type}: {value}!") msg = msg.format(value=value, type=safe_name(value, affirm=True)) raise ValueError(msg)
[docs]def check_no_extra_kwargs(kwargs): """Check that no extra keyword arguments are still not processed. For example:: >>> from xotl.tools.validators import check_no_extra_kwargs >>> def only_safe_arg(**kwargs): ... safe = kwargs.pop('safe', False) ... check_no_extra_kwargs(kwargs) ... print('OK for safe:', safe) """ if kwargs: plural = "" if len(kwargs) == 1 else "s" msg = 'Unexpected keyword argument%s: "%s"!' raise TypeError(msg % (plural, ", ".join(kwargs)))