Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/attr/validators.py: 56%
201 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
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 "provides",
39 "set_disabled",
40]
43def set_disabled(disabled):
44 """
45 Globally disable or enable running validators.
47 By default, they are run.
49 :param disabled: If ``True``, disable running all validators.
50 :type disabled: bool
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 :return: ``True`` if validators are currently disabled.
66 :rtype: bool
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, 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 raise TypeError(
101 "'{name}' must be {type!r} (got {value!r} that is a "
102 "{actual!r}).".format(
103 name=attr.name,
104 type=self.type,
105 actual=value.__class__,
106 value=value,
107 ),
108 attr,
109 self.type,
110 value,
111 )
113 def __repr__(self):
114 return "<instance_of validator for type {type!r}>".format(
115 type=self.type
116 )
119def instance_of(type):
120 """
121 A validator that raises a `TypeError` if the initializer is called
122 with a wrong type for this particular attribute (checks are performed using
123 `isinstance` therefore it's also valid to pass a tuple of types).
125 :param type: The type to check for.
126 :type type: type or tuple of type
128 :raises TypeError: With a human readable error message, the attribute
129 (of type `attrs.Attribute`), the expected type, and the value it
130 got.
131 """
132 return _InstanceOfValidator(type)
135@attrs(repr=False, frozen=True, slots=True)
136class _MatchesReValidator:
137 pattern = attrib()
138 match_func = attrib()
140 def __call__(self, inst, attr, value):
141 """
142 We use a callable class to be able to change the ``__repr__``.
143 """
144 if not self.match_func(value):
145 raise ValueError(
146 "'{name}' must match regex {pattern!r}"
147 " ({value!r} doesn't)".format(
148 name=attr.name, pattern=self.pattern.pattern, value=value
149 ),
150 attr,
151 self.pattern,
152 value,
153 )
155 def __repr__(self):
156 return "<matches_re validator for pattern {pattern!r}>".format(
157 pattern=self.pattern
158 )
161def matches_re(regex, flags=0, func=None):
162 r"""
163 A validator that raises `ValueError` if the initializer is called
164 with a string that doesn't match *regex*.
166 :param regex: a regex string or precompiled pattern to match against
167 :param int flags: flags that will be passed to the underlying re function
168 (default 0)
169 :param callable func: which underlying `re` function to call. Valid options
170 are `re.fullmatch`, `re.search`, and `re.match`; the default ``None``
171 means `re.fullmatch`. For performance reasons, the pattern is always
172 precompiled using `re.compile`.
174 .. versionadded:: 19.2.0
175 .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern.
176 """
177 valid_funcs = (re.fullmatch, None, re.search, re.match)
178 if func not in valid_funcs:
179 raise ValueError(
180 "'func' must be one of {}.".format(
181 ", ".join(
182 sorted(
183 e and e.__name__ or "None" for e in set(valid_funcs)
184 )
185 )
186 )
187 )
189 if isinstance(regex, Pattern):
190 if flags:
191 raise TypeError(
192 "'flags' can only be used with a string pattern; "
193 "pass flags to re.compile() instead"
194 )
195 pattern = regex
196 else:
197 pattern = re.compile(regex, flags)
199 if func is re.match:
200 match_func = pattern.match
201 elif func is re.search:
202 match_func = pattern.search
203 else:
204 match_func = pattern.fullmatch
206 return _MatchesReValidator(pattern, match_func)
209@attrs(repr=False, slots=True, hash=True)
210class _ProvidesValidator:
211 interface = attrib()
213 def __call__(self, inst, attr, value):
214 """
215 We use a callable class to be able to change the ``__repr__``.
216 """
217 if not self.interface.providedBy(value):
218 raise TypeError(
219 "'{name}' must provide {interface!r} which {value!r} "
220 "doesn't.".format(
221 name=attr.name, interface=self.interface, value=value
222 ),
223 attr,
224 self.interface,
225 value,
226 )
228 def __repr__(self):
229 return "<provides validator for interface {interface!r}>".format(
230 interface=self.interface
231 )
234def provides(interface):
235 """
236 A validator that raises a `TypeError` if the initializer is called
237 with an object that does not provide the requested *interface* (checks are
238 performed using ``interface.providedBy(value)`` (see `zope.interface
239 <https://zopeinterface.readthedocs.io/en/latest/>`_).
241 :param interface: The interface to check for.
242 :type interface: ``zope.interface.Interface``
244 :raises TypeError: With a human readable error message, the attribute
245 (of type `attrs.Attribute`), the expected interface, and the
246 value it got.
248 .. deprecated:: 23.1.0
249 """
250 import warnings
252 warnings.warn(
253 "attrs's zope-interface support is deprecated and will be removed in, "
254 "or after, April 2024.",
255 DeprecationWarning,
256 stacklevel=2,
257 )
258 return _ProvidesValidator(interface)
261@attrs(repr=False, slots=True, hash=True)
262class _OptionalValidator:
263 validator = attrib()
265 def __call__(self, inst, attr, value):
266 if value is None:
267 return
269 self.validator(inst, attr, value)
271 def __repr__(self):
272 return "<optional validator for {what} or None>".format(
273 what=repr(self.validator)
274 )
277def optional(validator):
278 """
279 A validator that makes an attribute optional. An optional attribute is one
280 which can be set to ``None`` in addition to satisfying the requirements of
281 the sub-validator.
283 :param Callable | tuple[Callable] | list[Callable] validator: A validator
284 (or validators) that is used for non-``None`` values.
286 .. versionadded:: 15.1.0
287 .. versionchanged:: 17.1.0 *validator* can be a list of validators.
288 .. versionchanged:: 23.1.0 *validator* can also be a tuple of validators.
289 """
290 if isinstance(validator, (list, tuple)):
291 return _OptionalValidator(_AndValidator(validator))
293 return _OptionalValidator(validator)
296@attrs(repr=False, slots=True, hash=True)
297class _InValidator:
298 options = attrib()
300 def __call__(self, inst, attr, value):
301 try:
302 in_options = value in self.options
303 except TypeError: # e.g. `1 in "abc"`
304 in_options = False
306 if not in_options:
307 raise ValueError(
308 "'{name}' must be in {options!r} (got {value!r})".format(
309 name=attr.name, options=self.options, value=value
310 ),
311 attr,
312 self.options,
313 value,
314 )
316 def __repr__(self):
317 return "<in_ validator with options {options!r}>".format(
318 options=self.options
319 )
322def in_(options):
323 """
324 A validator that raises a `ValueError` if the initializer is called
325 with a value that does not belong in the options provided. The check is
326 performed using ``value in options``.
328 :param options: Allowed options.
329 :type options: list, tuple, `enum.Enum`, ...
331 :raises ValueError: With a human readable error message, the attribute (of
332 type `attrs.Attribute`), the expected options, and the value it
333 got.
335 .. versionadded:: 17.1.0
336 .. versionchanged:: 22.1.0
337 The ValueError was incomplete until now and only contained the human
338 readable error message. Now it contains all the information that has
339 been promised since 17.1.0.
340 """
341 return _InValidator(options)
344@attrs(repr=False, slots=False, hash=True)
345class _IsCallableValidator:
346 def __call__(self, inst, attr, value):
347 """
348 We use a callable class to be able to change the ``__repr__``.
349 """
350 if not callable(value):
351 message = (
352 "'{name}' must be callable "
353 "(got {value!r} that is a {actual!r})."
354 )
355 raise NotCallableError(
356 msg=message.format(
357 name=attr.name, value=value, actual=value.__class__
358 ),
359 value=value,
360 )
362 def __repr__(self):
363 return "<is_callable validator>"
366def is_callable():
367 """
368 A validator that raises a `attrs.exceptions.NotCallableError` if the
369 initializer is called with a value for this particular attribute
370 that is not callable.
372 .. versionadded:: 19.1.0
374 :raises attrs.exceptions.NotCallableError: With a human readable error
375 message containing the attribute (`attrs.Attribute`) name,
376 and the value it got.
377 """
378 return _IsCallableValidator()
381@attrs(repr=False, slots=True, hash=True)
382class _DeepIterable:
383 member_validator = attrib(validator=is_callable())
384 iterable_validator = attrib(
385 default=None, validator=optional(is_callable())
386 )
388 def __call__(self, inst, attr, value):
389 """
390 We use a callable class to be able to change the ``__repr__``.
391 """
392 if self.iterable_validator is not None:
393 self.iterable_validator(inst, attr, value)
395 for member in value:
396 self.member_validator(inst, attr, member)
398 def __repr__(self):
399 iterable_identifier = (
400 ""
401 if self.iterable_validator is None
402 else f" {self.iterable_validator!r}"
403 )
404 return (
405 "<deep_iterable validator for{iterable_identifier}"
406 " iterables of {member!r}>"
407 ).format(
408 iterable_identifier=iterable_identifier,
409 member=self.member_validator,
410 )
413def deep_iterable(member_validator, iterable_validator=None):
414 """
415 A validator that performs deep validation of an iterable.
417 :param member_validator: Validator(s) to apply to iterable members
418 :param iterable_validator: Validator to apply to iterable itself
419 (optional)
421 .. versionadded:: 19.1.0
423 :raises TypeError: if any sub-validators fail
424 """
425 if isinstance(member_validator, (list, tuple)):
426 member_validator = and_(*member_validator)
427 return _DeepIterable(member_validator, iterable_validator)
430@attrs(repr=False, slots=True, hash=True)
431class _DeepMapping:
432 key_validator = attrib(validator=is_callable())
433 value_validator = attrib(validator=is_callable())
434 mapping_validator = attrib(default=None, validator=optional(is_callable()))
436 def __call__(self, inst, attr, value):
437 """
438 We use a callable class to be able to change the ``__repr__``.
439 """
440 if self.mapping_validator is not None:
441 self.mapping_validator(inst, attr, value)
443 for key in value:
444 self.key_validator(inst, attr, key)
445 self.value_validator(inst, attr, value[key])
447 def __repr__(self):
448 return (
449 "<deep_mapping validator for objects mapping {key!r} to {value!r}>"
450 ).format(key=self.key_validator, value=self.value_validator)
453def deep_mapping(key_validator, value_validator, mapping_validator=None):
454 """
455 A validator that performs deep validation of a dictionary.
457 :param key_validator: Validator to apply to dictionary keys
458 :param value_validator: Validator to apply to dictionary values
459 :param mapping_validator: Validator to apply to top-level mapping
460 attribute (optional)
462 .. versionadded:: 19.1.0
464 :raises TypeError: if any sub-validators fail
465 """
466 return _DeepMapping(key_validator, value_validator, mapping_validator)
469@attrs(repr=False, frozen=True, slots=True)
470class _NumberValidator:
471 bound = attrib()
472 compare_op = attrib()
473 compare_func = attrib()
475 def __call__(self, inst, attr, value):
476 """
477 We use a callable class to be able to change the ``__repr__``.
478 """
479 if not self.compare_func(value, self.bound):
480 raise ValueError(
481 "'{name}' must be {op} {bound}: {value}".format(
482 name=attr.name,
483 op=self.compare_op,
484 bound=self.bound,
485 value=value,
486 )
487 )
489 def __repr__(self):
490 return "<Validator for x {op} {bound}>".format(
491 op=self.compare_op, bound=self.bound
492 )
495def lt(val):
496 """
497 A validator that raises `ValueError` if the initializer is called
498 with a number larger or equal to *val*.
500 :param val: Exclusive upper bound for values
502 .. versionadded:: 21.3.0
503 """
504 return _NumberValidator(val, "<", operator.lt)
507def le(val):
508 """
509 A validator that raises `ValueError` if the initializer is called
510 with a number greater than *val*.
512 :param val: Inclusive upper bound for values
514 .. versionadded:: 21.3.0
515 """
516 return _NumberValidator(val, "<=", operator.le)
519def ge(val):
520 """
521 A validator that raises `ValueError` if the initializer is called
522 with a number smaller than *val*.
524 :param val: Inclusive lower bound for values
526 .. versionadded:: 21.3.0
527 """
528 return _NumberValidator(val, ">=", operator.ge)
531def gt(val):
532 """
533 A validator that raises `ValueError` if the initializer is called
534 with a number smaller or equal to *val*.
536 :param val: Exclusive lower bound for values
538 .. versionadded:: 21.3.0
539 """
540 return _NumberValidator(val, ">", operator.gt)
543@attrs(repr=False, frozen=True, slots=True)
544class _MaxLengthValidator:
545 max_length = attrib()
547 def __call__(self, inst, attr, value):
548 """
549 We use a callable class to be able to change the ``__repr__``.
550 """
551 if len(value) > self.max_length:
552 raise ValueError(
553 "Length of '{name}' must be <= {max}: {len}".format(
554 name=attr.name, max=self.max_length, len=len(value)
555 )
556 )
558 def __repr__(self):
559 return f"<max_len validator for {self.max_length}>"
562def max_len(length):
563 """
564 A validator that raises `ValueError` if the initializer is called
565 with a string or iterable that is longer than *length*.
567 :param int length: Maximum length of the string or iterable
569 .. versionadded:: 21.3.0
570 """
571 return _MaxLengthValidator(length)
574@attrs(repr=False, frozen=True, slots=True)
575class _MinLengthValidator:
576 min_length = attrib()
578 def __call__(self, inst, attr, value):
579 """
580 We use a callable class to be able to change the ``__repr__``.
581 """
582 if len(value) < self.min_length:
583 raise ValueError(
584 "Length of '{name}' must be => {min}: {len}".format(
585 name=attr.name, min=self.min_length, len=len(value)
586 )
587 )
589 def __repr__(self):
590 return f"<min_len validator for {self.min_length}>"
593def min_len(length):
594 """
595 A validator that raises `ValueError` if the initializer is called
596 with a string or iterable that is shorter than *length*.
598 :param int length: Minimum length of the string or iterable
600 .. versionadded:: 22.1.0
601 """
602 return _MinLengthValidator(length)
605@attrs(repr=False, slots=True, hash=True)
606class _SubclassOfValidator:
607 type = attrib()
609 def __call__(self, inst, attr, value):
610 """
611 We use a callable class to be able to change the ``__repr__``.
612 """
613 if not issubclass(value, self.type):
614 raise TypeError(
615 "'{name}' must be a subclass of {type!r} "
616 "(got {value!r}).".format(
617 name=attr.name,
618 type=self.type,
619 value=value,
620 ),
621 attr,
622 self.type,
623 value,
624 )
626 def __repr__(self):
627 return "<subclass_of validator for type {type!r}>".format(
628 type=self.type
629 )
632def _subclass_of(type):
633 """
634 A validator that raises a `TypeError` if the initializer is called
635 with a wrong type for this particular attribute (checks are performed using
636 `issubclass` therefore it's also valid to pass a tuple of types).
638 :param type: The type to check for.
639 :type type: type or tuple of types
641 :raises TypeError: With a human readable error message, the attribute
642 (of type `attrs.Attribute`), the expected type, and the value it
643 got.
644 """
645 return _SubclassOfValidator(type)
648@attrs(repr=False, slots=True, hash=True)
649class _NotValidator:
650 validator = attrib()
651 msg = attrib(
652 converter=default_if_none(
653 "not_ validator child '{validator!r}' "
654 "did not raise a captured error"
655 )
656 )
657 exc_types = attrib(
658 validator=deep_iterable(
659 member_validator=_subclass_of(Exception),
660 iterable_validator=instance_of(tuple),
661 ),
662 )
664 def __call__(self, inst, attr, value):
665 try:
666 self.validator(inst, attr, value)
667 except self.exc_types:
668 pass # suppress error to invert validity
669 else:
670 raise ValueError(
671 self.msg.format(
672 validator=self.validator,
673 exc_types=self.exc_types,
674 ),
675 attr,
676 self.validator,
677 value,
678 self.exc_types,
679 )
681 def __repr__(self):
682 return (
683 "<not_ validator wrapping {what!r}, " "capturing {exc_types!r}>"
684 ).format(
685 what=self.validator,
686 exc_types=self.exc_types,
687 )
690def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)):
691 """
692 A validator that wraps and logically 'inverts' the validator passed to it.
693 It will raise a `ValueError` if the provided validator *doesn't* raise a
694 `ValueError` or `TypeError` (by default), and will suppress the exception
695 if the provided validator *does*.
697 Intended to be used with existing validators to compose logic without
698 needing to create inverted variants, for example, ``not_(in_(...))``.
700 :param validator: A validator to be logically inverted.
701 :param msg: Message to raise if validator fails.
702 Formatted with keys ``exc_types`` and ``validator``.
703 :type msg: str
704 :param exc_types: Exception type(s) to capture.
705 Other types raised by child validators will not be intercepted and
706 pass through.
708 :raises ValueError: With a human readable error message,
709 the attribute (of type `attrs.Attribute`),
710 the validator that failed to raise an exception,
711 the value it got,
712 and the expected exception types.
714 .. versionadded:: 22.2.0
715 """
716 try:
717 exc_types = tuple(exc_types)
718 except TypeError:
719 exc_types = (exc_types,)
720 return _NotValidator(validator, msg, exc_types)