Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/attr/validators.py: 49%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# SPDX-License-Identifier: MIT
3"""
4Commonly useful validators.
5"""
7import operator
8import re
10from contextlib import contextmanager
11from re import Pattern
13from ._config import get_run_validators, set_run_validators
14from ._make import _AndValidator, and_, attrib, attrs
15from .converters import default_if_none
16from .exceptions import NotCallableError
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]
42def set_disabled(disabled):
43 """
44 Globally disable or enable running validators.
46 By default, they are run.
48 Args:
49 disabled (bool): If `True`, disable running all validators.
51 .. warning::
53 This function is not thread-safe!
55 .. versionadded:: 21.3.0
56 """
57 set_run_validators(not disabled)
60def get_disabled():
61 """
62 Return a bool indicating whether validators are currently disabled or not.
64 Returns:
65 bool:`True` if validators are currently disabled.
67 .. versionadded:: 21.3.0
68 """
69 return not get_run_validators()
72@contextmanager
73def disabled():
74 """
75 Context manager that disables running validators within its context.
77 .. warning::
79 This context manager is not thread-safe!
81 .. versionadded:: 21.3.0
82 """
83 set_run_validators(False)
84 try:
85 yield
86 finally:
87 set_run_validators(True)
90@attrs(repr=False, slots=True, unsafe_hash=True)
91class _InstanceOfValidator:
92 type = attrib()
94 def __call__(self, inst, attr, value):
95 """
96 We use a callable class to be able to change the ``__repr__``.
97 """
98 if not isinstance(value, self.type):
99 msg = f"'{attr.name}' must be {self.type!r} (got {value!r} that is a {value.__class__!r})."
100 raise TypeError(
101 msg,
102 attr,
103 self.type,
104 value,
105 )
107 def __repr__(self):
108 return f"<instance_of validator for type {self.type!r}>"
111def instance_of(type):
112 """
113 A validator that raises a `TypeError` if the initializer is called with a
114 wrong type for this particular attribute (checks are performed using
115 `isinstance` therefore it's also valid to pass a tuple of types).
117 Args:
118 type (type | tuple[type]): The type to check for.
120 Raises:
121 TypeError:
122 With a human readable error message, the attribute (of type
123 `attrs.Attribute`), the expected type, and the value it got.
124 """
125 return _InstanceOfValidator(type)
128@attrs(repr=False, frozen=True, slots=True)
129class _MatchesReValidator:
130 pattern = attrib()
131 match_func = attrib()
133 def __call__(self, inst, attr, value):
134 """
135 We use a callable class to be able to change the ``__repr__``.
136 """
137 if not self.match_func(value):
138 msg = f"'{attr.name}' must match regex {self.pattern.pattern!r} ({value!r} doesn't)"
139 raise ValueError(
140 msg,
141 attr,
142 self.pattern,
143 value,
144 )
146 def __repr__(self):
147 return f"<matches_re validator for pattern {self.pattern!r}>"
150def matches_re(regex, flags=0, func=None):
151 r"""
152 A validator that raises `ValueError` if the initializer is called with a
153 string that doesn't match *regex*.
155 Args:
156 regex (str, re.Pattern):
157 A regex string or precompiled pattern to match against
159 flags (int):
160 Flags that will be passed to the underlying re function (default 0)
162 func (typing.Callable):
163 Which underlying `re` function to call. Valid options are
164 `re.fullmatch`, `re.search`, and `re.match`; the default `None`
165 means `re.fullmatch`. For performance reasons, the pattern is
166 always precompiled using `re.compile`.
168 .. versionadded:: 19.2.0
169 .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern.
170 """
171 valid_funcs = (re.fullmatch, None, re.search, re.match)
172 if func not in valid_funcs:
173 msg = "'func' must be one of {}.".format(
174 ", ".join(
175 sorted((e and e.__name__) or "None" for e in set(valid_funcs))
176 )
177 )
178 raise ValueError(msg)
180 if isinstance(regex, Pattern):
181 if flags:
182 msg = "'flags' can only be used with a string pattern; pass flags to re.compile() instead"
183 raise TypeError(msg)
184 pattern = regex
185 else:
186 pattern = re.compile(regex, flags)
188 if func is re.match:
189 match_func = pattern.match
190 elif func is re.search:
191 match_func = pattern.search
192 else:
193 match_func = pattern.fullmatch
195 return _MatchesReValidator(pattern, match_func)
198@attrs(repr=False, slots=True, unsafe_hash=True)
199class _OptionalValidator:
200 validator = attrib()
202 def __call__(self, inst, attr, value):
203 if value is None:
204 return
206 self.validator(inst, attr, value)
208 def __repr__(self):
209 return f"<optional validator for {self.validator!r} or None>"
212def optional(validator):
213 """
214 A validator that makes an attribute optional. An optional attribute is one
215 which can be set to `None` in addition to satisfying the requirements of
216 the sub-validator.
218 Args:
219 validator
220 (typing.Callable | tuple[typing.Callable] | list[typing.Callable]):
221 A validator (or validators) that is used for non-`None` values.
223 .. versionadded:: 15.1.0
224 .. versionchanged:: 17.1.0 *validator* can be a list of validators.
225 .. versionchanged:: 23.1.0 *validator* can also be a tuple of validators.
226 """
227 if isinstance(validator, (list, tuple)):
228 return _OptionalValidator(_AndValidator(validator))
230 return _OptionalValidator(validator)
233@attrs(repr=False, slots=True, unsafe_hash=True)
234class _InValidator:
235 options = attrib()
236 _original_options = attrib(hash=False)
238 def __call__(self, inst, attr, value):
239 try:
240 in_options = value in self.options
241 except TypeError: # e.g. `1 in "abc"`
242 in_options = False
244 if not in_options:
245 msg = f"'{attr.name}' must be in {self._original_options!r} (got {value!r})"
246 raise ValueError(
247 msg,
248 attr,
249 self._original_options,
250 value,
251 )
253 def __repr__(self):
254 return f"<in_ validator with options {self._original_options!r}>"
257def in_(options):
258 """
259 A validator that raises a `ValueError` if the initializer is called with a
260 value that does not belong in the *options* provided.
262 The check is performed using ``value in options``, so *options* has to
263 support that operation.
265 To keep the validator hashable, dicts, lists, and sets are transparently
266 transformed into a `tuple`.
268 Args:
269 options: Allowed options.
271 Raises:
272 ValueError:
273 With a human readable error message, the attribute (of type
274 `attrs.Attribute`), the expected options, and the value it got.
276 .. versionadded:: 17.1.0
277 .. versionchanged:: 22.1.0
278 The ValueError was incomplete until now and only contained the human
279 readable error message. Now it contains all the information that has
280 been promised since 17.1.0.
281 .. versionchanged:: 24.1.0
282 *options* that are a list, dict, or a set are now transformed into a
283 tuple to keep the validator hashable.
284 """
285 repr_options = options
286 if isinstance(options, (list, dict, set)):
287 options = tuple(options)
289 return _InValidator(options, repr_options)
292@attrs(repr=False, slots=False, unsafe_hash=True)
293class _IsCallableValidator:
294 def __call__(self, inst, attr, value):
295 """
296 We use a callable class to be able to change the ``__repr__``.
297 """
298 if not callable(value):
299 message = (
300 "'{name}' must be callable "
301 "(got {value!r} that is a {actual!r})."
302 )
303 raise NotCallableError(
304 msg=message.format(
305 name=attr.name, value=value, actual=value.__class__
306 ),
307 value=value,
308 )
310 def __repr__(self):
311 return "<is_callable validator>"
314def is_callable():
315 """
316 A validator that raises a `attrs.exceptions.NotCallableError` if the
317 initializer is called with a value for this particular attribute that is
318 not callable.
320 .. versionadded:: 19.1.0
322 Raises:
323 attrs.exceptions.NotCallableError:
324 With a human readable error message containing the attribute
325 (`attrs.Attribute`) name, and the value it got.
326 """
327 return _IsCallableValidator()
330@attrs(repr=False, slots=True, unsafe_hash=True)
331class _DeepIterable:
332 member_validator = attrib(validator=is_callable())
333 iterable_validator = attrib(
334 default=None, validator=optional(is_callable())
335 )
337 def __call__(self, inst, attr, value):
338 """
339 We use a callable class to be able to change the ``__repr__``.
340 """
341 if self.iterable_validator is not None:
342 self.iterable_validator(inst, attr, value)
344 for member in value:
345 self.member_validator(inst, attr, member)
347 def __repr__(self):
348 iterable_identifier = (
349 ""
350 if self.iterable_validator is None
351 else f" {self.iterable_validator!r}"
352 )
353 return (
354 f"<deep_iterable validator for{iterable_identifier}"
355 f" iterables of {self.member_validator!r}>"
356 )
359def deep_iterable(member_validator, iterable_validator=None):
360 """
361 A validator that performs deep validation of an iterable.
363 Args:
364 member_validator: Validator(s) to apply to iterable members.
366 iterable_validator:
367 Validator(s) to apply to iterable itself (optional).
369 Raises
370 TypeError: if any sub-validators fail
372 .. versionadded:: 19.1.0
374 .. versionchanged:: 25.4.0
375 *member_validator* and *iterable_validator* can now be a list or tuple
376 of validators.
377 """
378 if isinstance(member_validator, (list, tuple)):
379 member_validator = and_(*member_validator)
380 if isinstance(iterable_validator, (list, tuple)):
381 iterable_validator = and_(*iterable_validator)
382 return _DeepIterable(member_validator, iterable_validator)
385@attrs(repr=False, slots=True, unsafe_hash=True)
386class _DeepMapping:
387 key_validator = attrib(validator=optional(is_callable()))
388 value_validator = attrib(validator=optional(is_callable()))
389 mapping_validator = attrib(validator=optional(is_callable()))
391 def __call__(self, inst, attr, value):
392 """
393 We use a callable class to be able to change the ``__repr__``.
394 """
395 if self.mapping_validator is not None:
396 self.mapping_validator(inst, attr, value)
398 for key in value:
399 if self.key_validator is not None:
400 self.key_validator(inst, attr, key)
401 if self.value_validator is not None:
402 self.value_validator(inst, attr, value[key])
404 def __repr__(self):
405 return f"<deep_mapping validator for objects mapping {self.key_validator!r} to {self.value_validator!r}>"
408def deep_mapping(
409 key_validator=None, value_validator=None, mapping_validator=None
410):
411 """
412 A validator that performs deep validation of a dictionary.
414 All validators are optional, but at least one of *key_validator* or
415 *value_validator* must be provided.
417 Args:
418 key_validator: Validator(s) to apply to dictionary keys.
420 value_validator: Validator(s) to apply to dictionary values.
422 mapping_validator:
423 Validator(s) to apply to top-level mapping attribute.
425 .. versionadded:: 19.1.0
427 .. versionchanged:: 25.4.0
428 *key_validator* and *value_validator* are now optional, but at least one
429 of them must be provided.
431 .. versionchanged:: 25.4.0
432 *key_validator*, *value_validator*, and *mapping_validator* can now be a
433 list or tuple of validators.
435 Raises:
436 TypeError: If any sub-validator fails on validation.
438 ValueError:
439 If neither *key_validator* nor *value_validator* is provided on
440 instantiation.
441 """
442 if key_validator is None and value_validator is None:
443 msg = (
444 "At least one of key_validator or value_validator must be provided"
445 )
446 raise ValueError(msg)
448 if isinstance(key_validator, (list, tuple)):
449 key_validator = and_(*key_validator)
450 if isinstance(value_validator, (list, tuple)):
451 value_validator = and_(*value_validator)
452 if isinstance(mapping_validator, (list, tuple)):
453 mapping_validator = and_(*mapping_validator)
455 return _DeepMapping(key_validator, value_validator, mapping_validator)
458@attrs(repr=False, frozen=True, slots=True)
459class _NumberValidator:
460 bound = attrib()
461 compare_op = attrib()
462 compare_func = attrib()
464 def __call__(self, inst, attr, value):
465 """
466 We use a callable class to be able to change the ``__repr__``.
467 """
468 if not self.compare_func(value, self.bound):
469 msg = f"'{attr.name}' must be {self.compare_op} {self.bound}: {value}"
470 raise ValueError(msg)
472 def __repr__(self):
473 return f"<Validator for x {self.compare_op} {self.bound}>"
476def lt(val):
477 """
478 A validator that raises `ValueError` if the initializer is called with a
479 number larger or equal to *val*.
481 The validator uses `operator.lt` to compare the values.
483 Args:
484 val: Exclusive upper bound for values.
486 .. versionadded:: 21.3.0
487 """
488 return _NumberValidator(val, "<", operator.lt)
491def le(val):
492 """
493 A validator that raises `ValueError` if the initializer is called with a
494 number greater than *val*.
496 The validator uses `operator.le` to compare the values.
498 Args:
499 val: Inclusive upper bound for values.
501 .. versionadded:: 21.3.0
502 """
503 return _NumberValidator(val, "<=", operator.le)
506def ge(val):
507 """
508 A validator that raises `ValueError` if the initializer is called with a
509 number smaller than *val*.
511 The validator uses `operator.ge` to compare the values.
513 Args:
514 val: Inclusive lower bound for values
516 .. versionadded:: 21.3.0
517 """
518 return _NumberValidator(val, ">=", operator.ge)
521def gt(val):
522 """
523 A validator that raises `ValueError` if the initializer is called with a
524 number smaller or equal to *val*.
526 The validator uses `operator.gt` to compare the values.
528 Args:
529 val: Exclusive lower bound for values
531 .. versionadded:: 21.3.0
532 """
533 return _NumberValidator(val, ">", operator.gt)
536@attrs(repr=False, frozen=True, slots=True)
537class _MaxLengthValidator:
538 max_length = attrib()
540 def __call__(self, inst, attr, value):
541 """
542 We use a callable class to be able to change the ``__repr__``.
543 """
544 if len(value) > self.max_length:
545 msg = f"Length of '{attr.name}' must be <= {self.max_length}: {len(value)}"
546 raise ValueError(msg)
548 def __repr__(self):
549 return f"<max_len validator for {self.max_length}>"
552def max_len(length):
553 """
554 A validator that raises `ValueError` if the initializer is called
555 with a string or iterable that is longer than *length*.
557 Args:
558 length (int): Maximum length of the string or iterable
560 .. versionadded:: 21.3.0
561 """
562 return _MaxLengthValidator(length)
565@attrs(repr=False, frozen=True, slots=True)
566class _MinLengthValidator:
567 min_length = attrib()
569 def __call__(self, inst, attr, value):
570 """
571 We use a callable class to be able to change the ``__repr__``.
572 """
573 if len(value) < self.min_length:
574 msg = f"Length of '{attr.name}' must be >= {self.min_length}: {len(value)}"
575 raise ValueError(msg)
577 def __repr__(self):
578 return f"<min_len validator for {self.min_length}>"
581def min_len(length):
582 """
583 A validator that raises `ValueError` if the initializer is called
584 with a string or iterable that is shorter than *length*.
586 Args:
587 length (int): Minimum length of the string or iterable
589 .. versionadded:: 22.1.0
590 """
591 return _MinLengthValidator(length)
594@attrs(repr=False, slots=True, unsafe_hash=True)
595class _SubclassOfValidator:
596 type = attrib()
598 def __call__(self, inst, attr, value):
599 """
600 We use a callable class to be able to change the ``__repr__``.
601 """
602 if not issubclass(value, self.type):
603 msg = f"'{attr.name}' must be a subclass of {self.type!r} (got {value!r})."
604 raise TypeError(
605 msg,
606 attr,
607 self.type,
608 value,
609 )
611 def __repr__(self):
612 return f"<subclass_of validator for type {self.type!r}>"
615def _subclass_of(type):
616 """
617 A validator that raises a `TypeError` if the initializer is called with a
618 wrong type for this particular attribute (checks are performed using
619 `issubclass` therefore it's also valid to pass a tuple of types).
621 Args:
622 type (type | tuple[type, ...]): The type(s) to check for.
624 Raises:
625 TypeError:
626 With a human readable error message, the attribute (of type
627 `attrs.Attribute`), the expected type, and the value it got.
628 """
629 return _SubclassOfValidator(type)
632@attrs(repr=False, slots=True, unsafe_hash=True)
633class _NotValidator:
634 validator = attrib()
635 msg = attrib(
636 converter=default_if_none(
637 "not_ validator child '{validator!r}' "
638 "did not raise a captured error"
639 )
640 )
641 exc_types = attrib(
642 validator=deep_iterable(
643 member_validator=_subclass_of(Exception),
644 iterable_validator=instance_of(tuple),
645 ),
646 )
648 def __call__(self, inst, attr, value):
649 try:
650 self.validator(inst, attr, value)
651 except self.exc_types:
652 pass # suppress error to invert validity
653 else:
654 raise ValueError(
655 self.msg.format(
656 validator=self.validator,
657 exc_types=self.exc_types,
658 ),
659 attr,
660 self.validator,
661 value,
662 self.exc_types,
663 )
665 def __repr__(self):
666 return f"<not_ validator wrapping {self.validator!r}, capturing {self.exc_types!r}>"
669def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)):
670 """
671 A validator that wraps and logically 'inverts' the validator passed to it.
672 It will raise a `ValueError` if the provided validator *doesn't* raise a
673 `ValueError` or `TypeError` (by default), and will suppress the exception
674 if the provided validator *does*.
676 Intended to be used with existing validators to compose logic without
677 needing to create inverted variants, for example, ``not_(in_(...))``.
679 Args:
680 validator: A validator to be logically inverted.
682 msg (str):
683 Message to raise if validator fails. Formatted with keys
684 ``exc_types`` and ``validator``.
686 exc_types (tuple[type, ...]):
687 Exception type(s) to capture. Other types raised by child
688 validators will not be intercepted and pass through.
690 Raises:
691 ValueError:
692 With a human readable error message, the attribute (of type
693 `attrs.Attribute`), the validator that failed to raise an
694 exception, the value it got, and the expected exception types.
696 .. versionadded:: 22.2.0
697 """
698 try:
699 exc_types = tuple(exc_types)
700 except TypeError:
701 exc_types = (exc_types,)
702 return _NotValidator(validator, msg, exc_types)
705@attrs(repr=False, slots=True, unsafe_hash=True)
706class _OrValidator:
707 validators = attrib()
709 def __call__(self, inst, attr, value):
710 for v in self.validators:
711 try:
712 v(inst, attr, value)
713 except Exception: # noqa: BLE001, PERF203, S112
714 continue
715 else:
716 return
718 msg = f"None of {self.validators!r} satisfied for value {value!r}"
719 raise ValueError(msg)
721 def __repr__(self):
722 return f"<or validator wrapping {self.validators!r}>"
725def or_(*validators):
726 """
727 A validator that composes multiple validators into one.
729 When called on a value, it runs all wrapped validators until one of them is
730 satisfied.
732 Args:
733 validators (~collections.abc.Iterable[typing.Callable]):
734 Arbitrary number of validators.
736 Raises:
737 ValueError:
738 If no validator is satisfied. Raised with a human-readable error
739 message listing all the wrapped validators and the value that
740 failed all of them.
742 .. versionadded:: 24.1.0
743 """
744 vals = []
745 for v in validators:
746 vals.extend(v.validators if isinstance(v, _OrValidator) else [v])
748 return _OrValidator(tuple(vals))