Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/attr/validators.py: 51%
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 to apply to iterable members.
366 iterable_validator:
367 Validator to apply to iterable itself (optional).
369 Raises
370 TypeError: if any sub-validators fail
372 .. versionadded:: 19.1.0
373 """
374 if isinstance(member_validator, (list, tuple)):
375 member_validator = and_(*member_validator)
376 return _DeepIterable(member_validator, iterable_validator)
379@attrs(repr=False, slots=True, unsafe_hash=True)
380class _DeepMapping:
381 key_validator = attrib(validator=is_callable())
382 value_validator = attrib(validator=is_callable())
383 mapping_validator = attrib(default=None, validator=optional(is_callable()))
385 def __call__(self, inst, attr, value):
386 """
387 We use a callable class to be able to change the ``__repr__``.
388 """
389 if self.mapping_validator is not None:
390 self.mapping_validator(inst, attr, value)
392 for key in value:
393 self.key_validator(inst, attr, key)
394 self.value_validator(inst, attr, value[key])
396 def __repr__(self):
397 return f"<deep_mapping validator for objects mapping {self.key_validator!r} to {self.value_validator!r}>"
400def deep_mapping(key_validator, value_validator, mapping_validator=None):
401 """
402 A validator that performs deep validation of a dictionary.
404 Args:
405 key_validator: Validator to apply to dictionary keys.
407 value_validator: Validator to apply to dictionary values.
409 mapping_validator:
410 Validator to apply to top-level mapping attribute (optional).
412 .. versionadded:: 19.1.0
414 Raises:
415 TypeError: if any sub-validators fail
416 """
417 return _DeepMapping(key_validator, value_validator, mapping_validator)
420@attrs(repr=False, frozen=True, slots=True)
421class _NumberValidator:
422 bound = attrib()
423 compare_op = attrib()
424 compare_func = attrib()
426 def __call__(self, inst, attr, value):
427 """
428 We use a callable class to be able to change the ``__repr__``.
429 """
430 if not self.compare_func(value, self.bound):
431 msg = f"'{attr.name}' must be {self.compare_op} {self.bound}: {value}"
432 raise ValueError(msg)
434 def __repr__(self):
435 return f"<Validator for x {self.compare_op} {self.bound}>"
438def lt(val):
439 """
440 A validator that raises `ValueError` if the initializer is called with a
441 number larger or equal to *val*.
443 The validator uses `operator.lt` to compare the values.
445 Args:
446 val: Exclusive upper bound for values.
448 .. versionadded:: 21.3.0
449 """
450 return _NumberValidator(val, "<", operator.lt)
453def le(val):
454 """
455 A validator that raises `ValueError` if the initializer is called with a
456 number greater than *val*.
458 The validator uses `operator.le` to compare the values.
460 Args:
461 val: Inclusive upper bound for values.
463 .. versionadded:: 21.3.0
464 """
465 return _NumberValidator(val, "<=", operator.le)
468def ge(val):
469 """
470 A validator that raises `ValueError` if the initializer is called with a
471 number smaller than *val*.
473 The validator uses `operator.ge` to compare the values.
475 Args:
476 val: Inclusive lower bound for values
478 .. versionadded:: 21.3.0
479 """
480 return _NumberValidator(val, ">=", operator.ge)
483def gt(val):
484 """
485 A validator that raises `ValueError` if the initializer is called with a
486 number smaller or equal to *val*.
488 The validator uses `operator.gt` to compare the values.
490 Args:
491 val: Exclusive lower bound for values
493 .. versionadded:: 21.3.0
494 """
495 return _NumberValidator(val, ">", operator.gt)
498@attrs(repr=False, frozen=True, slots=True)
499class _MaxLengthValidator:
500 max_length = attrib()
502 def __call__(self, inst, attr, value):
503 """
504 We use a callable class to be able to change the ``__repr__``.
505 """
506 if len(value) > self.max_length:
507 msg = f"Length of '{attr.name}' must be <= {self.max_length}: {len(value)}"
508 raise ValueError(msg)
510 def __repr__(self):
511 return f"<max_len validator for {self.max_length}>"
514def max_len(length):
515 """
516 A validator that raises `ValueError` if the initializer is called
517 with a string or iterable that is longer than *length*.
519 Args:
520 length (int): Maximum length of the string or iterable
522 .. versionadded:: 21.3.0
523 """
524 return _MaxLengthValidator(length)
527@attrs(repr=False, frozen=True, slots=True)
528class _MinLengthValidator:
529 min_length = attrib()
531 def __call__(self, inst, attr, value):
532 """
533 We use a callable class to be able to change the ``__repr__``.
534 """
535 if len(value) < self.min_length:
536 msg = f"Length of '{attr.name}' must be >= {self.min_length}: {len(value)}"
537 raise ValueError(msg)
539 def __repr__(self):
540 return f"<min_len validator for {self.min_length}>"
543def min_len(length):
544 """
545 A validator that raises `ValueError` if the initializer is called
546 with a string or iterable that is shorter than *length*.
548 Args:
549 length (int): Minimum length of the string or iterable
551 .. versionadded:: 22.1.0
552 """
553 return _MinLengthValidator(length)
556@attrs(repr=False, slots=True, unsafe_hash=True)
557class _SubclassOfValidator:
558 type = attrib()
560 def __call__(self, inst, attr, value):
561 """
562 We use a callable class to be able to change the ``__repr__``.
563 """
564 if not issubclass(value, self.type):
565 msg = f"'{attr.name}' must be a subclass of {self.type!r} (got {value!r})."
566 raise TypeError(
567 msg,
568 attr,
569 self.type,
570 value,
571 )
573 def __repr__(self):
574 return f"<subclass_of validator for type {self.type!r}>"
577def _subclass_of(type):
578 """
579 A validator that raises a `TypeError` if the initializer is called with a
580 wrong type for this particular attribute (checks are performed using
581 `issubclass` therefore it's also valid to pass a tuple of types).
583 Args:
584 type (type | tuple[type, ...]): The type(s) to check for.
586 Raises:
587 TypeError:
588 With a human readable error message, the attribute (of type
589 `attrs.Attribute`), the expected type, and the value it got.
590 """
591 return _SubclassOfValidator(type)
594@attrs(repr=False, slots=True, unsafe_hash=True)
595class _NotValidator:
596 validator = attrib()
597 msg = attrib(
598 converter=default_if_none(
599 "not_ validator child '{validator!r}' "
600 "did not raise a captured error"
601 )
602 )
603 exc_types = attrib(
604 validator=deep_iterable(
605 member_validator=_subclass_of(Exception),
606 iterable_validator=instance_of(tuple),
607 ),
608 )
610 def __call__(self, inst, attr, value):
611 try:
612 self.validator(inst, attr, value)
613 except self.exc_types:
614 pass # suppress error to invert validity
615 else:
616 raise ValueError(
617 self.msg.format(
618 validator=self.validator,
619 exc_types=self.exc_types,
620 ),
621 attr,
622 self.validator,
623 value,
624 self.exc_types,
625 )
627 def __repr__(self):
628 return f"<not_ validator wrapping {self.validator!r}, capturing {self.exc_types!r}>"
631def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)):
632 """
633 A validator that wraps and logically 'inverts' the validator passed to it.
634 It will raise a `ValueError` if the provided validator *doesn't* raise a
635 `ValueError` or `TypeError` (by default), and will suppress the exception
636 if the provided validator *does*.
638 Intended to be used with existing validators to compose logic without
639 needing to create inverted variants, for example, ``not_(in_(...))``.
641 Args:
642 validator: A validator to be logically inverted.
644 msg (str):
645 Message to raise if validator fails. Formatted with keys
646 ``exc_types`` and ``validator``.
648 exc_types (tuple[type, ...]):
649 Exception type(s) to capture. Other types raised by child
650 validators will not be intercepted and pass through.
652 Raises:
653 ValueError:
654 With a human readable error message, the attribute (of type
655 `attrs.Attribute`), the validator that failed to raise an
656 exception, the value it got, and the expected exception types.
658 .. versionadded:: 22.2.0
659 """
660 try:
661 exc_types = tuple(exc_types)
662 except TypeError:
663 exc_types = (exc_types,)
664 return _NotValidator(validator, msg, exc_types)
667@attrs(repr=False, slots=True, unsafe_hash=True)
668class _OrValidator:
669 validators = attrib()
671 def __call__(self, inst, attr, value):
672 for v in self.validators:
673 try:
674 v(inst, attr, value)
675 except Exception: # noqa: BLE001, PERF203, S112
676 continue
677 else:
678 return
680 msg = f"None of {self.validators!r} satisfied for value {value!r}"
681 raise ValueError(msg)
683 def __repr__(self):
684 return f"<or validator wrapping {self.validators!r}>"
687def or_(*validators):
688 """
689 A validator that composes multiple validators into one.
691 When called on a value, it runs all wrapped validators until one of them is
692 satisfied.
694 Args:
695 validators (~collections.abc.Iterable[typing.Callable]):
696 Arbitrary number of validators.
698 Raises:
699 ValueError:
700 If no validator is satisfied. Raised with a human-readable error
701 message listing all the wrapped validators and the value that
702 failed all of them.
704 .. versionadded:: 24.1.0
705 """
706 vals = []
707 for v in validators:
708 vals.extend(v.validators if isinstance(v, _OrValidator) else [v])
710 return _OrValidator(tuple(vals))