Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/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"""
8import operator
9import re
11from contextlib import contextmanager
12from re import Pattern
14from ._config import get_run_validators, set_run_validators
15from ._make import _AndValidator, and_, attrib, attrs
16from .converters import default_if_none
17from .exceptions import NotCallableError
20__all__ = [
21 "and_",
22 "deep_iterable",
23 "deep_mapping",
24 "disabled",
25 "ge",
26 "get_disabled",
27 "gt",
28 "in_",
29 "instance_of",
30 "is_callable",
31 "le",
32 "lt",
33 "matches_re",
34 "max_len",
35 "min_len",
36 "not_",
37 "optional",
38 "or_",
39 "set_disabled",
40]
43def set_disabled(disabled):
44 """
45 Globally disable or enable running validators.
47 By default, they are run.
49 Args:
50 disabled (bool): If `True`, disable running all validators.
52 .. warning::
54 This function is not thread-safe!
56 .. versionadded:: 21.3.0
57 """
58 set_run_validators(not disabled)
61def get_disabled():
62 """
63 Return a bool indicating whether validators are currently disabled or not.
65 Returns:
66 bool:`True` if validators are currently disabled.
68 .. versionadded:: 21.3.0
69 """
70 return not get_run_validators()
73@contextmanager
74def disabled():
75 """
76 Context manager that disables running validators within its context.
78 .. warning::
80 This context manager is not thread-safe!
82 .. versionadded:: 21.3.0
83 """
84 set_run_validators(False)
85 try:
86 yield
87 finally:
88 set_run_validators(True)
91@attrs(repr=False, slots=True, unsafe_hash=True)
92class _InstanceOfValidator:
93 type = attrib()
95 def __call__(self, inst, attr, value):
96 """
97 We use a callable class to be able to change the ``__repr__``.
98 """
99 if not isinstance(value, self.type):
100 msg = f"'{attr.name}' must be {self.type!r} (got {value!r} that is a {value.__class__!r})."
101 raise TypeError(
102 msg,
103 attr,
104 self.type,
105 value,
106 )
108 def __repr__(self):
109 return f"<instance_of validator for type {self.type!r}>"
112def instance_of(type):
113 """
114 A validator that raises a `TypeError` if the initializer is called with a
115 wrong type for this particular attribute (checks are performed using
116 `isinstance` therefore it's also valid to pass a tuple of types).
118 Args:
119 type (type | tuple[type]): The type to check for.
121 Raises:
122 TypeError:
123 With a human readable error message, the attribute (of type
124 `attrs.Attribute`), the expected type, and the value it got.
125 """
126 return _InstanceOfValidator(type)
129@attrs(repr=False, frozen=True, slots=True)
130class _MatchesReValidator:
131 pattern = attrib()
132 match_func = attrib()
134 def __call__(self, inst, attr, value):
135 """
136 We use a callable class to be able to change the ``__repr__``.
137 """
138 if not self.match_func(value):
139 msg = f"'{attr.name}' must match regex {self.pattern.pattern!r} ({value!r} doesn't)"
140 raise ValueError(
141 msg,
142 attr,
143 self.pattern,
144 value,
145 )
147 def __repr__(self):
148 return f"<matches_re validator for pattern {self.pattern!r}>"
151def matches_re(regex, flags=0, func=None):
152 r"""
153 A validator that raises `ValueError` if the initializer is called with a
154 string that doesn't match *regex*.
156 Args:
157 regex (str, re.Pattern):
158 A regex string or precompiled pattern to match against
160 flags (int):
161 Flags that will be passed to the underlying re function (default 0)
163 func (typing.Callable):
164 Which underlying `re` function to call. Valid options are
165 `re.fullmatch`, `re.search`, and `re.match`; the default `None`
166 means `re.fullmatch`. For performance reasons, the pattern is
167 always precompiled using `re.compile`.
169 .. versionadded:: 19.2.0
170 .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern.
171 """
172 valid_funcs = (re.fullmatch, None, re.search, re.match)
173 if func not in valid_funcs:
174 msg = "'func' must be one of {}.".format(
175 ", ".join(
176 sorted(e and e.__name__ or "None" for e in set(valid_funcs))
177 )
178 )
179 raise ValueError(msg)
181 if isinstance(regex, Pattern):
182 if flags:
183 msg = "'flags' can only be used with a string pattern; pass flags to re.compile() instead"
184 raise TypeError(msg)
185 pattern = regex
186 else:
187 pattern = re.compile(regex, flags)
189 if func is re.match:
190 match_func = pattern.match
191 elif func is re.search:
192 match_func = pattern.search
193 else:
194 match_func = pattern.fullmatch
196 return _MatchesReValidator(pattern, match_func)
199@attrs(repr=False, slots=True, unsafe_hash=True)
200class _OptionalValidator:
201 validator = attrib()
203 def __call__(self, inst, attr, value):
204 if value is None:
205 return
207 self.validator(inst, attr, value)
209 def __repr__(self):
210 return f"<optional validator for {self.validator!r} or None>"
213def optional(validator):
214 """
215 A validator that makes an attribute optional. An optional attribute is one
216 which can be set to `None` in addition to satisfying the requirements of
217 the sub-validator.
219 Args:
220 validator
221 (typing.Callable | tuple[typing.Callable] | list[typing.Callable]):
222 A validator (or validators) that is used for non-`None` values.
224 .. versionadded:: 15.1.0
225 .. versionchanged:: 17.1.0 *validator* can be a list of validators.
226 .. versionchanged:: 23.1.0 *validator* can also be a tuple of validators.
227 """
228 if isinstance(validator, (list, tuple)):
229 return _OptionalValidator(_AndValidator(validator))
231 return _OptionalValidator(validator)
234@attrs(repr=False, slots=True, unsafe_hash=True)
235class _InValidator:
236 options = attrib()
237 _original_options = attrib(hash=False)
239 def __call__(self, inst, attr, value):
240 try:
241 in_options = value in self.options
242 except TypeError: # e.g. `1 in "abc"`
243 in_options = False
245 if not in_options:
246 msg = f"'{attr.name}' must be in {self._original_options!r} (got {value!r})"
247 raise ValueError(
248 msg,
249 attr,
250 self._original_options,
251 value,
252 )
254 def __repr__(self):
255 return f"<in_ validator with options {self._original_options!r}>"
258def in_(options):
259 """
260 A validator that raises a `ValueError` if the initializer is called with a
261 value that does not belong in the *options* provided.
263 The check is performed using ``value in options``, so *options* has to
264 support that operation.
266 To keep the validator hashable, dicts, lists, and sets are transparently
267 transformed into a `tuple`.
269 Args:
270 options: Allowed options.
272 Raises:
273 ValueError:
274 With a human readable error message, the attribute (of type
275 `attrs.Attribute`), the expected options, and the value it got.
277 .. versionadded:: 17.1.0
278 .. versionchanged:: 22.1.0
279 The ValueError was incomplete until now and only contained the human
280 readable error message. Now it contains all the information that has
281 been promised since 17.1.0.
282 .. versionchanged:: 24.1.0
283 *options* that are a list, dict, or a set are now transformed into a
284 tuple to keep the validator hashable.
285 """
286 repr_options = options
287 if isinstance(options, (list, dict, set)):
288 options = tuple(options)
290 return _InValidator(options, repr_options)
293@attrs(repr=False, slots=False, unsafe_hash=True)
294class _IsCallableValidator:
295 def __call__(self, inst, attr, value):
296 """
297 We use a callable class to be able to change the ``__repr__``.
298 """
299 if not callable(value):
300 message = (
301 "'{name}' must be callable "
302 "(got {value!r} that is a {actual!r})."
303 )
304 raise NotCallableError(
305 msg=message.format(
306 name=attr.name, value=value, actual=value.__class__
307 ),
308 value=value,
309 )
311 def __repr__(self):
312 return "<is_callable validator>"
315def is_callable():
316 """
317 A validator that raises a `attrs.exceptions.NotCallableError` if the
318 initializer is called with a value for this particular attribute that is
319 not callable.
321 .. versionadded:: 19.1.0
323 Raises:
324 attrs.exceptions.NotCallableError:
325 With a human readable error message containing the attribute
326 (`attrs.Attribute`) name, and the value it got.
327 """
328 return _IsCallableValidator()
331@attrs(repr=False, slots=True, unsafe_hash=True)
332class _DeepIterable:
333 member_validator = attrib(validator=is_callable())
334 iterable_validator = attrib(
335 default=None, validator=optional(is_callable())
336 )
338 def __call__(self, inst, attr, value):
339 """
340 We use a callable class to be able to change the ``__repr__``.
341 """
342 if self.iterable_validator is not None:
343 self.iterable_validator(inst, attr, value)
345 for member in value:
346 self.member_validator(inst, attr, member)
348 def __repr__(self):
349 iterable_identifier = (
350 ""
351 if self.iterable_validator is None
352 else f" {self.iterable_validator!r}"
353 )
354 return (
355 f"<deep_iterable validator for{iterable_identifier}"
356 f" iterables of {self.member_validator!r}>"
357 )
360def deep_iterable(member_validator, iterable_validator=None):
361 """
362 A validator that performs deep validation of an iterable.
364 Args:
365 member_validator: Validator to apply to iterable members.
367 iterable_validator:
368 Validator to apply to iterable itself (optional).
370 Raises
371 TypeError: if any sub-validators fail
373 .. versionadded:: 19.1.0
374 """
375 if isinstance(member_validator, (list, tuple)):
376 member_validator = and_(*member_validator)
377 return _DeepIterable(member_validator, iterable_validator)
380@attrs(repr=False, slots=True, unsafe_hash=True)
381class _DeepMapping:
382 key_validator = attrib(validator=is_callable())
383 value_validator = attrib(validator=is_callable())
384 mapping_validator = attrib(default=None, validator=optional(is_callable()))
386 def __call__(self, inst, attr, value):
387 """
388 We use a callable class to be able to change the ``__repr__``.
389 """
390 if self.mapping_validator is not None:
391 self.mapping_validator(inst, attr, value)
393 for key in value:
394 self.key_validator(inst, attr, key)
395 self.value_validator(inst, attr, value[key])
397 def __repr__(self):
398 return f"<deep_mapping validator for objects mapping {self.key_validator!r} to {self.value_validator!r}>"
401def deep_mapping(key_validator, value_validator, mapping_validator=None):
402 """
403 A validator that performs deep validation of a dictionary.
405 Args:
406 key_validator: Validator to apply to dictionary keys.
408 value_validator: Validator to apply to dictionary values.
410 mapping_validator:
411 Validator to apply to top-level mapping attribute (optional).
413 .. versionadded:: 19.1.0
415 Raises:
416 TypeError: if any sub-validators fail
417 """
418 return _DeepMapping(key_validator, value_validator, mapping_validator)
421@attrs(repr=False, frozen=True, slots=True)
422class _NumberValidator:
423 bound = attrib()
424 compare_op = attrib()
425 compare_func = attrib()
427 def __call__(self, inst, attr, value):
428 """
429 We use a callable class to be able to change the ``__repr__``.
430 """
431 if not self.compare_func(value, self.bound):
432 msg = f"'{attr.name}' must be {self.compare_op} {self.bound}: {value}"
433 raise ValueError(msg)
435 def __repr__(self):
436 return f"<Validator for x {self.compare_op} {self.bound}>"
439def lt(val):
440 """
441 A validator that raises `ValueError` if the initializer is called with a
442 number larger or equal to *val*.
444 The validator uses `operator.lt` to compare the values.
446 Args:
447 val: Exclusive upper bound for values.
449 .. versionadded:: 21.3.0
450 """
451 return _NumberValidator(val, "<", operator.lt)
454def le(val):
455 """
456 A validator that raises `ValueError` if the initializer is called with a
457 number greater than *val*.
459 The validator uses `operator.le` to compare the values.
461 Args:
462 val: Inclusive upper bound for values.
464 .. versionadded:: 21.3.0
465 """
466 return _NumberValidator(val, "<=", operator.le)
469def ge(val):
470 """
471 A validator that raises `ValueError` if the initializer is called with a
472 number smaller than *val*.
474 The validator uses `operator.ge` to compare the values.
476 Args:
477 val: Inclusive lower bound for values
479 .. versionadded:: 21.3.0
480 """
481 return _NumberValidator(val, ">=", operator.ge)
484def gt(val):
485 """
486 A validator that raises `ValueError` if the initializer is called with a
487 number smaller or equal to *val*.
489 The validator uses `operator.ge` to compare the values.
491 Args:
492 val: Exclusive lower bound for values
494 .. versionadded:: 21.3.0
495 """
496 return _NumberValidator(val, ">", operator.gt)
499@attrs(repr=False, frozen=True, slots=True)
500class _MaxLengthValidator:
501 max_length = attrib()
503 def __call__(self, inst, attr, value):
504 """
505 We use a callable class to be able to change the ``__repr__``.
506 """
507 if len(value) > self.max_length:
508 msg = f"Length of '{attr.name}' must be <= {self.max_length}: {len(value)}"
509 raise ValueError(msg)
511 def __repr__(self):
512 return f"<max_len validator for {self.max_length}>"
515def max_len(length):
516 """
517 A validator that raises `ValueError` if the initializer is called
518 with a string or iterable that is longer than *length*.
520 Args:
521 length (int): Maximum length of the string or iterable
523 .. versionadded:: 21.3.0
524 """
525 return _MaxLengthValidator(length)
528@attrs(repr=False, frozen=True, slots=True)
529class _MinLengthValidator:
530 min_length = attrib()
532 def __call__(self, inst, attr, value):
533 """
534 We use a callable class to be able to change the ``__repr__``.
535 """
536 if len(value) < self.min_length:
537 msg = f"Length of '{attr.name}' must be >= {self.min_length}: {len(value)}"
538 raise ValueError(msg)
540 def __repr__(self):
541 return f"<min_len validator for {self.min_length}>"
544def min_len(length):
545 """
546 A validator that raises `ValueError` if the initializer is called
547 with a string or iterable that is shorter than *length*.
549 Args:
550 length (int): Minimum length of the string or iterable
552 .. versionadded:: 22.1.0
553 """
554 return _MinLengthValidator(length)
557@attrs(repr=False, slots=True, unsafe_hash=True)
558class _SubclassOfValidator:
559 type = attrib()
561 def __call__(self, inst, attr, value):
562 """
563 We use a callable class to be able to change the ``__repr__``.
564 """
565 if not issubclass(value, self.type):
566 msg = f"'{attr.name}' must be a subclass of {self.type!r} (got {value!r})."
567 raise TypeError(
568 msg,
569 attr,
570 self.type,
571 value,
572 )
574 def __repr__(self):
575 return f"<subclass_of validator for type {self.type!r}>"
578def _subclass_of(type):
579 """
580 A validator that raises a `TypeError` if the initializer is called with a
581 wrong type for this particular attribute (checks are performed using
582 `issubclass` therefore it's also valid to pass a tuple of types).
584 Args:
585 type (type | tuple[type, ...]): The type(s) to check for.
587 Raises:
588 TypeError:
589 With a human readable error message, the attribute (of type
590 `attrs.Attribute`), the expected type, and the value it got.
591 """
592 return _SubclassOfValidator(type)
595@attrs(repr=False, slots=True, unsafe_hash=True)
596class _NotValidator:
597 validator = attrib()
598 msg = attrib(
599 converter=default_if_none(
600 "not_ validator child '{validator!r}' "
601 "did not raise a captured error"
602 )
603 )
604 exc_types = attrib(
605 validator=deep_iterable(
606 member_validator=_subclass_of(Exception),
607 iterable_validator=instance_of(tuple),
608 ),
609 )
611 def __call__(self, inst, attr, value):
612 try:
613 self.validator(inst, attr, value)
614 except self.exc_types:
615 pass # suppress error to invert validity
616 else:
617 raise ValueError(
618 self.msg.format(
619 validator=self.validator,
620 exc_types=self.exc_types,
621 ),
622 attr,
623 self.validator,
624 value,
625 self.exc_types,
626 )
628 def __repr__(self):
629 return f"<not_ validator wrapping {self.validator!r}, capturing {self.exc_types!r}>"
632def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)):
633 """
634 A validator that wraps and logically 'inverts' the validator passed to it.
635 It will raise a `ValueError` if the provided validator *doesn't* raise a
636 `ValueError` or `TypeError` (by default), and will suppress the exception
637 if the provided validator *does*.
639 Intended to be used with existing validators to compose logic without
640 needing to create inverted variants, for example, ``not_(in_(...))``.
642 Args:
643 validator: A validator to be logically inverted.
645 msg (str):
646 Message to raise if validator fails. Formatted with keys
647 ``exc_types`` and ``validator``.
649 exc_types (tuple[type, ...]):
650 Exception type(s) to capture. Other types raised by child
651 validators will not be intercepted and pass through.
653 Raises:
654 ValueError:
655 With a human readable error message, the attribute (of type
656 `attrs.Attribute`), the validator that failed to raise an
657 exception, the value it got, and the expected exception types.
659 .. versionadded:: 22.2.0
660 """
661 try:
662 exc_types = tuple(exc_types)
663 except TypeError:
664 exc_types = (exc_types,)
665 return _NotValidator(validator, msg, exc_types)
668@attrs(repr=False, slots=True, unsafe_hash=True)
669class _OrValidator:
670 validators = attrib()
672 def __call__(self, inst, attr, value):
673 for v in self.validators:
674 try:
675 v(inst, attr, value)
676 except Exception: # noqa: BLE001, PERF203, S112
677 continue
678 else:
679 return
681 msg = f"None of {self.validators!r} satisfied for value {value!r}"
682 raise ValueError(msg)
684 def __repr__(self):
685 return f"<or validator wrapping {self.validators!r}>"
688def or_(*validators):
689 """
690 A validator that composes multiple validators into one.
692 When called on a value, it runs all wrapped validators until one of them is
693 satisfied.
695 Args:
696 validators (~collections.abc.Iterable[typing.Callable]):
697 Arbitrary number of validators.
699 Raises:
700 ValueError:
701 If no validator is satisfied. Raised with a human-readable error
702 message listing all the wrapped validators and the value that
703 failed all of them.
705 .. versionadded:: 24.1.0
706 """
707 vals = []
708 for v in validators:
709 vals.extend(v.validators if isinstance(v, _OrValidator) else [v])
711 return _OrValidator(tuple(vals))