Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
validators.py751 linesDownload Raw Back to attr
1# SPDX-License-Identifier: MIT2 3"""4Commonly useful validators.5"""6 7import operator8import re9 10from contextlib import contextmanager11from re import Pattern12 13from ._config import get_run_validators, set_run_validators14from ._make import _AndValidator, and_, attrib, attrs15from .converters import default_if_none16from .exceptions import NotCallableError17 18 19__all__ = [20    "and_",21    "deep_iterable",22    "deep_mapping",23    "disabled",24    "ge",25    "get_disabled",26    "gt",27    "in_",28    "instance_of",29    "is_callable",30    "le",31    "lt",32    "matches_re",33    "max_len",34    "min_len",35    "not_",36    "optional",37    "or_",38    "set_disabled",39]40 41 42def set_disabled(disabled):43    """44    Globally disable or enable running validators.45 46    By default, they are run.47 48    Args:49        disabled (bool): If `True`, disable running all validators.50 51    .. warning::52 53        This function is not thread-safe!54 55    .. versionadded:: 21.3.056    """57    set_run_validators(not disabled)58 59 60def get_disabled():61    """62    Return a bool indicating whether validators are currently disabled or not.63 64    Returns:65        bool:`True` if validators are currently disabled.66 67    .. versionadded:: 21.3.068    """69    return not get_run_validators()70 71 72@contextmanager73def disabled():74    """75    Context manager that disables running validators within its context.76 77    .. warning::78 79        This context manager is not thread-safe!80 81    .. versionadded:: 21.3.082    .. versionchanged:: 26.1.0 The contextmanager is nestable.83    """84    prev = get_run_validators()85    set_run_validators(False)86    try:87        yield88    finally:89        set_run_validators(prev)90 91 92@attrs(repr=False, slots=True, unsafe_hash=True)93class _InstanceOfValidator:94    type = attrib()95 96    def __call__(self, inst, attr, value):97        """98        We use a callable class to be able to change the ``__repr__``.99        """100        if not isinstance(value, self.type):101            msg = f"'{attr.name}' must be {self.type!r} (got {value!r} that is a {value.__class__!r})."102            raise TypeError(103                msg,104                attr,105                self.type,106                value,107            )108 109    def __repr__(self):110        return f"<instance_of validator for type {self.type!r}>"111 112 113def instance_of(type):114    """115    A validator that raises a `TypeError` if the initializer is called with a116    wrong type for this particular attribute (checks are performed using117    `isinstance` therefore it's also valid to pass a tuple of types).118 119    Args:120        type (type | tuple[type]): The type to check for.121 122    Raises:123        TypeError:124            With a human readable error message, the attribute (of type125            `attrs.Attribute`), the expected type, and the value it got.126    """127    return _InstanceOfValidator(type)128 129 130@attrs(repr=False, frozen=True, slots=True)131class _MatchesReValidator:132    pattern = attrib()133    match_func = attrib()134 135    def __call__(self, inst, attr, value):136        """137        We use a callable class to be able to change the ``__repr__``.138        """139        if not self.match_func(value):140            msg = f"'{attr.name}' must match regex {self.pattern.pattern!r} ({value!r} doesn't)"141            raise ValueError(142                msg,143                attr,144                self.pattern,145                value,146            )147 148    def __repr__(self):149        return f"<matches_re validator for pattern {self.pattern!r}>"150 151 152def matches_re(regex, flags=0, func=None):153    r"""154    A validator that raises `ValueError` if the initializer is called with a155    string that doesn't match *regex*.156 157    Args:158        regex (str, re.Pattern):159            A regex string or precompiled pattern to match against160 161        flags (int):162            Flags that will be passed to the underlying re function (default 0)163 164        func (typing.Callable):165            Which underlying `re` function to call. Valid options are166            `re.fullmatch`, `re.search`, and `re.match`; the default `None`167            means `re.fullmatch`. For performance reasons, the pattern is168            always precompiled using `re.compile`.169 170    .. versionadded:: 19.2.0171    .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern.172    """173    valid_funcs = (re.fullmatch, None, re.search, re.match)174    if func not in valid_funcs:175        msg = "'func' must be one of {}.".format(176            ", ".join(177                sorted((e and e.__name__) or "None" for e in set(valid_funcs))178            )179        )180        raise ValueError(msg)181 182    if isinstance(regex, Pattern):183        if flags:184            msg = "'flags' can only be used with a string pattern; pass flags to re.compile() instead"185            raise TypeError(msg)186        pattern = regex187    else:188        pattern = re.compile(regex, flags)189 190    if func is re.match:191        match_func = pattern.match192    elif func is re.search:193        match_func = pattern.search194    else:195        match_func = pattern.fullmatch196 197    return _MatchesReValidator(pattern, match_func)198 199 200@attrs(repr=False, slots=True, unsafe_hash=True)201class _OptionalValidator:202    validator = attrib()203 204    def __call__(self, inst, attr, value):205        if value is None:206            return207 208        self.validator(inst, attr, value)209 210    def __repr__(self):211        return f"<optional validator for {self.validator!r} or None>"212 213 214def optional(validator):215    """216    A validator that makes an attribute optional.  An optional attribute is one217    which can be set to `None` in addition to satisfying the requirements of218    the sub-validator.219 220    Args:221        validator222            (typing.Callable | tuple[typing.Callable] | list[typing.Callable]):223            A validator (or validators) that is used for non-`None` values.224 225    .. versionadded:: 15.1.0226    .. versionchanged:: 17.1.0 *validator* can be a list of validators.227    .. versionchanged:: 23.1.0 *validator* can also be a tuple of validators.228    """229    if isinstance(validator, (list, tuple)):230        return _OptionalValidator(_AndValidator(validator))231 232    return _OptionalValidator(validator)233 234 235@attrs(repr=False, slots=True, unsafe_hash=True)236class _InValidator:237    options = attrib()238    _original_options = attrib(hash=False)239 240    def __call__(self, inst, attr, value):241        try:242            in_options = value in self.options243        except TypeError:  # e.g. `1 in "abc"`244            in_options = False245 246        if not in_options:247            msg = f"'{attr.name}' must be in {self._original_options!r} (got {value!r})"248            raise ValueError(249                msg,250                attr,251                self._original_options,252                value,253            )254 255    def __repr__(self):256        return f"<in_ validator with options {self._original_options!r}>"257 258 259def in_(options):260    """261    A validator that raises a `ValueError` if the initializer is called with a262    value that does not belong in the *options* provided.263 264    The check is performed using ``value in options``, so *options* has to265    support that operation.266 267    To keep the validator hashable, dicts, lists, and sets are transparently268    transformed into a `tuple`.269 270    Args:271        options: Allowed options.272 273    Raises:274        ValueError:275            With a human readable error message, the attribute (of type276            `attrs.Attribute`), the expected options, and the value it got.277 278    .. versionadded:: 17.1.0279    .. versionchanged:: 22.1.0280       The ValueError was incomplete until now and only contained the human281       readable error message. Now it contains all the information that has282       been promised since 17.1.0.283    .. versionchanged:: 24.1.0284       *options* that are a list, dict, or a set are now transformed into a285       tuple to keep the validator hashable.286    """287    repr_options = options288    if isinstance(options, (list, dict, set)):289        options = tuple(options)290 291    return _InValidator(options, repr_options)292 293 294@attrs(repr=False, slots=False, unsafe_hash=True)295class _IsCallableValidator:296    def __call__(self, inst, attr, value):297        """298        We use a callable class to be able to change the ``__repr__``.299        """300        if not callable(value):301            message = (302                "'{name}' must be callable "303                "(got {value!r} that is a {actual!r})."304            )305            raise NotCallableError(306                msg=message.format(307                    name=attr.name, value=value, actual=value.__class__308                ),309                value=value,310            )311 312    def __repr__(self):313        return "<is_callable validator>"314 315 316def is_callable():317    """318    A validator that raises a `attrs.exceptions.NotCallableError` if the319    initializer is called with a value for this particular attribute that is320    not callable.321 322    .. versionadded:: 19.1.0323 324    Raises:325        attrs.exceptions.NotCallableError:326            With a human readable error message containing the attribute327            (`attrs.Attribute`) name, and the value it got.328    """329    return _IsCallableValidator()330 331 332@attrs(repr=False, slots=True, unsafe_hash=True)333class _DeepIterable:334    member_validator = attrib(validator=is_callable())335    iterable_validator = attrib(336        default=None, validator=optional(is_callable())337    )338 339    def __call__(self, inst, attr, value):340        """341        We use a callable class to be able to change the ``__repr__``.342        """343        if self.iterable_validator is not None:344            self.iterable_validator(inst, attr, value)345 346        for member in value:347            self.member_validator(inst, attr, member)348 349    def __repr__(self):350        iterable_identifier = (351            ""352            if self.iterable_validator is None353            else f" {self.iterable_validator!r}"354        )355        return (356            f"<deep_iterable validator for{iterable_identifier}"357            f" iterables of {self.member_validator!r}>"358        )359 360 361def deep_iterable(member_validator, iterable_validator=None):362    """363    A validator that performs deep validation of an iterable.364 365    Args:366        member_validator: Validator(s) to apply to iterable members.367 368        iterable_validator:369            Validator(s) to apply to iterable itself (optional).370 371    Raises372        TypeError: if any sub-validators fail373 374    .. versionadded:: 19.1.0375 376    .. versionchanged:: 25.4.0377       *member_validator* and *iterable_validator* can now be a list or tuple378       of validators.379    """380    if isinstance(member_validator, (list, tuple)):381        member_validator = and_(*member_validator)382    if isinstance(iterable_validator, (list, tuple)):383        iterable_validator = and_(*iterable_validator)384    return _DeepIterable(member_validator, iterable_validator)385 386 387@attrs(repr=False, slots=True, unsafe_hash=True)388class _DeepMapping:389    key_validator = attrib(validator=optional(is_callable()))390    value_validator = attrib(validator=optional(is_callable()))391    mapping_validator = attrib(validator=optional(is_callable()))392 393    def __call__(self, inst, attr, value):394        """395        We use a callable class to be able to change the ``__repr__``.396        """397        if self.mapping_validator is not None:398            self.mapping_validator(inst, attr, value)399 400        for key in value:401            if self.key_validator is not None:402                self.key_validator(inst, attr, key)403            if self.value_validator is not None:404                self.value_validator(inst, attr, value[key])405 406    def __repr__(self):407        return f"<deep_mapping validator for objects mapping {self.key_validator!r} to {self.value_validator!r}>"408 409 410def deep_mapping(411    key_validator=None, value_validator=None, mapping_validator=None412):413    """414    A validator that performs deep validation of a dictionary.415 416    All validators are optional, but at least one of *key_validator* or417    *value_validator* must be provided.418 419    Args:420        key_validator: Validator(s) to apply to dictionary keys.421 422        value_validator: Validator(s) to apply to dictionary values.423 424        mapping_validator:425            Validator(s) to apply to top-level mapping attribute.426 427    .. versionadded:: 19.1.0428 429    .. versionchanged:: 25.4.0430       *key_validator* and *value_validator* are now optional, but at least one431       of them must be provided.432 433    .. versionchanged:: 25.4.0434       *key_validator*, *value_validator*, and *mapping_validator* can now be a435       list or tuple of validators.436 437    Raises:438        TypeError: If any sub-validator fails on validation.439 440        ValueError:441            If neither *key_validator* nor *value_validator* is provided on442            instantiation.443    """444    if key_validator is None and value_validator is None:445        msg = (446            "At least one of key_validator or value_validator must be provided"447        )448        raise ValueError(msg)449 450    if isinstance(key_validator, (list, tuple)):451        key_validator = and_(*key_validator)452    if isinstance(value_validator, (list, tuple)):453        value_validator = and_(*value_validator)454    if isinstance(mapping_validator, (list, tuple)):455        mapping_validator = and_(*mapping_validator)456 457    return _DeepMapping(key_validator, value_validator, mapping_validator)458 459 460@attrs(repr=False, frozen=True, slots=True)461class _NumberValidator:462    bound = attrib()463    compare_op = attrib()464    compare_func = attrib()465 466    def __call__(self, inst, attr, value):467        """468        We use a callable class to be able to change the ``__repr__``.469        """470        if not self.compare_func(value, self.bound):471            msg = f"'{attr.name}' must be {self.compare_op} {self.bound}: {value}"472            raise ValueError(msg)473 474    def __repr__(self):475        return f"<Validator for x {self.compare_op} {self.bound}>"476 477 478def lt(val):479    """480    A validator that raises `ValueError` if the initializer is called with a481    number larger or equal to *val*.482 483    The validator uses `operator.lt` to compare the values.484 485    Args:486        val: Exclusive upper bound for values.487 488    .. versionadded:: 21.3.0489    """490    return _NumberValidator(val, "<", operator.lt)491 492 493def le(val):494    """495    A validator that raises `ValueError` if the initializer is called with a496    number greater than *val*.497 498    The validator uses `operator.le` to compare the values.499 500    Args:501        val: Inclusive upper bound for values.502 503    .. versionadded:: 21.3.0504    """505    return _NumberValidator(val, "<=", operator.le)506 507 508def ge(val):509    """510    A validator that raises `ValueError` if the initializer is called with a511    number smaller than *val*.512 513    The validator uses `operator.ge` to compare the values.514 515    Args:516        val: Inclusive lower bound for values517 518    .. versionadded:: 21.3.0519    """520    return _NumberValidator(val, ">=", operator.ge)521 522 523def gt(val):524    """525    A validator that raises `ValueError` if the initializer is called with a526    number smaller or equal to *val*.527 528    The validator uses `operator.gt` to compare the values.529 530    Args:531       val: Exclusive lower bound for values532 533    .. versionadded:: 21.3.0534    """535    return _NumberValidator(val, ">", operator.gt)536 537 538@attrs(repr=False, frozen=True, slots=True)539class _MaxLengthValidator:540    max_length = attrib()541 542    def __call__(self, inst, attr, value):543        """544        We use a callable class to be able to change the ``__repr__``.545        """546        if len(value) > self.max_length:547            msg = f"Length of '{attr.name}' must be <= {self.max_length}: {len(value)}"548            raise ValueError(msg)549 550    def __repr__(self):551        return f"<max_len validator for {self.max_length}>"552 553 554def max_len(length):555    """556    A validator that raises `ValueError` if the initializer is called557    with a string or iterable that is longer than *length*.558 559    Args:560        length (int): Maximum length of the string or iterable561 562    .. versionadded:: 21.3.0563    """564    return _MaxLengthValidator(length)565 566 567@attrs(repr=False, frozen=True, slots=True)568class _MinLengthValidator:569    min_length = attrib()570 571    def __call__(self, inst, attr, value):572        """573        We use a callable class to be able to change the ``__repr__``.574        """575        if len(value) < self.min_length:576            msg = f"Length of '{attr.name}' must be >= {self.min_length}: {len(value)}"577            raise ValueError(msg)578 579    def __repr__(self):580        return f"<min_len validator for {self.min_length}>"581 582 583def min_len(length):584    """585    A validator that raises `ValueError` if the initializer is called586    with a string or iterable that is shorter than *length*.587 588    Args:589        length (int): Minimum length of the string or iterable590 591    .. versionadded:: 22.1.0592    """593    return _MinLengthValidator(length)594 595 596@attrs(repr=False, slots=True, unsafe_hash=True)597class _SubclassOfValidator:598    type = attrib()599 600    def __call__(self, inst, attr, value):601        """602        We use a callable class to be able to change the ``__repr__``.603        """604        if not issubclass(value, self.type):605            msg = f"'{attr.name}' must be a subclass of {self.type!r} (got {value!r})."606            raise TypeError(607                msg,608                attr,609                self.type,610                value,611            )612 613    def __repr__(self):614        return f"<subclass_of validator for type {self.type!r}>"615 616 617def _subclass_of(type):618    """619    A validator that raises a `TypeError` if the initializer is called with a620    wrong type for this particular attribute (checks are performed using621    `issubclass` therefore it's also valid to pass a tuple of types).622 623    Args:624        type (type | tuple[type, ...]): The type(s) to check for.625 626    Raises:627        TypeError:628            With a human readable error message, the attribute (of type629            `attrs.Attribute`), the expected type, and the value it got.630    """631    return _SubclassOfValidator(type)632 633 634@attrs(repr=False, slots=True, unsafe_hash=True)635class _NotValidator:636    validator = attrib()637    msg = attrib(638        converter=default_if_none(639            "not_ validator child '{validator!r}' "640            "did not raise a captured error"641        )642    )643    exc_types = attrib(644        validator=deep_iterable(645            member_validator=_subclass_of(Exception),646            iterable_validator=instance_of(tuple),647        ),648    )649 650    def __call__(self, inst, attr, value):651        try:652            self.validator(inst, attr, value)653        except self.exc_types:654            pass  # suppress error to invert validity655        else:656            raise ValueError(657                self.msg.format(658                    validator=self.validator,659                    exc_types=self.exc_types,660                ),661                attr,662                self.validator,663                value,664                self.exc_types,665            )666 667    def __repr__(self):668        return f"<not_ validator wrapping {self.validator!r}, capturing {self.exc_types!r}>"669 670 671def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)):672    """673    A validator that wraps and logically 'inverts' the validator passed to it.674    It will raise a `ValueError` if the provided validator *doesn't* raise a675    `ValueError` or `TypeError` (by default), and will suppress the exception676    if the provided validator *does*.677 678    Intended to be used with existing validators to compose logic without679    needing to create inverted variants, for example, ``not_(in_(...))``.680 681    Args:682        validator: A validator to be logically inverted.683 684        msg (str):685            Message to raise if validator fails. Formatted with keys686            ``exc_types`` and ``validator``.687 688        exc_types (tuple[type, ...]):689            Exception type(s) to capture. Other types raised by child690            validators will not be intercepted and pass through.691 692    Raises:693        ValueError:694            With a human readable error message, the attribute (of type695            `attrs.Attribute`), the validator that failed to raise an696            exception, the value it got, and the expected exception types.697 698    .. versionadded:: 22.2.0699    """700    try:701        exc_types = tuple(exc_types)702    except TypeError:703        exc_types = (exc_types,)704    return _NotValidator(validator, msg, exc_types)705 706 707@attrs(repr=False, slots=True, unsafe_hash=True)708class _OrValidator:709    validators = attrib()710 711    def __call__(self, inst, attr, value):712        for v in self.validators:713            try:714                v(inst, attr, value)715            except Exception:  # noqa: BLE001, PERF203, S112716                continue717            else:718                return719 720        msg = f"None of {self.validators!r} satisfied for value {value!r}"721        raise ValueError(msg)722 723    def __repr__(self):724        return f"<or validator wrapping {self.validators!r}>"725 726 727def or_(*validators):728    """729    A validator that composes multiple validators into one.730 731    When called on a value, it runs all wrapped validators until one of them is732    satisfied.733 734    Args:735        validators (~collections.abc.Iterable[typing.Callable]):736            Arbitrary number of validators.737 738    Raises:739        ValueError:740            If no validator is satisfied. Raised with a human-readable error741            message listing all the wrapped validators and the value that742            failed all of them.743 744    .. versionadded:: 24.1.0745    """746    vals = []747    for v in validators:748        vals.extend(v.validators if isinstance(v, _OrValidator) else [v])749 750    return _OrValidator(tuple(vals))751 
codekingpro/portable-devtools · Team Ai