validators.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. # SPDX-License-Identifier: MIT
  2. """
  3. Commonly useful validators.
  4. """
  5. from __future__ import absolute_import, division, print_function
  6. import operator
  7. import re
  8. from contextlib import contextmanager
  9. from ._config import get_run_validators, set_run_validators
  10. from ._make import _AndValidator, and_, attrib, attrs
  11. from .exceptions import NotCallableError
  12. try:
  13. Pattern = re.Pattern
  14. except AttributeError: # Python <3.7 lacks a Pattern type.
  15. Pattern = type(re.compile(""))
  16. __all__ = [
  17. "and_",
  18. "deep_iterable",
  19. "deep_mapping",
  20. "disabled",
  21. "ge",
  22. "get_disabled",
  23. "gt",
  24. "in_",
  25. "instance_of",
  26. "is_callable",
  27. "le",
  28. "lt",
  29. "matches_re",
  30. "max_len",
  31. "optional",
  32. "provides",
  33. "set_disabled",
  34. ]
  35. def set_disabled(disabled):
  36. """
  37. Globally disable or enable running validators.
  38. By default, they are run.
  39. :param disabled: If ``True``, disable running all validators.
  40. :type disabled: bool
  41. .. warning::
  42. This function is not thread-safe!
  43. .. versionadded:: 21.3.0
  44. """
  45. set_run_validators(not disabled)
  46. def get_disabled():
  47. """
  48. Return a bool indicating whether validators are currently disabled or not.
  49. :return: ``True`` if validators are currently disabled.
  50. :rtype: bool
  51. .. versionadded:: 21.3.0
  52. """
  53. return not get_run_validators()
  54. @contextmanager
  55. def disabled():
  56. """
  57. Context manager that disables running validators within its context.
  58. .. warning::
  59. This context manager is not thread-safe!
  60. .. versionadded:: 21.3.0
  61. """
  62. set_run_validators(False)
  63. try:
  64. yield
  65. finally:
  66. set_run_validators(True)
  67. @attrs(repr=False, slots=True, hash=True)
  68. class _InstanceOfValidator(object):
  69. type = attrib()
  70. def __call__(self, inst, attr, value):
  71. """
  72. We use a callable class to be able to change the ``__repr__``.
  73. """
  74. if not isinstance(value, self.type):
  75. raise TypeError(
  76. "'{name}' must be {type!r} (got {value!r} that is a "
  77. "{actual!r}).".format(
  78. name=attr.name,
  79. type=self.type,
  80. actual=value.__class__,
  81. value=value,
  82. ),
  83. attr,
  84. self.type,
  85. value,
  86. )
  87. def __repr__(self):
  88. return "<instance_of validator for type {type!r}>".format(
  89. type=self.type
  90. )
  91. def instance_of(type):
  92. """
  93. A validator that raises a `TypeError` if the initializer is called
  94. with a wrong type for this particular attribute (checks are performed using
  95. `isinstance` therefore it's also valid to pass a tuple of types).
  96. :param type: The type to check for.
  97. :type type: type or tuple of types
  98. :raises TypeError: With a human readable error message, the attribute
  99. (of type `attrs.Attribute`), the expected type, and the value it
  100. got.
  101. """
  102. return _InstanceOfValidator(type)
  103. @attrs(repr=False, frozen=True, slots=True)
  104. class _MatchesReValidator(object):
  105. pattern = attrib()
  106. match_func = attrib()
  107. def __call__(self, inst, attr, value):
  108. """
  109. We use a callable class to be able to change the ``__repr__``.
  110. """
  111. if not self.match_func(value):
  112. raise ValueError(
  113. "'{name}' must match regex {pattern!r}"
  114. " ({value!r} doesn't)".format(
  115. name=attr.name, pattern=self.pattern.pattern, value=value
  116. ),
  117. attr,
  118. self.pattern,
  119. value,
  120. )
  121. def __repr__(self):
  122. return "<matches_re validator for pattern {pattern!r}>".format(
  123. pattern=self.pattern
  124. )
  125. def matches_re(regex, flags=0, func=None):
  126. r"""
  127. A validator that raises `ValueError` if the initializer is called
  128. with a string that doesn't match *regex*.
  129. :param regex: a regex string or precompiled pattern to match against
  130. :param int flags: flags that will be passed to the underlying re function
  131. (default 0)
  132. :param callable func: which underlying `re` function to call (options
  133. are `re.fullmatch`, `re.search`, `re.match`, default
  134. is ``None`` which means either `re.fullmatch` or an emulation of
  135. it on Python 2). For performance reasons, they won't be used directly
  136. but on a pre-`re.compile`\ ed pattern.
  137. .. versionadded:: 19.2.0
  138. .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern.
  139. """
  140. fullmatch = getattr(re, "fullmatch", None)
  141. valid_funcs = (fullmatch, None, re.search, re.match)
  142. if func not in valid_funcs:
  143. raise ValueError(
  144. "'func' must be one of {}.".format(
  145. ", ".join(
  146. sorted(
  147. e and e.__name__ or "None" for e in set(valid_funcs)
  148. )
  149. )
  150. )
  151. )
  152. if isinstance(regex, Pattern):
  153. if flags:
  154. raise TypeError(
  155. "'flags' can only be used with a string pattern; "
  156. "pass flags to re.compile() instead"
  157. )
  158. pattern = regex
  159. else:
  160. pattern = re.compile(regex, flags)
  161. if func is re.match:
  162. match_func = pattern.match
  163. elif func is re.search:
  164. match_func = pattern.search
  165. elif fullmatch:
  166. match_func = pattern.fullmatch
  167. else: # Python 2 fullmatch emulation (https://bugs.python.org/issue16203)
  168. pattern = re.compile(
  169. r"(?:{})\Z".format(pattern.pattern), pattern.flags
  170. )
  171. match_func = pattern.match
  172. return _MatchesReValidator(pattern, match_func)
  173. @attrs(repr=False, slots=True, hash=True)
  174. class _ProvidesValidator(object):
  175. interface = attrib()
  176. def __call__(self, inst, attr, value):
  177. """
  178. We use a callable class to be able to change the ``__repr__``.
  179. """
  180. if not self.interface.providedBy(value):
  181. raise TypeError(
  182. "'{name}' must provide {interface!r} which {value!r} "
  183. "doesn't.".format(
  184. name=attr.name, interface=self.interface, value=value
  185. ),
  186. attr,
  187. self.interface,
  188. value,
  189. )
  190. def __repr__(self):
  191. return "<provides validator for interface {interface!r}>".format(
  192. interface=self.interface
  193. )
  194. def provides(interface):
  195. """
  196. A validator that raises a `TypeError` if the initializer is called
  197. with an object that does not provide the requested *interface* (checks are
  198. performed using ``interface.providedBy(value)`` (see `zope.interface
  199. <https://zopeinterface.readthedocs.io/en/latest/>`_).
  200. :param interface: The interface to check for.
  201. :type interface: ``zope.interface.Interface``
  202. :raises TypeError: With a human readable error message, the attribute
  203. (of type `attrs.Attribute`), the expected interface, and the
  204. value it got.
  205. """
  206. return _ProvidesValidator(interface)
  207. @attrs(repr=False, slots=True, hash=True)
  208. class _OptionalValidator(object):
  209. validator = attrib()
  210. def __call__(self, inst, attr, value):
  211. if value is None:
  212. return
  213. self.validator(inst, attr, value)
  214. def __repr__(self):
  215. return "<optional validator for {what} or None>".format(
  216. what=repr(self.validator)
  217. )
  218. def optional(validator):
  219. """
  220. A validator that makes an attribute optional. An optional attribute is one
  221. which can be set to ``None`` in addition to satisfying the requirements of
  222. the sub-validator.
  223. :param validator: A validator (or a list of validators) that is used for
  224. non-``None`` values.
  225. :type validator: callable or `list` of callables.
  226. .. versionadded:: 15.1.0
  227. .. versionchanged:: 17.1.0 *validator* can be a list of validators.
  228. """
  229. if isinstance(validator, list):
  230. return _OptionalValidator(_AndValidator(validator))
  231. return _OptionalValidator(validator)
  232. @attrs(repr=False, slots=True, hash=True)
  233. class _InValidator(object):
  234. options = attrib()
  235. def __call__(self, inst, attr, value):
  236. try:
  237. in_options = value in self.options
  238. except TypeError: # e.g. `1 in "abc"`
  239. in_options = False
  240. if not in_options:
  241. raise ValueError(
  242. "'{name}' must be in {options!r} (got {value!r})".format(
  243. name=attr.name, options=self.options, value=value
  244. )
  245. )
  246. def __repr__(self):
  247. return "<in_ validator with options {options!r}>".format(
  248. options=self.options
  249. )
  250. def in_(options):
  251. """
  252. A validator that raises a `ValueError` if the initializer is called
  253. with a value that does not belong in the options provided. The check is
  254. performed using ``value in options``.
  255. :param options: Allowed options.
  256. :type options: list, tuple, `enum.Enum`, ...
  257. :raises ValueError: With a human readable error message, the attribute (of
  258. type `attrs.Attribute`), the expected options, and the value it
  259. got.
  260. .. versionadded:: 17.1.0
  261. """
  262. return _InValidator(options)
  263. @attrs(repr=False, slots=False, hash=True)
  264. class _IsCallableValidator(object):
  265. def __call__(self, inst, attr, value):
  266. """
  267. We use a callable class to be able to change the ``__repr__``.
  268. """
  269. if not callable(value):
  270. message = (
  271. "'{name}' must be callable "
  272. "(got {value!r} that is a {actual!r})."
  273. )
  274. raise NotCallableError(
  275. msg=message.format(
  276. name=attr.name, value=value, actual=value.__class__
  277. ),
  278. value=value,
  279. )
  280. def __repr__(self):
  281. return "<is_callable validator>"
  282. def is_callable():
  283. """
  284. A validator that raises a `attr.exceptions.NotCallableError` if the
  285. initializer is called with a value for this particular attribute
  286. that is not callable.
  287. .. versionadded:: 19.1.0
  288. :raises `attr.exceptions.NotCallableError`: With a human readable error
  289. message containing the attribute (`attrs.Attribute`) name,
  290. and the value it got.
  291. """
  292. return _IsCallableValidator()
  293. @attrs(repr=False, slots=True, hash=True)
  294. class _DeepIterable(object):
  295. member_validator = attrib(validator=is_callable())
  296. iterable_validator = attrib(
  297. default=None, validator=optional(is_callable())
  298. )
  299. def __call__(self, inst, attr, value):
  300. """
  301. We use a callable class to be able to change the ``__repr__``.
  302. """
  303. if self.iterable_validator is not None:
  304. self.iterable_validator(inst, attr, value)
  305. for member in value:
  306. self.member_validator(inst, attr, member)
  307. def __repr__(self):
  308. iterable_identifier = (
  309. ""
  310. if self.iterable_validator is None
  311. else " {iterable!r}".format(iterable=self.iterable_validator)
  312. )
  313. return (
  314. "<deep_iterable validator for{iterable_identifier}"
  315. " iterables of {member!r}>"
  316. ).format(
  317. iterable_identifier=iterable_identifier,
  318. member=self.member_validator,
  319. )
  320. def deep_iterable(member_validator, iterable_validator=None):
  321. """
  322. A validator that performs deep validation of an iterable.
  323. :param member_validator: Validator to apply to iterable members
  324. :param iterable_validator: Validator to apply to iterable itself
  325. (optional)
  326. .. versionadded:: 19.1.0
  327. :raises TypeError: if any sub-validators fail
  328. """
  329. return _DeepIterable(member_validator, iterable_validator)
  330. @attrs(repr=False, slots=True, hash=True)
  331. class _DeepMapping(object):
  332. key_validator = attrib(validator=is_callable())
  333. value_validator = attrib(validator=is_callable())
  334. mapping_validator = attrib(default=None, validator=optional(is_callable()))
  335. def __call__(self, inst, attr, value):
  336. """
  337. We use a callable class to be able to change the ``__repr__``.
  338. """
  339. if self.mapping_validator is not None:
  340. self.mapping_validator(inst, attr, value)
  341. for key in value:
  342. self.key_validator(inst, attr, key)
  343. self.value_validator(inst, attr, value[key])
  344. def __repr__(self):
  345. return (
  346. "<deep_mapping validator for objects mapping {key!r} to {value!r}>"
  347. ).format(key=self.key_validator, value=self.value_validator)
  348. def deep_mapping(key_validator, value_validator, mapping_validator=None):
  349. """
  350. A validator that performs deep validation of a dictionary.
  351. :param key_validator: Validator to apply to dictionary keys
  352. :param value_validator: Validator to apply to dictionary values
  353. :param mapping_validator: Validator to apply to top-level mapping
  354. attribute (optional)
  355. .. versionadded:: 19.1.0
  356. :raises TypeError: if any sub-validators fail
  357. """
  358. return _DeepMapping(key_validator, value_validator, mapping_validator)
  359. @attrs(repr=False, frozen=True, slots=True)
  360. class _NumberValidator(object):
  361. bound = attrib()
  362. compare_op = attrib()
  363. compare_func = attrib()
  364. def __call__(self, inst, attr, value):
  365. """
  366. We use a callable class to be able to change the ``__repr__``.
  367. """
  368. if not self.compare_func(value, self.bound):
  369. raise ValueError(
  370. "'{name}' must be {op} {bound}: {value}".format(
  371. name=attr.name,
  372. op=self.compare_op,
  373. bound=self.bound,
  374. value=value,
  375. )
  376. )
  377. def __repr__(self):
  378. return "<Validator for x {op} {bound}>".format(
  379. op=self.compare_op, bound=self.bound
  380. )
  381. def lt(val):
  382. """
  383. A validator that raises `ValueError` if the initializer is called
  384. with a number larger or equal to *val*.
  385. :param val: Exclusive upper bound for values
  386. .. versionadded:: 21.3.0
  387. """
  388. return _NumberValidator(val, "<", operator.lt)
  389. def le(val):
  390. """
  391. A validator that raises `ValueError` if the initializer is called
  392. with a number greater than *val*.
  393. :param val: Inclusive upper bound for values
  394. .. versionadded:: 21.3.0
  395. """
  396. return _NumberValidator(val, "<=", operator.le)
  397. def ge(val):
  398. """
  399. A validator that raises `ValueError` if the initializer is called
  400. with a number smaller than *val*.
  401. :param val: Inclusive lower bound for values
  402. .. versionadded:: 21.3.0
  403. """
  404. return _NumberValidator(val, ">=", operator.ge)
  405. def gt(val):
  406. """
  407. A validator that raises `ValueError` if the initializer is called
  408. with a number smaller or equal to *val*.
  409. :param val: Exclusive lower bound for values
  410. .. versionadded:: 21.3.0
  411. """
  412. return _NumberValidator(val, ">", operator.gt)
  413. @attrs(repr=False, frozen=True, slots=True)
  414. class _MaxLengthValidator(object):
  415. max_length = attrib()
  416. def __call__(self, inst, attr, value):
  417. """
  418. We use a callable class to be able to change the ``__repr__``.
  419. """
  420. if len(value) > self.max_length:
  421. raise ValueError(
  422. "Length of '{name}' must be <= {max}: {len}".format(
  423. name=attr.name, max=self.max_length, len=len(value)
  424. )
  425. )
  426. def __repr__(self):
  427. return "<max_len validator for {max}>".format(max=self.max_length)
  428. def max_len(length):
  429. """
  430. A validator that raises `ValueError` if the initializer is called
  431. with a string or iterable that is longer than *length*.
  432. :param int length: Maximum length of the string or iterable
  433. .. versionadded:: 21.3.0
  434. """
  435. return _MaxLengthValidator(length)