validators.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. # SPDX-License-Identifier: MIT
  2. """
  3. Commonly useful validators.
  4. """
  5. import operator
  6. import re
  7. from contextlib import contextmanager
  8. from re import Pattern
  9. from ._config import get_run_validators, set_run_validators
  10. from ._make import _AndValidator, and_, attrib, attrs
  11. from .converters import default_if_none
  12. from .exceptions import NotCallableError
  13. __all__ = [
  14. "and_",
  15. "deep_iterable",
  16. "deep_mapping",
  17. "disabled",
  18. "ge",
  19. "get_disabled",
  20. "gt",
  21. "in_",
  22. "instance_of",
  23. "is_callable",
  24. "le",
  25. "lt",
  26. "matches_re",
  27. "max_len",
  28. "min_len",
  29. "not_",
  30. "optional",
  31. "or_",
  32. "set_disabled",
  33. ]
  34. def set_disabled(disabled):
  35. """
  36. Globally disable or enable running validators.
  37. By default, they are run.
  38. Args:
  39. disabled (bool): If `True`, disable running all validators.
  40. .. warning::
  41. This function is not thread-safe!
  42. .. versionadded:: 21.3.0
  43. """
  44. set_run_validators(not disabled)
  45. def get_disabled():
  46. """
  47. Return a bool indicating whether validators are currently disabled or not.
  48. Returns:
  49. bool:`True` if validators are currently disabled.
  50. .. versionadded:: 21.3.0
  51. """
  52. return not get_run_validators()
  53. @contextmanager
  54. def disabled():
  55. """
  56. Context manager that disables running validators within its context.
  57. .. warning::
  58. This context manager is not thread-safe!
  59. .. versionadded:: 21.3.0
  60. .. versionchanged:: 26.1.0 The contextmanager is nestable.
  61. """
  62. prev = get_run_validators()
  63. set_run_validators(False)
  64. try:
  65. yield
  66. finally:
  67. set_run_validators(prev)
  68. @attrs(repr=False, slots=True, unsafe_hash=True)
  69. class _InstanceOfValidator:
  70. type = attrib()
  71. def __call__(self, inst, attr, value):
  72. """
  73. We use a callable class to be able to change the ``__repr__``.
  74. """
  75. if not isinstance(value, self.type):
  76. msg = f"'{attr.name}' must be {self.type!r} (got {value!r} that is a {value.__class__!r})."
  77. raise TypeError(
  78. msg,
  79. attr,
  80. self.type,
  81. value,
  82. )
  83. def __repr__(self):
  84. return f"<instance_of validator for type {self.type!r}>"
  85. def instance_of(type):
  86. """
  87. A validator that raises a `TypeError` if the initializer is called with a
  88. wrong type for this particular attribute (checks are performed using
  89. `isinstance` therefore it's also valid to pass a tuple of types).
  90. Args:
  91. type (type | tuple[type]): The type to check for.
  92. Raises:
  93. TypeError:
  94. With a human readable error message, the attribute (of type
  95. `attrs.Attribute`), the expected type, and the value it got.
  96. """
  97. return _InstanceOfValidator(type)
  98. @attrs(repr=False, frozen=True, slots=True)
  99. class _MatchesReValidator:
  100. pattern = attrib()
  101. match_func = attrib()
  102. def __call__(self, inst, attr, value):
  103. """
  104. We use a callable class to be able to change the ``__repr__``.
  105. """
  106. if not self.match_func(value):
  107. msg = f"'{attr.name}' must match regex {self.pattern.pattern!r} ({value!r} doesn't)"
  108. raise ValueError(
  109. msg,
  110. attr,
  111. self.pattern,
  112. value,
  113. )
  114. def __repr__(self):
  115. return f"<matches_re validator for pattern {self.pattern!r}>"
  116. def matches_re(regex, flags=0, func=None):
  117. r"""
  118. A validator that raises `ValueError` if the initializer is called with a
  119. string that doesn't match *regex*.
  120. Args:
  121. regex (str, re.Pattern):
  122. A regex string or precompiled pattern to match against
  123. flags (int):
  124. Flags that will be passed to the underlying re function (default 0)
  125. func (typing.Callable):
  126. Which underlying `re` function to call. Valid options are
  127. `re.fullmatch`, `re.search`, and `re.match`; the default `None`
  128. means `re.fullmatch`. For performance reasons, the pattern is
  129. always precompiled using `re.compile`.
  130. .. versionadded:: 19.2.0
  131. .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern.
  132. """
  133. valid_funcs = (re.fullmatch, None, re.search, re.match)
  134. if func not in valid_funcs:
  135. msg = "'func' must be one of {}.".format(
  136. ", ".join(
  137. sorted((e and e.__name__) or "None" for e in set(valid_funcs))
  138. )
  139. )
  140. raise ValueError(msg)
  141. if isinstance(regex, Pattern):
  142. if flags:
  143. msg = "'flags' can only be used with a string pattern; pass flags to re.compile() instead"
  144. raise TypeError(msg)
  145. pattern = regex
  146. else:
  147. pattern = re.compile(regex, flags)
  148. if func is re.match:
  149. match_func = pattern.match
  150. elif func is re.search:
  151. match_func = pattern.search
  152. else:
  153. match_func = pattern.fullmatch
  154. return _MatchesReValidator(pattern, match_func)
  155. @attrs(repr=False, slots=True, unsafe_hash=True)
  156. class _OptionalValidator:
  157. validator = attrib()
  158. def __call__(self, inst, attr, value):
  159. if value is None:
  160. return
  161. self.validator(inst, attr, value)
  162. def __repr__(self):
  163. return f"<optional validator for {self.validator!r} or None>"
  164. def optional(validator):
  165. """
  166. A validator that makes an attribute optional. An optional attribute is one
  167. which can be set to `None` in addition to satisfying the requirements of
  168. the sub-validator.
  169. Args:
  170. validator
  171. (typing.Callable | tuple[typing.Callable] | list[typing.Callable]):
  172. A validator (or validators) that is used for non-`None` values.
  173. .. versionadded:: 15.1.0
  174. .. versionchanged:: 17.1.0 *validator* can be a list of validators.
  175. .. versionchanged:: 23.1.0 *validator* can also be a tuple of validators.
  176. """
  177. if isinstance(validator, (list, tuple)):
  178. return _OptionalValidator(_AndValidator(validator))
  179. return _OptionalValidator(validator)
  180. @attrs(repr=False, slots=True, unsafe_hash=True)
  181. class _InValidator:
  182. options = attrib()
  183. _original_options = attrib(hash=False)
  184. def __call__(self, inst, attr, value):
  185. try:
  186. in_options = value in self.options
  187. except TypeError: # e.g. `1 in "abc"`
  188. in_options = False
  189. if not in_options:
  190. msg = f"'{attr.name}' must be in {self._original_options!r} (got {value!r})"
  191. raise ValueError(
  192. msg,
  193. attr,
  194. self._original_options,
  195. value,
  196. )
  197. def __repr__(self):
  198. return f"<in_ validator with options {self._original_options!r}>"
  199. def in_(options):
  200. """
  201. A validator that raises a `ValueError` if the initializer is called with a
  202. value that does not belong in the *options* provided.
  203. The check is performed using ``value in options``, so *options* has to
  204. support that operation.
  205. To keep the validator hashable, dicts, lists, and sets are transparently
  206. transformed into a `tuple`.
  207. Args:
  208. options: Allowed options.
  209. Raises:
  210. ValueError:
  211. With a human readable error message, the attribute (of type
  212. `attrs.Attribute`), the expected options, and the value it got.
  213. .. versionadded:: 17.1.0
  214. .. versionchanged:: 22.1.0
  215. The ValueError was incomplete until now and only contained the human
  216. readable error message. Now it contains all the information that has
  217. been promised since 17.1.0.
  218. .. versionchanged:: 24.1.0
  219. *options* that are a list, dict, or a set are now transformed into a
  220. tuple to keep the validator hashable.
  221. """
  222. repr_options = options
  223. if isinstance(options, (list, dict, set)):
  224. options = tuple(options)
  225. return _InValidator(options, repr_options)
  226. @attrs(repr=False, slots=False, unsafe_hash=True)
  227. class _IsCallableValidator:
  228. def __call__(self, inst, attr, value):
  229. """
  230. We use a callable class to be able to change the ``__repr__``.
  231. """
  232. if not callable(value):
  233. message = (
  234. "'{name}' must be callable "
  235. "(got {value!r} that is a {actual!r})."
  236. )
  237. raise NotCallableError(
  238. msg=message.format(
  239. name=attr.name, value=value, actual=value.__class__
  240. ),
  241. value=value,
  242. )
  243. def __repr__(self):
  244. return "<is_callable validator>"
  245. def is_callable():
  246. """
  247. A validator that raises a `attrs.exceptions.NotCallableError` if the
  248. initializer is called with a value for this particular attribute that is
  249. not callable.
  250. .. versionadded:: 19.1.0
  251. Raises:
  252. attrs.exceptions.NotCallableError:
  253. With a human readable error message containing the attribute
  254. (`attrs.Attribute`) name, and the value it got.
  255. """
  256. return _IsCallableValidator()
  257. @attrs(repr=False, slots=True, unsafe_hash=True)
  258. class _DeepIterable:
  259. member_validator = attrib(validator=is_callable())
  260. iterable_validator = attrib(
  261. default=None, validator=optional(is_callable())
  262. )
  263. def __call__(self, inst, attr, value):
  264. """
  265. We use a callable class to be able to change the ``__repr__``.
  266. """
  267. if self.iterable_validator is not None:
  268. self.iterable_validator(inst, attr, value)
  269. for member in value:
  270. self.member_validator(inst, attr, member)
  271. def __repr__(self):
  272. iterable_identifier = (
  273. ""
  274. if self.iterable_validator is None
  275. else f" {self.iterable_validator!r}"
  276. )
  277. return (
  278. f"<deep_iterable validator for{iterable_identifier}"
  279. f" iterables of {self.member_validator!r}>"
  280. )
  281. def deep_iterable(member_validator, iterable_validator=None):
  282. """
  283. A validator that performs deep validation of an iterable.
  284. Args:
  285. member_validator: Validator(s) to apply to iterable members.
  286. iterable_validator:
  287. Validator(s) to apply to iterable itself (optional).
  288. Raises
  289. TypeError: if any sub-validators fail
  290. .. versionadded:: 19.1.0
  291. .. versionchanged:: 25.4.0
  292. *member_validator* and *iterable_validator* can now be a list or tuple
  293. of validators.
  294. """
  295. if isinstance(member_validator, (list, tuple)):
  296. member_validator = and_(*member_validator)
  297. if isinstance(iterable_validator, (list, tuple)):
  298. iterable_validator = and_(*iterable_validator)
  299. return _DeepIterable(member_validator, iterable_validator)
  300. @attrs(repr=False, slots=True, unsafe_hash=True)
  301. class _DeepMapping:
  302. key_validator = attrib(validator=optional(is_callable()))
  303. value_validator = attrib(validator=optional(is_callable()))
  304. mapping_validator = attrib(validator=optional(is_callable()))
  305. def __call__(self, inst, attr, value):
  306. """
  307. We use a callable class to be able to change the ``__repr__``.
  308. """
  309. if self.mapping_validator is not None:
  310. self.mapping_validator(inst, attr, value)
  311. for key in value:
  312. if self.key_validator is not None:
  313. self.key_validator(inst, attr, key)
  314. if self.value_validator is not None:
  315. self.value_validator(inst, attr, value[key])
  316. def __repr__(self):
  317. return f"<deep_mapping validator for objects mapping {self.key_validator!r} to {self.value_validator!r}>"
  318. def deep_mapping(
  319. key_validator=None, value_validator=None, mapping_validator=None
  320. ):
  321. """
  322. A validator that performs deep validation of a dictionary.
  323. All validators are optional, but at least one of *key_validator* or
  324. *value_validator* must be provided.
  325. Args:
  326. key_validator: Validator(s) to apply to dictionary keys.
  327. value_validator: Validator(s) to apply to dictionary values.
  328. mapping_validator:
  329. Validator(s) to apply to top-level mapping attribute.
  330. .. versionadded:: 19.1.0
  331. .. versionchanged:: 25.4.0
  332. *key_validator* and *value_validator* are now optional, but at least one
  333. of them must be provided.
  334. .. versionchanged:: 25.4.0
  335. *key_validator*, *value_validator*, and *mapping_validator* can now be a
  336. list or tuple of validators.
  337. Raises:
  338. TypeError: If any sub-validator fails on validation.
  339. ValueError:
  340. If neither *key_validator* nor *value_validator* is provided on
  341. instantiation.
  342. """
  343. if key_validator is None and value_validator is None:
  344. msg = (
  345. "At least one of key_validator or value_validator must be provided"
  346. )
  347. raise ValueError(msg)
  348. if isinstance(key_validator, (list, tuple)):
  349. key_validator = and_(*key_validator)
  350. if isinstance(value_validator, (list, tuple)):
  351. value_validator = and_(*value_validator)
  352. if isinstance(mapping_validator, (list, tuple)):
  353. mapping_validator = and_(*mapping_validator)
  354. return _DeepMapping(key_validator, value_validator, mapping_validator)
  355. @attrs(repr=False, frozen=True, slots=True)
  356. class _NumberValidator:
  357. bound = attrib()
  358. compare_op = attrib()
  359. compare_func = attrib()
  360. def __call__(self, inst, attr, value):
  361. """
  362. We use a callable class to be able to change the ``__repr__``.
  363. """
  364. if not self.compare_func(value, self.bound):
  365. msg = f"'{attr.name}' must be {self.compare_op} {self.bound}: {value}"
  366. raise ValueError(msg)
  367. def __repr__(self):
  368. return f"<Validator for x {self.compare_op} {self.bound}>"
  369. def lt(val):
  370. """
  371. A validator that raises `ValueError` if the initializer is called with a
  372. number larger or equal to *val*.
  373. The validator uses `operator.lt` to compare the values.
  374. Args:
  375. val: Exclusive upper bound for values.
  376. .. versionadded:: 21.3.0
  377. """
  378. return _NumberValidator(val, "<", operator.lt)
  379. def le(val):
  380. """
  381. A validator that raises `ValueError` if the initializer is called with a
  382. number greater than *val*.
  383. The validator uses `operator.le` to compare the values.
  384. Args:
  385. val: Inclusive upper bound for values.
  386. .. versionadded:: 21.3.0
  387. """
  388. return _NumberValidator(val, "<=", operator.le)
  389. def ge(val):
  390. """
  391. A validator that raises `ValueError` if the initializer is called with a
  392. number smaller than *val*.
  393. The validator uses `operator.ge` to compare the values.
  394. Args:
  395. val: Inclusive lower bound for values
  396. .. versionadded:: 21.3.0
  397. """
  398. return _NumberValidator(val, ">=", operator.ge)
  399. def gt(val):
  400. """
  401. A validator that raises `ValueError` if the initializer is called with a
  402. number smaller or equal to *val*.
  403. The validator uses `operator.gt` to compare the values.
  404. Args:
  405. val: Exclusive lower bound for values
  406. .. versionadded:: 21.3.0
  407. """
  408. return _NumberValidator(val, ">", operator.gt)
  409. @attrs(repr=False, frozen=True, slots=True)
  410. class _MaxLengthValidator:
  411. max_length = attrib()
  412. def __call__(self, inst, attr, value):
  413. """
  414. We use a callable class to be able to change the ``__repr__``.
  415. """
  416. if len(value) > self.max_length:
  417. msg = f"Length of '{attr.name}' must be <= {self.max_length}: {len(value)}"
  418. raise ValueError(msg)
  419. def __repr__(self):
  420. return f"<max_len validator for {self.max_length}>"
  421. def max_len(length):
  422. """
  423. A validator that raises `ValueError` if the initializer is called
  424. with a string or iterable that is longer than *length*.
  425. Args:
  426. length (int): Maximum length of the string or iterable
  427. .. versionadded:: 21.3.0
  428. """
  429. return _MaxLengthValidator(length)
  430. @attrs(repr=False, frozen=True, slots=True)
  431. class _MinLengthValidator:
  432. min_length = attrib()
  433. def __call__(self, inst, attr, value):
  434. """
  435. We use a callable class to be able to change the ``__repr__``.
  436. """
  437. if len(value) < self.min_length:
  438. msg = f"Length of '{attr.name}' must be >= {self.min_length}: {len(value)}"
  439. raise ValueError(msg)
  440. def __repr__(self):
  441. return f"<min_len validator for {self.min_length}>"
  442. def min_len(length):
  443. """
  444. A validator that raises `ValueError` if the initializer is called
  445. with a string or iterable that is shorter than *length*.
  446. Args:
  447. length (int): Minimum length of the string or iterable
  448. .. versionadded:: 22.1.0
  449. """
  450. return _MinLengthValidator(length)
  451. @attrs(repr=False, slots=True, unsafe_hash=True)
  452. class _SubclassOfValidator:
  453. type = attrib()
  454. def __call__(self, inst, attr, value):
  455. """
  456. We use a callable class to be able to change the ``__repr__``.
  457. """
  458. if not issubclass(value, self.type):
  459. msg = f"'{attr.name}' must be a subclass of {self.type!r} (got {value!r})."
  460. raise TypeError(
  461. msg,
  462. attr,
  463. self.type,
  464. value,
  465. )
  466. def __repr__(self):
  467. return f"<subclass_of validator for type {self.type!r}>"
  468. def _subclass_of(type):
  469. """
  470. A validator that raises a `TypeError` if the initializer is called with a
  471. wrong type for this particular attribute (checks are performed using
  472. `issubclass` therefore it's also valid to pass a tuple of types).
  473. Args:
  474. type (type | tuple[type, ...]): The type(s) to check for.
  475. Raises:
  476. TypeError:
  477. With a human readable error message, the attribute (of type
  478. `attrs.Attribute`), the expected type, and the value it got.
  479. """
  480. return _SubclassOfValidator(type)
  481. @attrs(repr=False, slots=True, unsafe_hash=True)
  482. class _NotValidator:
  483. validator = attrib()
  484. msg = attrib(
  485. converter=default_if_none(
  486. "not_ validator child '{validator!r}' "
  487. "did not raise a captured error"
  488. )
  489. )
  490. exc_types = attrib(
  491. validator=deep_iterable(
  492. member_validator=_subclass_of(Exception),
  493. iterable_validator=instance_of(tuple),
  494. ),
  495. )
  496. def __call__(self, inst, attr, value):
  497. try:
  498. self.validator(inst, attr, value)
  499. except self.exc_types:
  500. pass # suppress error to invert validity
  501. else:
  502. raise ValueError(
  503. self.msg.format(
  504. validator=self.validator,
  505. exc_types=self.exc_types,
  506. ),
  507. attr,
  508. self.validator,
  509. value,
  510. self.exc_types,
  511. )
  512. def __repr__(self):
  513. return f"<not_ validator wrapping {self.validator!r}, capturing {self.exc_types!r}>"
  514. def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)):
  515. """
  516. A validator that wraps and logically 'inverts' the validator passed to it.
  517. It will raise a `ValueError` if the provided validator *doesn't* raise a
  518. `ValueError` or `TypeError` (by default), and will suppress the exception
  519. if the provided validator *does*.
  520. Intended to be used with existing validators to compose logic without
  521. needing to create inverted variants, for example, ``not_(in_(...))``.
  522. Args:
  523. validator: A validator to be logically inverted.
  524. msg (str):
  525. Message to raise if validator fails. Formatted with keys
  526. ``exc_types`` and ``validator``.
  527. exc_types (tuple[type, ...]):
  528. Exception type(s) to capture. Other types raised by child
  529. validators will not be intercepted and pass through.
  530. Raises:
  531. ValueError:
  532. With a human readable error message, the attribute (of type
  533. `attrs.Attribute`), the validator that failed to raise an
  534. exception, the value it got, and the expected exception types.
  535. .. versionadded:: 22.2.0
  536. """
  537. try:
  538. exc_types = tuple(exc_types)
  539. except TypeError:
  540. exc_types = (exc_types,)
  541. return _NotValidator(validator, msg, exc_types)
  542. @attrs(repr=False, slots=True, unsafe_hash=True)
  543. class _OrValidator:
  544. validators = attrib()
  545. def __call__(self, inst, attr, value):
  546. for v in self.validators:
  547. try:
  548. v(inst, attr, value)
  549. except Exception: # noqa: BLE001, PERF203, S112
  550. continue
  551. else:
  552. return
  553. msg = f"None of {self.validators!r} satisfied for value {value!r}"
  554. raise ValueError(msg)
  555. def __repr__(self):
  556. return f"<or validator wrapping {self.validators!r}>"
  557. def or_(*validators):
  558. """
  559. A validator that composes multiple validators into one.
  560. When called on a value, it runs all wrapped validators until one of them is
  561. satisfied.
  562. Args:
  563. validators (~collections.abc.Iterable[typing.Callable]):
  564. Arbitrary number of validators.
  565. Raises:
  566. ValueError:
  567. If no validator is satisfied. Raised with a human-readable error
  568. message listing all the wrapped validators and the value that
  569. failed all of them.
  570. .. versionadded:: 24.1.0
  571. """
  572. vals = []
  573. for v in validators:
  574. vals.extend(v.validators if isinstance(v, _OrValidator) else [v])
  575. return _OrValidator(tuple(vals))