Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 

711 wiersze
20 KiB

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