Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/attr/validators.py: 53%
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 "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 msg = "'{name}' must be {type!r} (got {value!r} that is a {actual!r}).".format(
101 name=attr.name,
102 type=self.type,
103 actual=value.__class__,
104 value=value,
105 )
106 raise TypeError(
107 msg,
108 attr,
109 self.type,
110 value,
111 )
113 def __repr__(self):
114 return f"<instance_of validator for type {self.type!r}>"
117def instance_of(type):
118 """
119 A validator that raises a `TypeError` if the initializer is called
120 with a wrong type for this particular attribute (checks are performed using
121 `isinstance` therefore it's also valid to pass a tuple of types).
123 :param type: The type to check for.
124 :type type: type or tuple of type
126 :raises TypeError: With a human readable error message, the attribute
127 (of type `attrs.Attribute`), the expected type, and the value it
128 got.
129 """
130 return _InstanceOfValidator(type)
133@attrs(repr=False, frozen=True, slots=True)
134class _MatchesReValidator:
135 pattern = attrib()
136 match_func = attrib()
138 def __call__(self, inst, attr, value):
139 """
140 We use a callable class to be able to change the ``__repr__``.
141 """
142 if not self.match_func(value):
143 msg = "'{name}' must match regex {pattern!r} ({value!r} doesn't)".format(
144 name=attr.name, pattern=self.pattern.pattern, value=value
145 )
146 raise ValueError(
147 msg,
148 attr,
149 self.pattern,
150 value,
151 )
153 def __repr__(self):
154 return f"<matches_re validator for pattern {self.pattern!r}>"
157def matches_re(regex, flags=0, func=None):
158 r"""
159 A validator that raises `ValueError` if the initializer is called
160 with a string that doesn't match *regex*.
162 :param regex: a regex string or precompiled pattern to match against
163 :param int flags: flags that will be passed to the underlying re function
164 (default 0)
165 :param callable func: which underlying `re` function to call. Valid options
166 are `re.fullmatch`, `re.search`, and `re.match`; the default ``None``
167 means `re.fullmatch`. For performance reasons, the pattern is always
168 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, hash=True)
201class _ProvidesValidator:
202 interface = attrib()
204 def __call__(self, inst, attr, value):
205 """
206 We use a callable class to be able to change the ``__repr__``.
207 """
208 if not self.interface.providedBy(value):
209 msg = "'{name}' must provide {interface!r} which {value!r} doesn't.".format(
210 name=attr.name, interface=self.interface, value=value
211 )
212 raise TypeError(
213 msg,
214 attr,
215 self.interface,
216 value,
217 )
219 def __repr__(self):
220 return f"<provides validator for interface {self.interface!r}>"
223def provides(interface):
224 """
225 A validator that raises a `TypeError` if the initializer is called
226 with an object that does not provide the requested *interface* (checks are
227 performed using ``interface.providedBy(value)`` (see `zope.interface
228 <https://zopeinterface.readthedocs.io/en/latest/>`_).
230 :param interface: The interface to check for.
231 :type interface: ``zope.interface.Interface``
233 :raises TypeError: With a human readable error message, the attribute
234 (of type `attrs.Attribute`), the expected interface, and the
235 value it got.
237 .. deprecated:: 23.1.0
238 """
239 import warnings
241 warnings.warn(
242 "attrs's zope-interface support is deprecated and will be removed in, "
243 "or after, April 2024.",
244 DeprecationWarning,
245 stacklevel=2,
246 )
247 return _ProvidesValidator(interface)
250@attrs(repr=False, slots=True, hash=True)
251class _OptionalValidator:
252 validator = attrib()
254 def __call__(self, inst, attr, value):
255 if value is None:
256 return
258 self.validator(inst, attr, value)
260 def __repr__(self):
261 return f"<optional validator for {self.validator!r} or None>"
264def optional(validator):
265 """
266 A validator that makes an attribute optional. An optional attribute is one
267 which can be set to ``None`` in addition to satisfying the requirements of
268 the sub-validator.
270 :param Callable | tuple[Callable] | list[Callable] validator: A validator
271 (or validators) that is used for non-``None`` values.
273 .. versionadded:: 15.1.0
274 .. versionchanged:: 17.1.0 *validator* can be a list of validators.
275 .. versionchanged:: 23.1.0 *validator* can also be a tuple of validators.
276 """
277 if isinstance(validator, (list, tuple)):
278 return _OptionalValidator(_AndValidator(validator))
280 return _OptionalValidator(validator)
283@attrs(repr=False, slots=True, hash=True)
284class _InValidator:
285 options = attrib()
287 def __call__(self, inst, attr, value):
288 try:
289 in_options = value in self.options
290 except TypeError: # e.g. `1 in "abc"`
291 in_options = False
293 if not in_options:
294 msg = f"'{attr.name}' must be in {self.options!r} (got {value!r})"
295 raise ValueError(
296 msg,
297 attr,
298 self.options,
299 value,
300 )
302 def __repr__(self):
303 return f"<in_ validator with options {self.options!r}>"
306def in_(options):
307 """
308 A validator that raises a `ValueError` if the initializer is called
309 with a value that does not belong in the options provided. The check is
310 performed using ``value in options``.
312 :param options: Allowed options.
313 :type options: list, tuple, `enum.Enum`, ...
315 :raises ValueError: With a human readable error message, the attribute (of
316 type `attrs.Attribute`), the expected options, and the value it
317 got.
319 .. versionadded:: 17.1.0
320 .. versionchanged:: 22.1.0
321 The ValueError was incomplete until now and only contained the human
322 readable error message. Now it contains all the information that has
323 been promised since 17.1.0.
324 """
325 return _InValidator(options)
328@attrs(repr=False, slots=False, hash=True)
329class _IsCallableValidator:
330 def __call__(self, inst, attr, value):
331 """
332 We use a callable class to be able to change the ``__repr__``.
333 """
334 if not callable(value):
335 message = (
336 "'{name}' must be callable "
337 "(got {value!r} that is a {actual!r})."
338 )
339 raise NotCallableError(
340 msg=message.format(
341 name=attr.name, value=value, actual=value.__class__
342 ),
343 value=value,
344 )
346 def __repr__(self):
347 return "<is_callable validator>"
350def is_callable():
351 """
352 A validator that raises a `attrs.exceptions.NotCallableError` if the
353 initializer is called with a value for this particular attribute
354 that is not callable.
356 .. versionadded:: 19.1.0
358 :raises attrs.exceptions.NotCallableError: With a human readable error
359 message containing the attribute (`attrs.Attribute`) name,
360 and the value it got.
361 """
362 return _IsCallableValidator()
365@attrs(repr=False, slots=True, hash=True)
366class _DeepIterable:
367 member_validator = attrib(validator=is_callable())
368 iterable_validator = attrib(
369 default=None, validator=optional(is_callable())
370 )
372 def __call__(self, inst, attr, value):
373 """
374 We use a callable class to be able to change the ``__repr__``.
375 """
376 if self.iterable_validator is not None:
377 self.iterable_validator(inst, attr, value)
379 for member in value:
380 self.member_validator(inst, attr, member)
382 def __repr__(self):
383 iterable_identifier = (
384 ""
385 if self.iterable_validator is None
386 else f" {self.iterable_validator!r}"
387 )
388 return (
389 f"<deep_iterable validator for{iterable_identifier}"
390 f" iterables of {self.member_validator!r}>"
391 )
394def deep_iterable(member_validator, iterable_validator=None):
395 """
396 A validator that performs deep validation of an iterable.
398 :param member_validator: Validator(s) to apply to iterable members
399 :param iterable_validator: Validator to apply to iterable itself
400 (optional)
402 .. versionadded:: 19.1.0
404 :raises TypeError: if any sub-validators fail
405 """
406 if isinstance(member_validator, (list, tuple)):
407 member_validator = and_(*member_validator)
408 return _DeepIterable(member_validator, iterable_validator)
411@attrs(repr=False, slots=True, hash=True)
412class _DeepMapping:
413 key_validator = attrib(validator=is_callable())
414 value_validator = attrib(validator=is_callable())
415 mapping_validator = attrib(default=None, validator=optional(is_callable()))
417 def __call__(self, inst, attr, value):
418 """
419 We use a callable class to be able to change the ``__repr__``.
420 """
421 if self.mapping_validator is not None:
422 self.mapping_validator(inst, attr, value)
424 for key in value:
425 self.key_validator(inst, attr, key)
426 self.value_validator(inst, attr, value[key])
428 def __repr__(self):
429 return (
430 "<deep_mapping validator for objects mapping {key!r} to {value!r}>"
431 ).format(key=self.key_validator, value=self.value_validator)
434def deep_mapping(key_validator, value_validator, mapping_validator=None):
435 """
436 A validator that performs deep validation of a dictionary.
438 :param key_validator: Validator to apply to dictionary keys
439 :param value_validator: Validator to apply to dictionary values
440 :param mapping_validator: Validator to apply to top-level mapping
441 attribute (optional)
443 .. versionadded:: 19.1.0
445 :raises TypeError: if any sub-validators fail
446 """
447 return _DeepMapping(key_validator, value_validator, mapping_validator)
450@attrs(repr=False, frozen=True, slots=True)
451class _NumberValidator:
452 bound = attrib()
453 compare_op = attrib()
454 compare_func = attrib()
456 def __call__(self, inst, attr, value):
457 """
458 We use a callable class to be able to change the ``__repr__``.
459 """
460 if not self.compare_func(value, self.bound):
461 msg = f"'{attr.name}' must be {self.compare_op} {self.bound}: {value}"
462 raise ValueError(msg)
464 def __repr__(self):
465 return f"<Validator for x {self.compare_op} {self.bound}>"
468def lt(val):
469 """
470 A validator that raises `ValueError` if the initializer is called
471 with a number larger or equal to *val*.
473 :param val: Exclusive upper bound for values
475 .. versionadded:: 21.3.0
476 """
477 return _NumberValidator(val, "<", operator.lt)
480def le(val):
481 """
482 A validator that raises `ValueError` if the initializer is called
483 with a number greater than *val*.
485 :param val: Inclusive upper bound for values
487 .. versionadded:: 21.3.0
488 """
489 return _NumberValidator(val, "<=", operator.le)
492def ge(val):
493 """
494 A validator that raises `ValueError` if the initializer is called
495 with a number smaller than *val*.
497 :param val: Inclusive lower bound for values
499 .. versionadded:: 21.3.0
500 """
501 return _NumberValidator(val, ">=", operator.ge)
504def gt(val):
505 """
506 A validator that raises `ValueError` if the initializer is called
507 with a number smaller or equal to *val*.
509 :param val: Exclusive lower bound for values
511 .. versionadded:: 21.3.0
512 """
513 return _NumberValidator(val, ">", operator.gt)
516@attrs(repr=False, frozen=True, slots=True)
517class _MaxLengthValidator:
518 max_length = attrib()
520 def __call__(self, inst, attr, value):
521 """
522 We use a callable class to be able to change the ``__repr__``.
523 """
524 if len(value) > self.max_length:
525 msg = f"Length of '{attr.name}' must be <= {self.max_length}: {len(value)}"
526 raise ValueError(msg)
528 def __repr__(self):
529 return f"<max_len validator for {self.max_length}>"
532def max_len(length):
533 """
534 A validator that raises `ValueError` if the initializer is called
535 with a string or iterable that is longer than *length*.
537 :param int length: Maximum length of the string or iterable
539 .. versionadded:: 21.3.0
540 """
541 return _MaxLengthValidator(length)
544@attrs(repr=False, frozen=True, slots=True)
545class _MinLengthValidator:
546 min_length = attrib()
548 def __call__(self, inst, attr, value):
549 """
550 We use a callable class to be able to change the ``__repr__``.
551 """
552 if len(value) < self.min_length:
553 msg = f"Length of '{attr.name}' must be >= {self.min_length}: {len(value)}"
554 raise ValueError(msg)
556 def __repr__(self):
557 return f"<min_len validator for {self.min_length}>"
560def min_len(length):
561 """
562 A validator that raises `ValueError` if the initializer is called
563 with a string or iterable that is shorter than *length*.
565 :param int length: Minimum length of the string or iterable
567 .. versionadded:: 22.1.0
568 """
569 return _MinLengthValidator(length)
572@attrs(repr=False, slots=True, hash=True)
573class _SubclassOfValidator:
574 type = attrib()
576 def __call__(self, inst, attr, value):
577 """
578 We use a callable class to be able to change the ``__repr__``.
579 """
580 if not issubclass(value, self.type):
581 msg = f"'{attr.name}' must be a subclass of {self.type!r} (got {value!r})."
582 raise TypeError(
583 msg,
584 attr,
585 self.type,
586 value,
587 )
589 def __repr__(self):
590 return f"<subclass_of validator for type {self.type!r}>"
593def _subclass_of(type):
594 """
595 A validator that raises a `TypeError` if the initializer is called
596 with a wrong type for this particular attribute (checks are performed using
597 `issubclass` therefore it's also valid to pass a tuple of types).
599 :param type: The type to check for.
600 :type type: type or tuple of types
602 :raises TypeError: With a human readable error message, the attribute
603 (of type `attrs.Attribute`), the expected type, and the value it
604 got.
605 """
606 return _SubclassOfValidator(type)
609@attrs(repr=False, slots=True, hash=True)
610class _NotValidator:
611 validator = attrib()
612 msg = attrib(
613 converter=default_if_none(
614 "not_ validator child '{validator!r}' "
615 "did not raise a captured error"
616 )
617 )
618 exc_types = attrib(
619 validator=deep_iterable(
620 member_validator=_subclass_of(Exception),
621 iterable_validator=instance_of(tuple),
622 ),
623 )
625 def __call__(self, inst, attr, value):
626 try:
627 self.validator(inst, attr, value)
628 except self.exc_types:
629 pass # suppress error to invert validity
630 else:
631 raise ValueError(
632 self.msg.format(
633 validator=self.validator,
634 exc_types=self.exc_types,
635 ),
636 attr,
637 self.validator,
638 value,
639 self.exc_types,
640 )
642 def __repr__(self):
643 return (
644 "<not_ validator wrapping {what!r}, capturing {exc_types!r}>"
645 ).format(
646 what=self.validator,
647 exc_types=self.exc_types,
648 )
651def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)):
652 """
653 A validator that wraps and logically 'inverts' the validator passed to it.
654 It will raise a `ValueError` if the provided validator *doesn't* raise a
655 `ValueError` or `TypeError` (by default), and will suppress the exception
656 if the provided validator *does*.
658 Intended to be used with existing validators to compose logic without
659 needing to create inverted variants, for example, ``not_(in_(...))``.
661 :param validator: A validator to be logically inverted.
662 :param msg: Message to raise if validator fails.
663 Formatted with keys ``exc_types`` and ``validator``.
664 :type msg: str
665 :param exc_types: Exception type(s) to capture.
666 Other types raised by child validators will not be intercepted and
667 pass through.
669 :raises ValueError: With a human readable error message,
670 the attribute (of type `attrs.Attribute`),
671 the validator that failed to raise an exception,
672 the value it got,
673 and the expected exception types.
675 .. versionadded:: 22.2.0
676 """
677 try:
678 exc_types = tuple(exc_types)
679 except TypeError:
680 exc_types = (exc_types,)
681 return _NotValidator(validator, msg, exc_types)