#!/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)