Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/attr/validators.py: 48%
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 .. versionchanged:: 26.1.0 The contextmanager is nestable.
83 """
84 prev = get_run_validators()
85 set_run_validators(False)
86 try:
87 yield
88 finally:
89 set_run_validators(prev)
92@attrs(repr=False, slots=True, unsafe_hash=True)
93class _InstanceOfValidator:
94 type = attrib()
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 )
109 def __repr__(self):
110 return f"<instance_of validator for type {self.type!r}>"
113def instance_of(type):
114 """
115 A validator that raises a `TypeError` if the initializer is called with a
116 wrong type for this particular attribute (checks are performed using
117 `isinstance` therefore it's also valid to pass a tuple of types).
119 Args:
120 type (type | tuple[type]): The type to check for.
122 Raises:
123 TypeError:
124 With a human readable error message, the attribute (of type
125 `attrs.Attribute`), the expected type, and the value it got.
126 """
127 return _InstanceOfValidator(type)
130@attrs(repr=False, frozen=True, slots=True)
131class _MatchesReValidator:
132 pattern = attrib()
133 match_func = attrib()
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 )
148 def __repr__(self):
149 return f"<matches_re validator for pattern {self.pattern!r}>"
152def matches_re(regex, flags=0, func=None):
153 r"""
154 A validator that raises `ValueError` if the initializer is called with a
155 string that doesn't match *regex*.
157 Args:
158 regex (str, re.Pattern):
159 A regex string or precompiled pattern to match against
161 flags (int):
162 Flags that will be passed to the underlying re function (default 0)
164 func (typing.Callable):
165 Which underlying `re` function to call. Valid options are
166 `re.fullmatch`, `re.search`, and `re.match`; the default `None`
167 means `re.fullmatch`. For performance reasons, the pattern is
168 always precompiled using `re.compile`.
170 .. versionadded:: 19.2.0
171 .. 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)
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 = regex
187 else:
188 pattern = re.compile(regex, flags)
190 if func is re.match:
191 match_func = pattern.match
192 elif func is re.search:
193 match_func = pattern.search
194 else:
195 match_func = pattern.fullmatch
197 return _MatchesReValidator(pattern, match_func)
200@attrs(repr=False, slots=True, unsafe_hash=True)
201class _OptionalValidator:
202 validator = attrib()
204 def __call__(self, inst, attr, value):
205 if value is None:
206 return
208 self.validator(inst, attr, value)
210 def __repr__(self):
211 return f"<optional validator for {self.validator!r} or None>"
214def optional(validator):
215 """
216 A validator that makes an attribute optional. An optional attribute is one
217 which can be set to `None` in addition to satisfying the requirements of
218 the sub-validator.
220 Args:
221 validator
222 (typing.Callable | tuple[typing.Callable] | list[typing.Callable]):
223 A validator (or validators) that is used for non-`None` values.
225 .. versionadded:: 15.1.0
226 .. 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))
232 return _OptionalValidator(validator)
235@attrs(repr=False, slots=True, unsafe_hash=True)
236class _InValidator:
237 options = attrib()
238 _original_options = attrib(hash=False)
240 def __call__(self, inst, attr, value):
241 try:
242 in_options = value in self.options
243 except TypeError: # e.g. `1 in "abc"`
244 in_options = False
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 )
255 def __repr__(self):
256 return f"<in_ validator with options {self._original_options!r}>"
259def in_(options):
260 """
261 A validator that raises a `ValueError` if the initializer is called with a
262 value that does not belong in the *options* provided.
264 The check is performed using ``value in options``, so *options* has to
265 support that operation.
267 To keep the validator hashable, dicts, lists, and sets are transparently
268 transformed into a `tuple`.
270 Args:
271 options: Allowed options.
273 Raises:
274 ValueError:
275 With a human readable error message, the attribute (of type
276 `attrs.Attribute`), the expected options, and the value it got.
278 .. versionadded:: 17.1.0
279 .. versionchanged:: 22.1.0
280 The ValueError was incomplete until now and only contained the human
281 readable error message. Now it contains all the information that has
282 been promised since 17.1.0.
283 .. versionchanged:: 24.1.0
284 *options* that are a list, dict, or a set are now transformed into a
285 tuple to keep the validator hashable.
286 """
287 repr_options = options
288 if isinstance(options, (list, dict, set)):
289 options = tuple(options)
291 return _InValidator(options, repr_options)
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 )
312 def __repr__(self):
313 return "<is_callable validator>"
316def is_callable():
317 """
318 A validator that raises a `attrs.exceptions.NotCallableError` if the
319 initializer is called with a value for this particular attribute that is
320 not callable.
322 .. versionadded:: 19.1.0
324 Raises:
325 attrs.exceptions.NotCallableError:
326 With a human readable error message containing the attribute
327 (`attrs.Attribute`) name, and the value it got.
328 """
329 return _IsCallableValidator()
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 )
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)
346 for member in value:
347 self.member_validator(inst, attr, member)
349 def __repr__(self):
350 iterable_identifier = (
351 ""
352 if self.iterable_validator is None
353 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 )
361def deep_iterable(member_validator, iterable_validator=None):
362 """
363 A validator that performs deep validation of an iterable.
365 Args:
366 member_validator: Validator(s) to apply to iterable members.
368 iterable_validator:
369 Validator(s) to apply to iterable itself (optional).
371 Raises
372 TypeError: if any sub-validators fail
374 .. versionadded:: 19.1.0
376 .. versionchanged:: 25.4.0
377 *member_validator* and *iterable_validator* can now be a list or tuple
378 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)
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()))
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)
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])
406 def __repr__(self):
407 return f"<deep_mapping validator for objects mapping {self.key_validator!r} to {self.value_validator!r}>"
410def deep_mapping(
411 key_validator=None, value_validator=None, mapping_validator=None
412):
413 """
414 A validator that performs deep validation of a dictionary.
416 All validators are optional, but at least one of *key_validator* or
417 *value_validator* must be provided.
419 Args:
420 key_validator: Validator(s) to apply to dictionary keys.
422 value_validator: Validator(s) to apply to dictionary values.
424 mapping_validator:
425 Validator(s) to apply to top-level mapping attribute.
427 .. versionadded:: 19.1.0
429 .. versionchanged:: 25.4.0
430 *key_validator* and *value_validator* are now optional, but at least one
431 of them must be provided.
433 .. versionchanged:: 25.4.0
434 *key_validator*, *value_validator*, and *mapping_validator* can now be a
435 list or tuple of validators.
437 Raises:
438 TypeError: If any sub-validator fails on validation.
440 ValueError:
441 If neither *key_validator* nor *value_validator* is provided on
442 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)
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)
457 return _DeepMapping(key_validator, value_validator, mapping_validator)
460@attrs(repr=False, frozen=True, slots=True)
461class _NumberValidator:
462 bound = attrib()
463 compare_op = attrib()
464 compare_func = attrib()
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)
474 def __repr__(self):
475 return f"<Validator for x {self.compare_op} {self.bound}>"
478def lt(val):
479 """
480 A validator that raises `ValueError` if the initializer is called with a
481 number larger or equal to *val*.
483 The validator uses `operator.lt` to compare the values.
485 Args:
486 val: Exclusive upper bound for values.
488 .. versionadded:: 21.3.0
489 """
490 return _NumberValidator(val, "<", operator.lt)
493def le(val):
494 """
495 A validator that raises `ValueError` if the initializer is called with a
496 number greater than *val*.
498 The validator uses `operator.le` to compare the values.
500 Args:
501 val: Inclusive upper bound for values.
503 .. versionadded:: 21.3.0
504 """
505 return _NumberValidator(val, "<=", operator.le)
508def ge(val):
509 """
510 A validator that raises `ValueError` if the initializer is called with a
511 number smaller than *val*.
513 The validator uses `operator.ge` to compare the values.
515 Args:
516 val: Inclusive lower bound for values
518 .. versionadded:: 21.3.0
519 """
520 return _NumberValidator(val, ">=", operator.ge)
523def gt(val):
524 """
525 A validator that raises `ValueError` if the initializer is called with a
526 number smaller or equal to *val*.
528 The validator uses `operator.gt` to compare the values.
530 Args:
531 val: Exclusive lower bound for values
533 .. versionadded:: 21.3.0
534 """
535 return _NumberValidator(val, ">", operator.gt)
538@attrs(repr=False, frozen=True, slots=True)
539class _MaxLengthValidator:
540 max_length = attrib()
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)
550 def __repr__(self):
551 return f"<max_len validator for {self.max_length}>"
554def max_len(length):
555 """
556 A validator that raises `ValueError` if the initializer is called
557 with a string or iterable that is longer than *length*.
559 Args:
560 length (int): Maximum length of the string or iterable
562 .. versionadded:: 21.3.0
563 """
564 return _MaxLengthValidator(length)
567@attrs(repr=False, frozen=True, slots=True)
568class _MinLengthValidator:
569 min_length = attrib()
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)
579 def __repr__(self):
580 return f"<min_len validator for {self.min_length}>"
583def min_len(length):
584 """
585 A validator that raises `ValueError` if the initializer is called
586 with a string or iterable that is shorter than *length*.
588 Args:
589 length (int): Minimum length of the string or iterable
591 .. versionadded:: 22.1.0
592 """
593 return _MinLengthValidator(length)
596@attrs(repr=False, slots=True, unsafe_hash=True)
597class _SubclassOfValidator:
598 type = attrib()
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 )
613 def __repr__(self):
614 return f"<subclass_of validator for type {self.type!r}>"
617def _subclass_of(type):
618 """
619 A validator that raises a `TypeError` if the initializer is called with a
620 wrong type for this particular attribute (checks are performed using
621 `issubclass` therefore it's also valid to pass a tuple of types).
623 Args:
624 type (type | tuple[type, ...]): The type(s) to check for.
626 Raises:
627 TypeError:
628 With a human readable error message, the attribute (of type
629 `attrs.Attribute`), the expected type, and the value it got.
630 """
631 return _SubclassOfValidator(type)
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 )
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 validity
655 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 )
667 def __repr__(self):
668 return f"<not_ validator wrapping {self.validator!r}, capturing {self.exc_types!r}>"
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 a
675 `ValueError` or `TypeError` (by default), and will suppress the exception
676 if the provided validator *does*.
678 Intended to be used with existing validators to compose logic without
679 needing to create inverted variants, for example, ``not_(in_(...))``.
681 Args:
682 validator: A validator to be logically inverted.
684 msg (str):
685 Message to raise if validator fails. Formatted with keys
686 ``exc_types`` and ``validator``.
688 exc_types (tuple[type, ...]):
689 Exception type(s) to capture. Other types raised by child
690 validators will not be intercepted and pass through.
692 Raises:
693 ValueError:
694 With a human readable error message, the attribute (of type
695 `attrs.Attribute`), the validator that failed to raise an
696 exception, the value it got, and the expected exception types.
698 .. versionadded:: 22.2.0
699 """
700 try:
701 exc_types = tuple(exc_types)
702 except TypeError:
703 exc_types = (exc_types,)
704 return _NotValidator(validator, msg, exc_types)
707@attrs(repr=False, slots=True, unsafe_hash=True)
708class _OrValidator:
709 validators = attrib()
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, S112
716 continue
717 else:
718 return
720 msg = f"None of {self.validators!r} satisfied for value {value!r}"
721 raise ValueError(msg)
723 def __repr__(self):
724 return f"<or validator wrapping {self.validators!r}>"
727def or_(*validators):
728 """
729 A validator that composes multiple validators into one.
731 When called on a value, it runs all wrapped validators until one of them is
732 satisfied.
734 Args:
735 validators (~collections.abc.Iterable[typing.Callable]):
736 Arbitrary number of validators.
738 Raises:
739 ValueError:
740 If no validator is satisfied. Raised with a human-readable error
741 message listing all the wrapped validators and the value that
742 failed all of them.
744 .. versionadded:: 24.1.0
745 """
746 vals = []
747 for v in validators:
748 vals.extend(v.validators if isinstance(v, _OrValidator) else [v])
750 return _OrValidator(tuple(vals))