Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/attr/validators.py: 57%
202 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
1# SPDX-License-Identifier: MIT
3"""
4Commonly useful validators.
5"""
8import operator
9import re
11from contextlib import contextmanager
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
19try:
20 Pattern = re.Pattern
21except AttributeError: # Python <3.7 lacks a Pattern type.
22 Pattern = type(re.compile(""))
25__all__ = [
26 "and_",
27 "deep_iterable",
28 "deep_mapping",
29 "disabled",
30 "ge",
31 "get_disabled",
32 "gt",
33 "in_",
34 "instance_of",
35 "is_callable",
36 "le",
37 "lt",
38 "matches_re",
39 "max_len",
40 "min_len",
41 "not_",
42 "optional",
43 "provides",
44 "set_disabled",
45]
48def set_disabled(disabled):
49 """
50 Globally disable or enable running validators.
52 By default, they are run.
54 :param disabled: If ``True``, disable running all validators.
55 :type disabled: bool
57 .. warning::
59 This function is not thread-safe!
61 .. versionadded:: 21.3.0
62 """
63 set_run_validators(not disabled)
66def get_disabled():
67 """
68 Return a bool indicating whether validators are currently disabled or not.
70 :return: ``True`` if validators are currently disabled.
71 :rtype: bool
73 .. versionadded:: 21.3.0
74 """
75 return not get_run_validators()
78@contextmanager
79def disabled():
80 """
81 Context manager that disables running validators within its context.
83 .. warning::
85 This context manager is not thread-safe!
87 .. versionadded:: 21.3.0
88 """
89 set_run_validators(False)
90 try:
91 yield
92 finally:
93 set_run_validators(True)
96@attrs(repr=False, slots=True, hash=True)
97class _InstanceOfValidator:
98 type = attrib()
100 def __call__(self, inst, attr, value):
101 """
102 We use a callable class to be able to change the ``__repr__``.
103 """
104 if not isinstance(value, self.type):
105 raise TypeError(
106 "'{name}' must be {type!r} (got {value!r} that is a "
107 "{actual!r}).".format(
108 name=attr.name,
109 type=self.type,
110 actual=value.__class__,
111 value=value,
112 ),
113 attr,
114 self.type,
115 value,
116 )
118 def __repr__(self):
119 return "<instance_of validator for type {type!r}>".format(
120 type=self.type
121 )
124def instance_of(type):
125 """
126 A validator that raises a `TypeError` if the initializer is called
127 with a wrong type for this particular attribute (checks are performed using
128 `isinstance` therefore it's also valid to pass a tuple of types).
130 :param type: The type to check for.
131 :type type: type or tuple of type
133 :raises TypeError: With a human readable error message, the attribute
134 (of type `attrs.Attribute`), the expected type, and the value it
135 got.
136 """
137 return _InstanceOfValidator(type)
140@attrs(repr=False, frozen=True, slots=True)
141class _MatchesReValidator:
142 pattern = attrib()
143 match_func = attrib()
145 def __call__(self, inst, attr, value):
146 """
147 We use a callable class to be able to change the ``__repr__``.
148 """
149 if not self.match_func(value):
150 raise ValueError(
151 "'{name}' must match regex {pattern!r}"
152 " ({value!r} doesn't)".format(
153 name=attr.name, pattern=self.pattern.pattern, value=value
154 ),
155 attr,
156 self.pattern,
157 value,
158 )
160 def __repr__(self):
161 return "<matches_re validator for pattern {pattern!r}>".format(
162 pattern=self.pattern
163 )
166def matches_re(regex, flags=0, func=None):
167 r"""
168 A validator that raises `ValueError` if the initializer is called
169 with a string that doesn't match *regex*.
171 :param regex: a regex string or precompiled pattern to match against
172 :param int flags: flags that will be passed to the underlying re function
173 (default 0)
174 :param callable func: which underlying `re` function to call. Valid options
175 are `re.fullmatch`, `re.search`, and `re.match`; the default ``None``
176 means `re.fullmatch`. For performance reasons, the pattern is always
177 precompiled using `re.compile`.
179 .. versionadded:: 19.2.0
180 .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern.
181 """
182 valid_funcs = (re.fullmatch, None, re.search, re.match)
183 if func not in valid_funcs:
184 raise ValueError(
185 "'func' must be one of {}.".format(
186 ", ".join(
187 sorted(
188 e and e.__name__ or "None" for e in set(valid_funcs)
189 )
190 )
191 )
192 )
194 if isinstance(regex, Pattern):
195 if flags:
196 raise TypeError(
197 "'flags' can only be used with a string pattern; "
198 "pass flags to re.compile() instead"
199 )
200 pattern = regex
201 else:
202 pattern = re.compile(regex, flags)
204 if func is re.match:
205 match_func = pattern.match
206 elif func is re.search:
207 match_func = pattern.search
208 else:
209 match_func = pattern.fullmatch
211 return _MatchesReValidator(pattern, match_func)
214@attrs(repr=False, slots=True, hash=True)
215class _ProvidesValidator:
216 interface = attrib()
218 def __call__(self, inst, attr, value):
219 """
220 We use a callable class to be able to change the ``__repr__``.
221 """
222 if not self.interface.providedBy(value):
223 raise TypeError(
224 "'{name}' must provide {interface!r} which {value!r} "
225 "doesn't.".format(
226 name=attr.name, interface=self.interface, value=value
227 ),
228 attr,
229 self.interface,
230 value,
231 )
233 def __repr__(self):
234 return "<provides validator for interface {interface!r}>".format(
235 interface=self.interface
236 )
239def provides(interface):
240 """
241 A validator that raises a `TypeError` if the initializer is called
242 with an object that does not provide the requested *interface* (checks are
243 performed using ``interface.providedBy(value)`` (see `zope.interface
244 <https://zopeinterface.readthedocs.io/en/latest/>`_).
246 :param interface: The interface to check for.
247 :type interface: ``zope.interface.Interface``
249 :raises TypeError: With a human readable error message, the attribute
250 (of type `attrs.Attribute`), the expected interface, and the
251 value it got.
252 """
253 return _ProvidesValidator(interface)
256@attrs(repr=False, slots=True, hash=True)
257class _OptionalValidator:
258 validator = attrib()
260 def __call__(self, inst, attr, value):
261 if value is None:
262 return
264 self.validator(inst, attr, value)
266 def __repr__(self):
267 return "<optional validator for {what} or None>".format(
268 what=repr(self.validator)
269 )
272def optional(validator):
273 """
274 A validator that makes an attribute optional. An optional attribute is one
275 which can be set to ``None`` in addition to satisfying the requirements of
276 the sub-validator.
278 :param validator: A validator (or a list of validators) that is used for
279 non-``None`` values.
280 :type validator: callable or `list` of callables.
282 .. versionadded:: 15.1.0
283 .. versionchanged:: 17.1.0 *validator* can be a list of validators.
284 """
285 if isinstance(validator, list):
286 return _OptionalValidator(_AndValidator(validator))
287 return _OptionalValidator(validator)
290@attrs(repr=False, slots=True, hash=True)
291class _InValidator:
292 options = attrib()
294 def __call__(self, inst, attr, value):
295 try:
296 in_options = value in self.options
297 except TypeError: # e.g. `1 in "abc"`
298 in_options = False
300 if not in_options:
301 raise ValueError(
302 "'{name}' must be in {options!r} (got {value!r})".format(
303 name=attr.name, options=self.options, value=value
304 ),
305 attr,
306 self.options,
307 value,
308 )
310 def __repr__(self):
311 return "<in_ validator with options {options!r}>".format(
312 options=self.options
313 )
316def in_(options):
317 """
318 A validator that raises a `ValueError` if the initializer is called
319 with a value that does not belong in the options provided. The check is
320 performed using ``value in options``.
322 :param options: Allowed options.
323 :type options: list, tuple, `enum.Enum`, ...
325 :raises ValueError: With a human readable error message, the attribute (of
326 type `attrs.Attribute`), the expected options, and the value it
327 got.
329 .. versionadded:: 17.1.0
330 .. versionchanged:: 22.1.0
331 The ValueError was incomplete until now and only contained the human
332 readable error message. Now it contains all the information that has
333 been promised since 17.1.0.
334 """
335 return _InValidator(options)
338@attrs(repr=False, slots=False, hash=True)
339class _IsCallableValidator:
340 def __call__(self, inst, attr, value):
341 """
342 We use a callable class to be able to change the ``__repr__``.
343 """
344 if not callable(value):
345 message = (
346 "'{name}' must be callable "
347 "(got {value!r} that is a {actual!r})."
348 )
349 raise NotCallableError(
350 msg=message.format(
351 name=attr.name, value=value, actual=value.__class__
352 ),
353 value=value,
354 )
356 def __repr__(self):
357 return "<is_callable validator>"
360def is_callable():
361 """
362 A validator that raises a `attr.exceptions.NotCallableError` if the
363 initializer is called with a value for this particular attribute
364 that is not callable.
366 .. versionadded:: 19.1.0
368 :raises `attr.exceptions.NotCallableError`: With a human readable error
369 message containing the attribute (`attrs.Attribute`) name,
370 and the value it got.
371 """
372 return _IsCallableValidator()
375@attrs(repr=False, slots=True, hash=True)
376class _DeepIterable:
377 member_validator = attrib(validator=is_callable())
378 iterable_validator = attrib(
379 default=None, validator=optional(is_callable())
380 )
382 def __call__(self, inst, attr, value):
383 """
384 We use a callable class to be able to change the ``__repr__``.
385 """
386 if self.iterable_validator is not None:
387 self.iterable_validator(inst, attr, value)
389 for member in value:
390 self.member_validator(inst, attr, member)
392 def __repr__(self):
393 iterable_identifier = (
394 ""
395 if self.iterable_validator is None
396 else f" {self.iterable_validator!r}"
397 )
398 return (
399 "<deep_iterable validator for{iterable_identifier}"
400 " iterables of {member!r}>"
401 ).format(
402 iterable_identifier=iterable_identifier,
403 member=self.member_validator,
404 )
407def deep_iterable(member_validator, iterable_validator=None):
408 """
409 A validator that performs deep validation of an iterable.
411 :param member_validator: Validator(s) to apply to iterable members
412 :param iterable_validator: Validator to apply to iterable itself
413 (optional)
415 .. versionadded:: 19.1.0
417 :raises TypeError: if any sub-validators fail
418 """
419 if isinstance(member_validator, (list, tuple)):
420 member_validator = and_(*member_validator)
421 return _DeepIterable(member_validator, iterable_validator)
424@attrs(repr=False, slots=True, hash=True)
425class _DeepMapping:
426 key_validator = attrib(validator=is_callable())
427 value_validator = attrib(validator=is_callable())
428 mapping_validator = attrib(default=None, validator=optional(is_callable()))
430 def __call__(self, inst, attr, value):
431 """
432 We use a callable class to be able to change the ``__repr__``.
433 """
434 if self.mapping_validator is not None:
435 self.mapping_validator(inst, attr, value)
437 for key in value:
438 self.key_validator(inst, attr, key)
439 self.value_validator(inst, attr, value[key])
441 def __repr__(self):
442 return (
443 "<deep_mapping validator for objects mapping {key!r} to {value!r}>"
444 ).format(key=self.key_validator, value=self.value_validator)
447def deep_mapping(key_validator, value_validator, mapping_validator=None):
448 """
449 A validator that performs deep validation of a dictionary.
451 :param key_validator: Validator to apply to dictionary keys
452 :param value_validator: Validator to apply to dictionary values
453 :param mapping_validator: Validator to apply to top-level mapping
454 attribute (optional)
456 .. versionadded:: 19.1.0
458 :raises TypeError: if any sub-validators fail
459 """
460 return _DeepMapping(key_validator, value_validator, mapping_validator)
463@attrs(repr=False, frozen=True, slots=True)
464class _NumberValidator:
465 bound = attrib()
466 compare_op = attrib()
467 compare_func = attrib()
469 def __call__(self, inst, attr, value):
470 """
471 We use a callable class to be able to change the ``__repr__``.
472 """
473 if not self.compare_func(value, self.bound):
474 raise ValueError(
475 "'{name}' must be {op} {bound}: {value}".format(
476 name=attr.name,
477 op=self.compare_op,
478 bound=self.bound,
479 value=value,
480 )
481 )
483 def __repr__(self):
484 return "<Validator for x {op} {bound}>".format(
485 op=self.compare_op, bound=self.bound
486 )
489def lt(val):
490 """
491 A validator that raises `ValueError` if the initializer is called
492 with a number larger or equal to *val*.
494 :param val: Exclusive upper bound for values
496 .. versionadded:: 21.3.0
497 """
498 return _NumberValidator(val, "<", operator.lt)
501def le(val):
502 """
503 A validator that raises `ValueError` if the initializer is called
504 with a number greater than *val*.
506 :param val: Inclusive upper bound for values
508 .. versionadded:: 21.3.0
509 """
510 return _NumberValidator(val, "<=", operator.le)
513def ge(val):
514 """
515 A validator that raises `ValueError` if the initializer is called
516 with a number smaller than *val*.
518 :param val: Inclusive lower bound for values
520 .. versionadded:: 21.3.0
521 """
522 return _NumberValidator(val, ">=", operator.ge)
525def gt(val):
526 """
527 A validator that raises `ValueError` if the initializer is called
528 with a number smaller or equal to *val*.
530 :param val: Exclusive lower bound for values
532 .. versionadded:: 21.3.0
533 """
534 return _NumberValidator(val, ">", operator.gt)
537@attrs(repr=False, frozen=True, slots=True)
538class _MaxLengthValidator:
539 max_length = attrib()
541 def __call__(self, inst, attr, value):
542 """
543 We use a callable class to be able to change the ``__repr__``.
544 """
545 if len(value) > self.max_length:
546 raise ValueError(
547 "Length of '{name}' must be <= {max}: {len}".format(
548 name=attr.name, max=self.max_length, len=len(value)
549 )
550 )
552 def __repr__(self):
553 return f"<max_len validator for {self.max_length}>"
556def max_len(length):
557 """
558 A validator that raises `ValueError` if the initializer is called
559 with a string or iterable that is longer than *length*.
561 :param int length: Maximum length of the string or iterable
563 .. versionadded:: 21.3.0
564 """
565 return _MaxLengthValidator(length)
568@attrs(repr=False, frozen=True, slots=True)
569class _MinLengthValidator:
570 min_length = attrib()
572 def __call__(self, inst, attr, value):
573 """
574 We use a callable class to be able to change the ``__repr__``.
575 """
576 if len(value) < self.min_length:
577 raise ValueError(
578 "Length of '{name}' must be => {min}: {len}".format(
579 name=attr.name, min=self.min_length, len=len(value)
580 )
581 )
583 def __repr__(self):
584 return f"<min_len validator for {self.min_length}>"
587def min_len(length):
588 """
589 A validator that raises `ValueError` if the initializer is called
590 with a string or iterable that is shorter than *length*.
592 :param int length: Minimum length of the string or iterable
594 .. versionadded:: 22.1.0
595 """
596 return _MinLengthValidator(length)
599@attrs(repr=False, slots=True, hash=True)
600class _SubclassOfValidator:
601 type = attrib()
603 def __call__(self, inst, attr, value):
604 """
605 We use a callable class to be able to change the ``__repr__``.
606 """
607 if not issubclass(value, self.type):
608 raise TypeError(
609 "'{name}' must be a subclass of {type!r} "
610 "(got {value!r}).".format(
611 name=attr.name,
612 type=self.type,
613 value=value,
614 ),
615 attr,
616 self.type,
617 value,
618 )
620 def __repr__(self):
621 return "<subclass_of validator for type {type!r}>".format(
622 type=self.type
623 )
626def _subclass_of(type):
627 """
628 A validator that raises a `TypeError` if the initializer is called
629 with a wrong type for this particular attribute (checks are performed using
630 `issubclass` therefore it's also valid to pass a tuple of types).
632 :param type: The type to check for.
633 :type type: type or tuple of types
635 :raises TypeError: With a human readable error message, the attribute
636 (of type `attrs.Attribute`), the expected type, and the value it
637 got.
638 """
639 return _SubclassOfValidator(type)
642@attrs(repr=False, slots=True, hash=True)
643class _NotValidator:
644 validator = attrib()
645 msg = attrib(
646 converter=default_if_none(
647 "not_ validator child '{validator!r}' "
648 "did not raise a captured error"
649 )
650 )
651 exc_types = attrib(
652 validator=deep_iterable(
653 member_validator=_subclass_of(Exception),
654 iterable_validator=instance_of(tuple),
655 ),
656 )
658 def __call__(self, inst, attr, value):
659 try:
660 self.validator(inst, attr, value)
661 except self.exc_types:
662 pass # suppress error to invert validity
663 else:
664 raise ValueError(
665 self.msg.format(
666 validator=self.validator,
667 exc_types=self.exc_types,
668 ),
669 attr,
670 self.validator,
671 value,
672 self.exc_types,
673 )
675 def __repr__(self):
676 return (
677 "<not_ validator wrapping {what!r}, " "capturing {exc_types!r}>"
678 ).format(
679 what=self.validator,
680 exc_types=self.exc_types,
681 )
684def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)):
685 """
686 A validator that wraps and logically 'inverts' the validator passed to it.
687 It will raise a `ValueError` if the provided validator *doesn't* raise a
688 `ValueError` or `TypeError` (by default), and will suppress the exception
689 if the provided validator *does*.
691 Intended to be used with existing validators to compose logic without
692 needing to create inverted variants, for example, ``not_(in_(...))``.
694 :param validator: A validator to be logically inverted.
695 :param msg: Message to raise if validator fails.
696 Formatted with keys ``exc_types`` and ``validator``.
697 :type msg: str
698 :param exc_types: Exception type(s) to capture.
699 Other types raised by child validators will not be intercepted and
700 pass through.
702 :raises ValueError: With a human readable error message,
703 the attribute (of type `attrs.Attribute`),
704 the validator that failed to raise an exception,
705 the value it got,
706 and the expected exception types.
708 .. versionadded:: 22.2.0
709 """
710 try:
711 exc_types = tuple(exc_types)
712 except TypeError:
713 exc_types = (exc_types,)
714 return _NotValidator(validator, msg, exc_types)