validators.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  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. "provides",
  32. "set_disabled",
  33. ]
  34. def set_disabled(disabled):
  35. """
  36. Globally disable or enable running validators.
  37. By default, they are run.
  38. :param disabled: If ``True``, disable running all validators.
  39. :type disabled: bool
  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. :return: ``True`` if validators are currently disabled.
  49. :rtype: bool
  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. """
  61. set_run_validators(False)
  62. try:
  63. yield
  64. finally:
  65. set_run_validators(True)
  66. @attrs(repr=False, slots=True, hash=True)
  67. class _InstanceOfValidator:
  68. type = attrib()
  69. def __call__(self, inst, attr, value):
  70. """
  71. We use a callable class to be able to change the ``__repr__``.
  72. """
  73. if not isinstance(value, self.type):
  74. msg = "'{name}' must be {type!r} (got {value!r} that is a {actual!r}).".format(
  75. name=attr.name,
  76. type=self.type,
  77. actual=value.__class__,
  78. value=value,
  79. )
  80. raise TypeError(
  81. msg,
  82. attr,
  83. self.type,
  84. value,
  85. )
  86. def __repr__(self):
  87. return f"<instance_of validator for type {self.type!r}>"
  88. def instance_of(type):
  89. """
  90. A validator that raises a `TypeError` if the initializer is called
  91. with a wrong type for this particular attribute (checks are performed using
  92. `isinstance` therefore it's also valid to pass a tuple of types).
  93. :param type: The type to check for.
  94. :type type: type or tuple of type
  95. :raises TypeError: With a human readable error message, the attribute
  96. (of type `attrs.Attribute`), the expected type, and the value it
  97. got.
  98. """
  99. return _InstanceOfValidator(type)
  100. @attrs(repr=False, frozen=True, slots=True)
  101. class _MatchesReValidator:
  102. pattern = attrib()
  103. match_func = attrib()
  104. def __call__(self, inst, attr, value):
  105. """
  106. We use a callable class to be able to change the ``__repr__``.
  107. """
  108. if not self.match_func(value):
  109. msg = "'{name}' must match regex {pattern!r} ({value!r} doesn't)".format(
  110. name=attr.name, pattern=self.pattern.pattern, value=value
  111. )
  112. raise ValueError(
  113. msg,
  114. attr,
  115. self.pattern,
  116. value,
  117. )
  118. def __repr__(self):
  119. return f"<matches_re validator for pattern {self.pattern!r}>"
  120. def matches_re(regex, flags=0, func=None):
  121. r"""
  122. A validator that raises `ValueError` if the initializer is called
  123. with a string that doesn't match *regex*.
  124. :param regex: a regex string or precompiled pattern to match against
  125. :param int flags: flags that will be passed to the underlying re function
  126. (default 0)
  127. :param callable func: which underlying `re` function to call. Valid options
  128. are `re.fullmatch`, `re.search`, and `re.match`; the default ``None``
  129. means `re.fullmatch`. For performance reasons, the pattern is always
  130. precompiled using `re.compile`.
  131. .. versionadded:: 19.2.0
  132. .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern.
  133. """
  134. valid_funcs = (re.fullmatch, None, re.search, re.match)
  135. if func not in valid_funcs:
  136. msg = "'func' must be one of {}.".format(
  137. ", ".join(
  138. sorted(e and e.__name__ or "None" for e in set(valid_funcs))
  139. )
  140. )
  141. raise ValueError(msg)
  142. if isinstance(regex, Pattern):
  143. if flags:
  144. msg = "'flags' can only be used with a string pattern; pass flags to re.compile() instead"
  145. raise TypeError(msg)
  146. pattern = regex
  147. else:
  148. pattern = re.compile(regex, flags)
  149. if func is re.match:
  150. match_func = pattern.match
  151. elif func is re.search:
  152. match_func = pattern.search
  153. else:
  154. match_func = pattern.fullmatch
  155. return _MatchesReValidator(pattern, match_func)
  156. @attrs(repr=False, slots=True, hash=True)
  157. class _ProvidesValidator:
  158. interface = attrib()
  159. def __call__(self, inst, attr, value):
  160. """
  161. We use a callable class to be able to change the ``__repr__``.
  162. """
  163. if not self.interface.providedBy(value):
  164. msg = "'{name}' must provide {interface!r} which {value!r} doesn't.".format(
  165. name=attr.name, interface=self.interface, value=value
  166. )
  167. raise TypeError(
  168. msg,
  169. attr,
  170. self.interface,
  171. value,
  172. )
  173. def __repr__(self):
  174. return f"<provides validator for interface {self.interface!r}>"
  175. def provides(interface):
  176. """
  177. A validator that raises a `TypeError` if the initializer is called
  178. with an object that does not provide the requested *interface* (checks are
  179. performed using ``interface.providedBy(value)`` (see `zope.interface
  180. <https://zopeinterface.readthedocs.io/en/latest/>`_).
  181. :param interface: The interface to check for.
  182. :type interface: ``zope.interface.Interface``
  183. :raises TypeError: With a human readable error message, the attribute
  184. (of type `attrs.Attribute`), the expected interface, and the
  185. value it got.
  186. .. deprecated:: 23.1.0
  187. """
  188. import warnings
  189. warnings.warn(
  190. "attrs's zope-interface support is deprecated and will be removed in, "
  191. "or after, April 2024.",
  192. DeprecationWarning,
  193. stacklevel=2,
  194. )
  195. return _ProvidesValidator(interface)
  196. @attrs(repr=False, slots=True, hash=True)
  197. class _OptionalValidator:
  198. validator = attrib()
  199. def __call__(self, inst, attr, value):
  200. if value is None:
  201. return
  202. self.validator(inst, attr, value)
  203. def __repr__(self):
  204. return f"<optional validator for {self.validator!r} or None>"
  205. def optional(validator):
  206. """
  207. A validator that makes an attribute optional. An optional attribute is one
  208. which can be set to ``None`` in addition to satisfying the requirements of
  209. the sub-validator.
  210. :param Callable | tuple[Callable] | list[Callable] validator: A validator
  211. (or validators) that is used for non-``None`` values.
  212. .. versionadded:: 15.1.0
  213. .. versionchanged:: 17.1.0 *validator* can be a list of validators.
  214. .. versionchanged:: 23.1.0 *validator* can also be a tuple of validators.
  215. """
  216. if isinstance(validator, (list, tuple)):
  217. return _OptionalValidator(_AndValidator(validator))
  218. return _OptionalValidator(validator)
  219. @attrs(repr=False, slots=True, hash=True)
  220. class _InValidator:
  221. options = attrib()
  222. def __call__(self, inst, attr, value):
  223. try:
  224. in_options = value in self.options
  225. except TypeError: # e.g. `1 in "abc"`
  226. in_options = False
  227. if not in_options:
  228. msg = f"'{attr.name}' must be in {self.options!r} (got {value!r})"
  229. raise ValueError(
  230. msg,
  231. attr,
  232. self.options,
  233. value,
  234. )
  235. def __repr__(self):
  236. return f"<in_ validator with options {self.options!r}>"
  237. def in_(options):
  238. """
  239. A validator that raises a `ValueError` if the initializer is called
  240. with a value that does not belong in the options provided. The check is
  241. performed using ``value in options``.
  242. :param options: Allowed options.
  243. :type options: list, tuple, `enum.Enum`, ...
  244. :raises ValueError: With a human readable error message, the attribute (of
  245. type `attrs.Attribute`), the expected options, and the value it
  246. got.
  247. .. versionadded:: 17.1.0
  248. .. versionchanged:: 22.1.0
  249. The ValueError was incomplete until now and only contained the human
  250. readable error message. Now it contains all the information that has
  251. been promised since 17.1.0.
  252. """
  253. return _InValidator(options)
  254. @attrs(repr=False, slots=False, hash=True)
  255. class _IsCallableValidator:
  256. def __call__(self, inst, attr, value):
  257. """
  258. We use a callable class to be able to change the ``__repr__``.
  259. """
  260. if not callable(value):
  261. message = (
  262. "'{name}' must be callable "
  263. "(got {value!r} that is a {actual!r})."
  264. )
  265. raise NotCallableError(
  266. msg=message.format(
  267. name=attr.name, value=value, actual=value.__class__
  268. ),
  269. value=value,
  270. )
  271. def __repr__(self):
  272. return "<is_callable validator>"
  273. def is_callable():
  274. """
  275. A validator that raises a `attrs.exceptions.NotCallableError` if the
  276. initializer is called with a value for this particular attribute
  277. that is not callable.
  278. .. versionadded:: 19.1.0
  279. :raises attrs.exceptions.NotCallableError: With a human readable error
  280. message containing the attribute (`attrs.Attribute`) name,
  281. and the value it got.
  282. """
  283. return _IsCallableValidator()
  284. @attrs(repr=False, slots=True, hash=True)
  285. class _DeepIterable:
  286. member_validator = attrib(validator=is_callable())
  287. iterable_validator = attrib(
  288. default=None, validator=optional(is_callable())
  289. )
  290. def __call__(self, inst, attr, value):
  291. """
  292. We use a callable class to be able to change the ``__repr__``.
  293. """
  294. if self.iterable_validator is not None:
  295. self.iterable_validator(inst, attr, value)
  296. for member in value:
  297. self.member_validator(inst, attr, member)
  298. def __repr__(self):
  299. iterable_identifier = (
  300. ""
  301. if self.iterable_validator is None
  302. else f" {self.iterable_validator!r}"
  303. )
  304. return (
  305. f"<deep_iterable validator for{iterable_identifier}"
  306. f" iterables of {self.member_validator!r}>"
  307. )
  308. def deep_iterable(member_validator, iterable_validator=None):
  309. """
  310. A validator that performs deep validation of an iterable.
  311. :param member_validator: Validator(s) to apply to iterable members
  312. :param iterable_validator: Validator to apply to iterable itself
  313. (optional)
  314. .. versionadded:: 19.1.0
  315. :raises TypeError: if any sub-validators fail
  316. """
  317. if isinstance(member_validator, (list, tuple)):
  318. member_validator = and_(*member_validator)
  319. return _DeepIterable(member_validator, iterable_validator)
  320. @attrs(repr=False, slots=True, hash=True)
  321. class _DeepMapping:
  322. key_validator = attrib(validator=is_callable())
  323. value_validator = attrib(validator=is_callable())
  324. mapping_validator = attrib(default=None, validator=optional(is_callable()))
  325. def __call__(self, inst, attr, value):
  326. """
  327. We use a callable class to be able to change the ``__repr__``.
  328. """
  329. if self.mapping_validator is not None:
  330. self.mapping_validator(inst, attr, value)
  331. for key in value:
  332. self.key_validator(inst, attr, key)
  333. self.value_validator(inst, attr, value[key])
  334. def __repr__(self):
  335. return (
  336. "<deep_mapping validator for objects mapping {key!r} to {value!r}>"
  337. ).format(key=self.key_validator, value=self.value_validator)
  338. def deep_mapping(key_validator, value_validator, mapping_validator=None):
  339. """
  340. A validator that performs deep validation of a dictionary.
  341. :param key_validator: Validator to apply to dictionary keys
  342. :param value_validator: Validator to apply to dictionary values
  343. :param mapping_validator: Validator to apply to top-level mapping
  344. attribute (optional)
  345. .. versionadded:: 19.1.0
  346. :raises TypeError: if any sub-validators fail
  347. """
  348. return _DeepMapping(key_validator, value_validator, mapping_validator)
  349. @attrs(repr=False, frozen=True, slots=True)
  350. class _NumberValidator:
  351. bound = attrib()
  352. compare_op = attrib()
  353. compare_func = attrib()
  354. def __call__(self, inst, attr, value):
  355. """
  356. We use a callable class to be able to change the ``__repr__``.
  357. """
  358. if not self.compare_func(value, self.bound):
  359. msg = f"'{attr.name}' must be {self.compare_op} {self.bound}: {value}"
  360. raise ValueError(msg)
  361. def __repr__(self):
  362. return f"<Validator for x {self.compare_op} {self.bound}>"
  363. def lt(val):
  364. """
  365. A validator that raises `ValueError` if the initializer is called
  366. with a number larger or equal to *val*.
  367. :param val: Exclusive upper bound for values
  368. .. versionadded:: 21.3.0
  369. """
  370. return _NumberValidator(val, "<", operator.lt)
  371. def le(val):
  372. """
  373. A validator that raises `ValueError` if the initializer is called
  374. with a number greater than *val*.
  375. :param val: Inclusive upper bound for values
  376. .. versionadded:: 21.3.0
  377. """
  378. return _NumberValidator(val, "<=", operator.le)
  379. def ge(val):
  380. """
  381. A validator that raises `ValueError` if the initializer is called
  382. with a number smaller than *val*.
  383. :param val: Inclusive lower bound for values
  384. .. versionadded:: 21.3.0
  385. """
  386. return _NumberValidator(val, ">=", operator.ge)
  387. def gt(val):
  388. """
  389. A validator that raises `ValueError` if the initializer is called
  390. with a number smaller or equal to *val*.
  391. :param val: Exclusive lower bound for values
  392. .. versionadded:: 21.3.0
  393. """
  394. return _NumberValidator(val, ">", operator.gt)
  395. @attrs(repr=False, frozen=True, slots=True)
  396. class _MaxLengthValidator:
  397. max_length = attrib()
  398. def __call__(self, inst, attr, value):
  399. """
  400. We use a callable class to be able to change the ``__repr__``.
  401. """
  402. if len(value) > self.max_length:
  403. msg = f"Length of '{attr.name}' must be <= {self.max_length}: {len(value)}"
  404. raise ValueError(msg)
  405. def __repr__(self):
  406. return f"<max_len validator for {self.max_length}>"
  407. def max_len(length):
  408. """
  409. A validator that raises `ValueError` if the initializer is called
  410. with a string or iterable that is longer than *length*.
  411. :param int length: Maximum length of the string or iterable
  412. .. versionadded:: 21.3.0
  413. """
  414. return _MaxLengthValidator(length)
  415. @attrs(repr=False, frozen=True, slots=True)
  416. class _MinLengthValidator:
  417. min_length = attrib()
  418. def __call__(self, inst, attr, value):
  419. """
  420. We use a callable class to be able to change the ``__repr__``.
  421. """
  422. if len(value) < self.min_length:
  423. msg = f"Length of '{attr.name}' must be >= {self.min_length}: {len(value)}"
  424. raise ValueError(msg)
  425. def __repr__(self):
  426. return f"<min_len validator for {self.min_length}>"
  427. def min_len(length):
  428. """
  429. A validator that raises `ValueError` if the initializer is called
  430. with a string or iterable that is shorter than *length*.
  431. :param int length: Minimum length of the string or iterable
  432. .. versionadded:: 22.1.0
  433. """
  434. return _MinLengthValidator(length)
  435. @attrs(repr=False, slots=True, hash=True)
  436. class _SubclassOfValidator:
  437. type = attrib()
  438. def __call__(self, inst, attr, value):
  439. """
  440. We use a callable class to be able to change the ``__repr__``.
  441. """
  442. if not issubclass(value, self.type):
  443. msg = f"'{attr.name}' must be a subclass of {self.type!r} (got {value!r})."
  444. raise TypeError(
  445. msg,
  446. attr,
  447. self.type,
  448. value,
  449. )
  450. def __repr__(self):
  451. return f"<subclass_of validator for type {self.type!r}>"
  452. def _subclass_of(type):
  453. """
  454. A validator that raises a `TypeError` if the initializer is called
  455. with a wrong type for this particular attribute (checks are performed using
  456. `issubclass` therefore it's also valid to pass a tuple of types).
  457. :param type: The type to check for.
  458. :type type: type or tuple of types
  459. :raises TypeError: With a human readable error message, the attribute
  460. (of type `attrs.Attribute`), the expected type, and the value it
  461. got.
  462. """
  463. return _SubclassOfValidator(type)
  464. @attrs(repr=False, slots=True, hash=True)
  465. class _NotValidator:
  466. validator = attrib()
  467. msg = attrib(
  468. converter=default_if_none(
  469. "not_ validator child '{validator!r}' "
  470. "did not raise a captured error"
  471. )
  472. )
  473. exc_types = attrib(
  474. validator=deep_iterable(
  475. member_validator=_subclass_of(Exception),
  476. iterable_validator=instance_of(tuple),
  477. ),
  478. )
  479. def __call__(self, inst, attr, value):
  480. try:
  481. self.validator(inst, attr, value)
  482. except self.exc_types:
  483. pass # suppress error to invert validity
  484. else:
  485. raise ValueError(
  486. self.msg.format(
  487. validator=self.validator,
  488. exc_types=self.exc_types,
  489. ),
  490. attr,
  491. self.validator,
  492. value,
  493. self.exc_types,
  494. )
  495. def __repr__(self):
  496. return (
  497. "<not_ validator wrapping {what!r}, capturing {exc_types!r}>"
  498. ).format(
  499. what=self.validator,
  500. exc_types=self.exc_types,
  501. )
  502. def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)):
  503. """
  504. A validator that wraps and logically 'inverts' the validator passed to it.
  505. It will raise a `ValueError` if the provided validator *doesn't* raise a
  506. `ValueError` or `TypeError` (by default), and will suppress the exception
  507. if the provided validator *does*.
  508. Intended to be used with existing validators to compose logic without
  509. needing to create inverted variants, for example, ``not_(in_(...))``.
  510. :param validator: A validator to be logically inverted.
  511. :param msg: Message to raise if validator fails.
  512. Formatted with keys ``exc_types`` and ``validator``.
  513. :type msg: str
  514. :param exc_types: Exception type(s) to capture.
  515. Other types raised by child validators will not be intercepted and
  516. pass through.
  517. :raises ValueError: With a human readable error message,
  518. the attribute (of type `attrs.Attribute`),
  519. the validator that failed to raise an exception,
  520. the value it got,
  521. and the expected exception types.
  522. .. versionadded:: 22.2.0
  523. """
  524. try:
  525. exc_types = tuple(exc_types)
  526. except TypeError:
  527. exc_types = (exc_types,)
  528. return _NotValidator(validator, msg, exc_types)