validator.py 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407
  1. """
  2. Extensible validation for Python dictionaries.
  3. This module implements Cerberus Validator class
  4. :copyright: 2012-2016 by Nicola Iarocci.
  5. :license: ISC, see LICENSE for more details.
  6. Full documentation is available at http://python-cerberus.org
  7. """
  8. from __future__ import absolute_import
  9. from ast import literal_eval
  10. from collections import Hashable, Iterable, Mapping, Sequence
  11. from copy import copy
  12. from datetime import date, datetime
  13. import re
  14. from warnings import warn
  15. from cerberus import errors
  16. from cerberus.platform import _int_types, _str_type
  17. from cerberus.schema import (schema_registry, rules_set_registry,
  18. DefinitionSchema, SchemaError)
  19. from cerberus.utils import (drop_item_from_tuple, isclass,
  20. readonly_classproperty, TypeDefinition)
  21. toy_error_handler = errors.ToyErrorHandler()
  22. def dummy_for_rule_validation(rule_constraints):
  23. def dummy(self, constraint, field, value):
  24. raise RuntimeError('Dummy method called. Its purpose is to hold just'
  25. 'validation constraints for a rule in its '
  26. 'docstring.')
  27. f = dummy
  28. f.__doc__ = rule_constraints
  29. return f
  30. class DocumentError(Exception):
  31. """ Raised when the target document is missing or has the wrong format """
  32. pass
  33. class _SchemaRuleTypeError(Exception):
  34. """ Raised when a schema (list) validation encounters a mapping.
  35. Not supposed to be used outside this module. """
  36. pass
  37. class BareValidator(object):
  38. """ Validator class. Normalizes and/or validates any mapping against a
  39. validation-schema which is provided as an argument at class instantiation
  40. or upon calling the :meth:`~cerberus.Validator.validate`,
  41. :meth:`~cerberus.Validator.validated` or
  42. :meth:`~cerberus.Validator.normalized` method. An instance itself is
  43. callable and executes a validation.
  44. All instantiation parameters are optional.
  45. There are the introspective properties :attr:`types`, :attr:`validators`,
  46. :attr:`coercers`, :attr:`default_setters`, :attr:`rules`,
  47. :attr:`normalization_rules` and :attr:`validation_rules`.
  48. The attributes reflecting the available rules are assembled considering
  49. constraints that are defined in the docstrings of rules' methods and is
  50. effectively used as validation schema for :attr:`schema`.
  51. :param schema: See :attr:`~cerberus.Validator.schema`.
  52. Defaults to :obj:`None`.
  53. :type schema: any :term:`mapping`
  54. :param ignore_none_values: See :attr:`~cerberus.Validator.ignore_none_values`.
  55. Defaults to ``False``.
  56. :type ignore_none_values: :class:`bool`
  57. :param allow_unknown: See :attr:`~cerberus.Validator.allow_unknown`.
  58. Defaults to ``False``.
  59. :type allow_unknown: :class:`bool` or any :term:`mapping`
  60. :param purge_unknown: See :attr:`~cerberus.Validator.purge_unknown`.
  61. Defaults to to ``False``.
  62. :type purge_unknown: :class:`bool`
  63. :param error_handler: The error handler that formats the result of
  64. :attr:`~cerberus.Validator.errors`.
  65. When given as two-value tuple with an error-handler
  66. class and a dictionary, the latter is passed to the
  67. initialization of the error handler.
  68. Default: :class:`~cerberus.errors.BasicErrorHandler`.
  69. :type error_handler: class or instance based on
  70. :class:`~cerberus.errors.BaseErrorHandler` or
  71. :class:`tuple`
  72. """ # noqa: E501
  73. mandatory_validations = ('nullable',)
  74. """ Rules that are evaluated on any field, regardless whether defined in
  75. the schema or not.
  76. Type: :class:`tuple` """
  77. priority_validations = ('nullable', 'readonly', 'type', 'empty')
  78. """ Rules that will be processed in that order before any other.
  79. Type: :class:`tuple` """
  80. types_mapping = {
  81. 'binary':
  82. TypeDefinition('binary', (bytes, bytearray), ()),
  83. 'boolean':
  84. TypeDefinition('boolean', (bool,), ()),
  85. 'date':
  86. TypeDefinition('date', (date,), ()),
  87. 'datetime':
  88. TypeDefinition('datetime', (datetime,), ()),
  89. 'dict':
  90. TypeDefinition('dict', (Mapping,), ()),
  91. 'float':
  92. TypeDefinition('float', (float, _int_types), ()),
  93. 'integer':
  94. TypeDefinition('integer', (_int_types,), ()),
  95. 'list':
  96. TypeDefinition('list', (Sequence,), (_str_type,)),
  97. 'number':
  98. TypeDefinition('number', (_int_types, float), (bool,)),
  99. 'set':
  100. TypeDefinition('set', (set,), ()),
  101. 'string':
  102. TypeDefinition('string', (_str_type), ())
  103. }
  104. """ This mapping holds all available constraints for the type rule and
  105. their assigned :class:`~cerberus.TypeDefinition`. """
  106. _valid_schemas = set()
  107. """ A :class:`set` of hashes derived from validation schemas that are
  108. legit for a particular ``Validator`` class. """
  109. def __init__(self, *args, **kwargs):
  110. """ The arguments will be treated as with this signature:
  111. ext(self, schema=None, ignore_none_values=False,
  112. allow_unknown=False, purge_unknown=False,
  113. error_handler=errors.BasicErrorHandler)
  114. """
  115. self.document = None
  116. """ The document that is or was recently processed.
  117. Type: any :term:`mapping` """
  118. self._errors = errors.ErrorList()
  119. """ The list of errors that were encountered since the last document
  120. processing was invoked.
  121. Type: :class:`~cerberus.errors.ErrorList` """
  122. self.recent_error = None
  123. """ The last individual error that was submitted.
  124. Type: :class:`~cerberus.errors.ValidationError` """
  125. self.document_error_tree = errors.DocumentErrorTree()
  126. """ A tree representiation of encountered errors following the
  127. structure of the document.
  128. Type: :class:`~cerberus.errors.DocumentErrorTree` """
  129. self.schema_error_tree = errors.SchemaErrorTree()
  130. """ A tree representiation of encountered errors following the
  131. structure of the schema.
  132. Type: :class:`~cerberus.errors.SchemaErrorTree` """
  133. self.document_path = ()
  134. """ The path within the document to the current sub-document.
  135. Type: :class:`tuple` """
  136. self.schema_path = ()
  137. """ The path within the schema to the current sub-schema.
  138. Type: :class:`tuple` """
  139. self.update = False
  140. self.error_handler = self.__init_error_handler(kwargs)
  141. """ The error handler used to format :attr:`~cerberus.Validator.errors`
  142. and process submitted errors with
  143. :meth:`~cerberus.Validator._error`.
  144. Type: :class:`~cerberus.errors.BaseErrorHandler` """
  145. self.__store_config(args, kwargs)
  146. self.schema = kwargs.get('schema', None)
  147. self.allow_unknown = kwargs.get('allow_unknown', False)
  148. self._remaining_rules = []
  149. """ Keeps track of the rules that are next in line to be evaluated
  150. during the validation of a field.
  151. Type: :class:`list` """
  152. super(BareValidator, self).__init__()
  153. @staticmethod
  154. def __init_error_handler(kwargs):
  155. error_handler = kwargs.pop('error_handler', errors.BasicErrorHandler)
  156. if isinstance(error_handler, tuple):
  157. error_handler, eh_config = error_handler
  158. else:
  159. eh_config = {}
  160. if isclass(error_handler) and \
  161. issubclass(error_handler, errors.BaseErrorHandler):
  162. return error_handler(**eh_config)
  163. elif isinstance(error_handler, errors.BaseErrorHandler):
  164. return error_handler
  165. else:
  166. raise RuntimeError('Invalid error_handler.')
  167. def __store_config(self, args, kwargs):
  168. """ Assign args to kwargs and store configuration. """
  169. signature = ('schema', 'ignore_none_values', 'allow_unknown',
  170. 'purge_unknown')
  171. for i, p in enumerate(signature[:len(args)]):
  172. if p in kwargs:
  173. raise TypeError("ext got multiple values for argument "
  174. "'%s'" % p)
  175. else:
  176. kwargs[p] = args[i]
  177. self._config = kwargs
  178. """ This dictionary holds the configuration arguments that were used to
  179. initialize the :class:`Validator` instance except the
  180. ``error_handler``. """
  181. @classmethod
  182. def clear_caches(cls):
  183. """ Purge the cache of known valid schemas. """
  184. cls._valid_schemas.clear()
  185. def _error(self, *args):
  186. """ Creates and adds one or multiple errors.
  187. :param args: Accepts different argument's signatures.
  188. *1. Bulk addition of errors:*
  189. - :term:`iterable` of
  190. :class:`~cerberus.errors.ValidationError`-instances
  191. The errors will be added to
  192. :attr:`~cerberus.Validator._errors`.
  193. *2. Custom error:*
  194. - the invalid field's name
  195. - the error message
  196. A custom error containing the message will be created and
  197. added to :attr:`~cerberus.Validator._errors`.
  198. There will however be fewer information contained in the
  199. error (no reference to the violated rule and its
  200. constraint).
  201. *3. Defined error:*
  202. - the invalid field's name
  203. - the error-reference, see :mod:`cerberus.errors`
  204. - arbitrary, supplemental information about the error
  205. A :class:`~cerberus.errors.ValidationError` instance will
  206. be created and added to
  207. :attr:`~cerberus.Validator._errors`.
  208. """
  209. if len(args) == 1:
  210. self._errors.extend(args[0])
  211. self._errors.sort()
  212. for error in args[0]:
  213. self.document_error_tree += error
  214. self.schema_error_tree += error
  215. self.error_handler.emit(error)
  216. elif len(args) == 2 and isinstance(args[1], _str_type):
  217. self._error(args[0], errors.CUSTOM, args[1])
  218. elif len(args) >= 2:
  219. field = args[0]
  220. code = args[1].code
  221. rule = args[1].rule
  222. info = args[2:]
  223. document_path = self.document_path + (field, )
  224. schema_path = self.schema_path
  225. if code != errors.UNKNOWN_FIELD.code and rule is not None:
  226. schema_path += (field, rule)
  227. if not rule:
  228. constraint = None
  229. else:
  230. field_definitions = self._resolve_rules_set(self.schema[field])
  231. if rule == 'nullable':
  232. constraint = field_definitions.get(rule, False)
  233. else:
  234. constraint = field_definitions[rule]
  235. value = self.document.get(field)
  236. self.recent_error = errors.ValidationError(
  237. document_path, schema_path, code, rule, constraint, value, info
  238. )
  239. self._error([self.recent_error])
  240. def _get_child_validator(self, document_crumb=None, schema_crumb=None,
  241. **kwargs):
  242. """ Creates a new instance of Validator-(sub-)class. All initial
  243. parameters of the parent are passed to the initialization, unless
  244. a parameter is given as an explicit *keyword*-parameter.
  245. :param document_crumb: Extends the
  246. :attr:`~cerberus.Validator.document_path`
  247. of the child-validator.
  248. :type document_crumb: :class:`tuple` or :term:`hashable`
  249. :param schema_crumb: Extends the
  250. :attr:`~cerberus.Validator.schema_path`
  251. of the child-validator.
  252. :type schema_crumb: :class:`tuple` or hashable
  253. :param kwargs: Overriding keyword-arguments for initialization.
  254. :type kwargs: :class:`dict`
  255. :return: an instance of ``self.__class__``
  256. """
  257. child_config = self._config.copy()
  258. child_config.update(kwargs)
  259. if not self.is_child:
  260. child_config['is_child'] = True
  261. child_config['error_handler'] = toy_error_handler
  262. child_config['root_allow_unknown'] = self.allow_unknown
  263. child_config['root_document'] = self.document
  264. child_config['root_schema'] = self.schema
  265. child_validator = self.__class__(**child_config)
  266. if document_crumb is None:
  267. child_validator.document_path = self.document_path
  268. else:
  269. if not isinstance(document_crumb, tuple):
  270. document_crumb = (document_crumb, )
  271. child_validator.document_path = self.document_path + document_crumb
  272. if schema_crumb is None:
  273. child_validator.schema_path = self.schema_path
  274. else:
  275. if not isinstance(schema_crumb, tuple):
  276. schema_crumb = (schema_crumb, )
  277. child_validator.schema_path = self.schema_path + schema_crumb
  278. return child_validator
  279. def __get_rule_handler(self, domain, rule):
  280. methodname = '_{0}_{1}'.format(domain, rule.replace(' ', '_'))
  281. result = getattr(self, methodname, None)
  282. if result is None:
  283. raise RuntimeError("There's no handler for '{}' in the '{}' "
  284. "domain.".format(rule, domain))
  285. return result
  286. def _drop_nodes_from_errorpaths(self, _errors, dp_items, sp_items):
  287. """ Removes nodes by index from an errorpath, relatively to the
  288. basepaths of self.
  289. :param errors: A list of :class:`errors.ValidationError` instances.
  290. :param dp_items: A list of integers, pointing at the nodes to drop from
  291. the :attr:`document_path`.
  292. :param sp_items: Alike ``dp_items``, but for :attr:`schema_path`.
  293. """
  294. dp_basedepth = len(self.document_path)
  295. sp_basedepth = len(self.schema_path)
  296. for error in _errors:
  297. for i in sorted(dp_items, reverse=True):
  298. error.document_path = \
  299. drop_item_from_tuple(error.document_path, dp_basedepth + i)
  300. for i in sorted(sp_items, reverse=True):
  301. error.schema_path = \
  302. drop_item_from_tuple(error.schema_path, sp_basedepth + i)
  303. if error.child_errors:
  304. self._drop_nodes_from_errorpaths(error.child_errors,
  305. dp_items, sp_items)
  306. def _lookup_field(self, path):
  307. """ Searches for a field as defined by path. This method is used by the
  308. ``dependency`` evaluation logic.
  309. :param path: Path elements are separated by a ``.``. A leading ``^``
  310. indicates that the path relates to the document root,
  311. otherwise it relates to the currently evaluated document,
  312. which is possibly a subdocument.
  313. The sequence ``^^`` at the start will be interpreted as a
  314. literal ``^``.
  315. :type path: :class:`str`
  316. :returns: Either the found field name and its value or :obj:`None` for
  317. both.
  318. :rtype: A two-value :class:`tuple`.
  319. """
  320. if path.startswith('^'):
  321. path = path[1:]
  322. context = self.document if path.startswith('^') \
  323. else self.root_document
  324. else:
  325. context = self.document
  326. parts = path.split('.')
  327. for part in parts:
  328. if part not in context:
  329. return None, None
  330. context = context.get(part)
  331. return parts[-1], context
  332. def _resolve_rules_set(self, rules_set):
  333. if isinstance(rules_set, Mapping):
  334. return rules_set
  335. elif isinstance(rules_set, _str_type):
  336. return self.rules_set_registry.get(rules_set)
  337. return None
  338. def _resolve_schema(self, schema):
  339. if isinstance(schema, Mapping):
  340. return schema
  341. elif isinstance(schema, _str_type):
  342. return self.schema_registry.get(schema)
  343. return None
  344. # Properties
  345. @property
  346. def allow_unknown(self):
  347. """ If ``True`` unknown fields that are not defined in the schema will
  348. be ignored. If a mapping with a validation schema is given, any
  349. undefined field will be validated against its rules.
  350. Also see :ref:`allowing-the-unknown`.
  351. Type: :class:`bool` or any :term:`mapping` """
  352. return self._config.get('allow_unknown', False)
  353. @allow_unknown.setter
  354. def allow_unknown(self, value):
  355. if not (self.is_child or isinstance(value, (bool, DefinitionSchema))):
  356. DefinitionSchema(self, {'allow_unknown': value})
  357. self._config['allow_unknown'] = value
  358. @property
  359. def errors(self):
  360. """ The errors of the last processing formatted by the handler that is
  361. bound to :attr:`~cerberus.Validator.error_handler`. """
  362. return self.error_handler(self._errors)
  363. @property
  364. def ignore_none_values(self):
  365. """ Whether to not process :obj:`None`-values in a document or not.
  366. Type: :class:`bool` """
  367. return self._config.get('ignore_none_values', False)
  368. @ignore_none_values.setter
  369. def ignore_none_values(self, value):
  370. self._config['ignore_none_values'] = value
  371. @property
  372. def is_child(self):
  373. """ ``True`` for child-validators obtained with
  374. :meth:`~cerberus.Validator._get_child_validator`.
  375. Type: :class:`bool` """
  376. return self._config.get('is_child', False)
  377. @property
  378. def _is_normalized(self):
  379. """ ``True`` if the document is already normalized. """
  380. return self._config.get('_is_normalized', False)
  381. @_is_normalized.setter
  382. def _is_normalized(self, value):
  383. self._config['_is_normalized'] = value
  384. @property
  385. def purge_unknown(self):
  386. """ If ``True`` unknown fields will be deleted from the document
  387. unless a validation is called with disabled normalization.
  388. Also see :ref:`purging-unknown-fields`. Type: :class:`bool` """
  389. return self._config.get('purge_unknown', False)
  390. @purge_unknown.setter
  391. def purge_unknown(self, value):
  392. self._config['purge_unknown'] = value
  393. @property
  394. def root_allow_unknown(self):
  395. """ The :attr:`~cerberus.Validator.allow_unknown` attribute of the
  396. first level ancestor of a child validator. """
  397. return self._config.get('root_allow_unknown', self.allow_unknown)
  398. @property
  399. def root_document(self):
  400. """ The :attr:`~cerberus.Validator.document` attribute of the
  401. first level ancestor of a child validator. """
  402. return self._config.get('root_document', self.document)
  403. @property
  404. def rules_set_registry(self):
  405. """ The registry that holds referenced rules sets.
  406. Type: :class:`~cerberus.Registry` """
  407. return self._config.get('rules_set_registry', rules_set_registry)
  408. @rules_set_registry.setter
  409. def rules_set_registry(self, registry):
  410. self._config['rules_set_registry'] = registry
  411. @property
  412. def root_schema(self):
  413. """ The :attr:`~cerberus.Validator.schema` attribute of the
  414. first level ancestor of a child validator. """
  415. return self._config.get('root_schema', self.schema)
  416. @property
  417. def schema(self):
  418. """ The validation schema of a validator. When a schema is passed to
  419. a method, it replaces this attribute.
  420. Type: any :term:`mapping` or :obj:`None` """
  421. return self._schema
  422. @schema.setter
  423. def schema(self, schema):
  424. if schema is None:
  425. self._schema = None
  426. elif self.is_child or isinstance(schema, DefinitionSchema):
  427. self._schema = schema
  428. else:
  429. self._schema = DefinitionSchema(self, schema)
  430. @property
  431. def schema_registry(self):
  432. """ The registry that holds referenced schemas.
  433. Type: :class:`~cerberus.Registry` """
  434. return self._config.get('schema_registry', schema_registry)
  435. @schema_registry.setter
  436. def schema_registry(self, registry):
  437. self._config['schema_registry'] = registry
  438. # FIXME the returned method has the correct docstring, but doesn't appear
  439. # in the API docs
  440. @readonly_classproperty
  441. def types(cls):
  442. """ The constraints that can be used for the 'type' rule.
  443. Type: A tuple of strings. """
  444. redundant_types = \
  445. set(cls.types_mapping) & set(cls._types_from_methods)
  446. if redundant_types:
  447. warn("These types are defined both with a method and in the"
  448. "'types_mapping' property of this validator: %s"
  449. % redundant_types)
  450. return tuple(cls.types_mapping) + cls._types_from_methods
  451. # Document processing
  452. def __init_processing(self, document, schema=None):
  453. self._errors = errors.ErrorList()
  454. self.recent_error = None
  455. self.document_error_tree = errors.DocumentErrorTree()
  456. self.schema_error_tree = errors.SchemaErrorTree()
  457. self.document = copy(document)
  458. if not self.is_child:
  459. self._is_normalized = False
  460. if schema is not None:
  461. self.schema = DefinitionSchema(self, schema)
  462. elif self.schema is None:
  463. if isinstance(self.allow_unknown, Mapping):
  464. self._schema = {}
  465. else:
  466. raise SchemaError(errors.SCHEMA_ERROR_MISSING)
  467. if document is None:
  468. raise DocumentError(errors.DOCUMENT_MISSING)
  469. if not isinstance(document, Mapping):
  470. raise DocumentError(
  471. errors.DOCUMENT_FORMAT.format(document))
  472. self.error_handler.start(self)
  473. def _drop_remaining_rules(self, *rules):
  474. """ Drops rules from the queue of the rules that still need to be
  475. evaluated for the currently processed field.
  476. If no arguments are given, the whole queue is emptied.
  477. """
  478. if rules:
  479. for rule in rules:
  480. try:
  481. self._remaining_rules.remove(rule)
  482. except ValueError:
  483. pass
  484. else:
  485. self._remaining_rules = []
  486. # # Normalizing
  487. def normalized(self, document, schema=None, always_return_document=False):
  488. """ Returns the document normalized according to the specified rules
  489. of a schema.
  490. :param document: The document to normalize.
  491. :type document: any :term:`mapping`
  492. :param schema: The validation schema. Defaults to :obj:`None`. If not
  493. provided here, the schema must have been provided at
  494. class instantiation.
  495. :type schema: any :term:`mapping`
  496. :param always_return_document: Return the document, even if an error
  497. occurred. Defaults to: ``False``.
  498. :type always_return_document: :class:`bool`
  499. :return: A normalized copy of the provided mapping or :obj:`None` if an
  500. error occurred during normalization.
  501. """
  502. self.__init_processing(document, schema)
  503. self.__normalize_mapping(self.document, self.schema)
  504. self.error_handler.end(self)
  505. if self._errors and not always_return_document:
  506. return None
  507. else:
  508. return self.document
  509. def __normalize_mapping(self, mapping, schema):
  510. if isinstance(schema, _str_type):
  511. schema = self._resolve_schema(schema)
  512. schema = schema.copy()
  513. for field in schema:
  514. schema[field] = self._resolve_rules_set(schema[field])
  515. self.__normalize_rename_fields(mapping, schema)
  516. if self.purge_unknown and not self.allow_unknown:
  517. self._normalize_purge_unknown(mapping, schema)
  518. # Check `readonly` fields before applying default values because
  519. # a field's schema definition might contain both `readonly` and
  520. # `default`.
  521. self.__validate_readonly_fields(mapping, schema)
  522. self.__normalize_default_fields(mapping, schema)
  523. self._normalize_coerce(mapping, schema)
  524. self.__normalize_containers(mapping, schema)
  525. self._is_normalized = True
  526. return mapping
  527. def _normalize_coerce(self, mapping, schema):
  528. """ {'oneof': [
  529. {'type': 'callable'},
  530. {'type': 'list',
  531. 'schema': {'oneof': [{'type': 'callable'},
  532. {'type': 'string'}]}},
  533. {'type': 'string'}
  534. ]} """
  535. error = errors.COERCION_FAILED
  536. for field in mapping:
  537. if field in schema and 'coerce' in schema[field]:
  538. mapping[field] = self.__normalize_coerce(
  539. schema[field]['coerce'], field, mapping[field],
  540. schema[field].get('nullable', False), error)
  541. elif isinstance(self.allow_unknown, Mapping) and \
  542. 'coerce' in self.allow_unknown:
  543. mapping[field] = self.__normalize_coerce(
  544. self.allow_unknown['coerce'], field, mapping[field],
  545. self.allow_unknown.get('nullable', False), error)
  546. def __normalize_coerce(self, processor, field, value, nullable, error):
  547. if isinstance(processor, _str_type):
  548. processor = self.__get_rule_handler('normalize_coerce', processor)
  549. elif isinstance(processor, Iterable):
  550. result = value
  551. for p in processor:
  552. result = self.__normalize_coerce(p, field, result,
  553. nullable, error)
  554. if errors.COERCION_FAILED in \
  555. self.document_error_tree.fetch_errors_from(
  556. self.document_path + (field,)):
  557. break
  558. return result
  559. try:
  560. return processor(value)
  561. except Exception as e:
  562. if not nullable and e is not TypeError:
  563. self._error(field, error, str(e))
  564. return value
  565. def __normalize_containers(self, mapping, schema):
  566. for field in mapping:
  567. if field not in schema:
  568. continue
  569. # TODO: This check conflates validation and normalization
  570. if isinstance(mapping[field], Mapping):
  571. if 'keyschema' in schema[field]:
  572. self.__normalize_mapping_per_keyschema(
  573. field, mapping, schema[field]['keyschema'])
  574. if 'valueschema' in schema[field]:
  575. self.__normalize_mapping_per_valueschema(
  576. field, mapping, schema[field]['valueschema'])
  577. if set(schema[field]) & set(('allow_unknown', 'purge_unknown',
  578. 'schema')):
  579. try:
  580. self.__normalize_mapping_per_schema(
  581. field, mapping, schema)
  582. except _SchemaRuleTypeError:
  583. pass
  584. elif isinstance(mapping[field], _str_type):
  585. continue
  586. elif isinstance(mapping[field], Sequence) and \
  587. 'schema' in schema[field]:
  588. self.__normalize_sequence(field, mapping, schema)
  589. def __normalize_mapping_per_keyschema(self, field, mapping, property_rules):
  590. schema = dict(((k, property_rules) for k in mapping[field]))
  591. document = dict(((k, k) for k in mapping[field]))
  592. validator = self._get_child_validator(
  593. document_crumb=field, schema_crumb=(field, 'keyschema'),
  594. schema=schema)
  595. result = validator.normalized(document, always_return_document=True)
  596. if validator._errors:
  597. self._drop_nodes_from_errorpaths(validator._errors, [], [2, 4])
  598. self._error(validator._errors)
  599. for k in result:
  600. if k == result[k]:
  601. continue
  602. if result[k] in mapping[field]:
  603. warn("Normalizing keys of {path}: {key} already exists, "
  604. "its value is replaced."
  605. .format(path='.'.join(self.document_path + (field,)),
  606. key=k))
  607. mapping[field][result[k]] = mapping[field][k]
  608. else:
  609. mapping[field][result[k]] = mapping[field][k]
  610. del mapping[field][k]
  611. def __normalize_mapping_per_valueschema(self, field, mapping, value_rules):
  612. schema = dict(((k, value_rules) for k in mapping[field]))
  613. validator = self._get_child_validator(
  614. document_crumb=field, schema_crumb=(field, 'valueschema'),
  615. schema=schema)
  616. mapping[field] = validator.normalized(mapping[field],
  617. always_return_document=True)
  618. if validator._errors:
  619. self._drop_nodes_from_errorpaths(validator._errors, [], [2])
  620. self._error(validator._errors)
  621. def __normalize_mapping_per_schema(self, field, mapping, schema):
  622. validator = self._get_child_validator(
  623. document_crumb=field, schema_crumb=(field, 'schema'),
  624. schema=schema[field].get('schema', {}),
  625. allow_unknown=schema[field].get('allow_unknown', self.allow_unknown), # noqa: E501
  626. purge_unknown=schema[field].get('purge_unknown', self.purge_unknown)) # noqa: E501
  627. value_type = type(mapping[field])
  628. result_value = validator.normalized(mapping[field],
  629. always_return_document=True)
  630. mapping[field] = value_type(result_value)
  631. if validator._errors:
  632. self._error(validator._errors)
  633. def __normalize_sequence(self, field, mapping, schema):
  634. schema = dict(((k, schema[field]['schema'])
  635. for k in range(len(mapping[field]))))
  636. document = dict((k, v) for k, v in enumerate(mapping[field]))
  637. validator = self._get_child_validator(
  638. document_crumb=field, schema_crumb=(field, 'schema'),
  639. schema=schema)
  640. value_type = type(mapping[field])
  641. result = validator.normalized(document, always_return_document=True)
  642. mapping[field] = value_type(result.values())
  643. if validator._errors:
  644. self._drop_nodes_from_errorpaths(validator._errors, [], [2])
  645. self._error(validator._errors)
  646. @staticmethod
  647. def _normalize_purge_unknown(mapping, schema):
  648. """ {'type': 'boolean'} """
  649. for field in tuple(mapping):
  650. if field not in schema:
  651. del mapping[field]
  652. return mapping
  653. def __normalize_rename_fields(self, mapping, schema):
  654. for field in tuple(mapping):
  655. if field in schema:
  656. self._normalize_rename(mapping, schema, field)
  657. self._normalize_rename_handler(mapping, schema, field)
  658. elif isinstance(self.allow_unknown, Mapping) and \
  659. 'rename_handler' in self.allow_unknown:
  660. self._normalize_rename_handler(
  661. mapping, {field: self.allow_unknown}, field)
  662. return mapping
  663. def _normalize_rename(self, mapping, schema, field):
  664. """ {'type': 'hashable'} """
  665. if 'rename' in schema[field]:
  666. mapping[schema[field]['rename']] = mapping[field]
  667. del mapping[field]
  668. def _normalize_rename_handler(self, mapping, schema, field):
  669. """ {'oneof': [
  670. {'type': 'callable'},
  671. {'type': 'list',
  672. 'schema': {'oneof': [{'type': 'callable'},
  673. {'type': 'string'}]}},
  674. {'type': 'string'}
  675. ]} """
  676. if 'rename_handler' not in schema[field]:
  677. return
  678. new_name = self.__normalize_coerce(
  679. schema[field]['rename_handler'], field, field,
  680. False, errors.RENAMING_FAILED)
  681. if new_name != field:
  682. mapping[new_name] = mapping[field]
  683. del mapping[field]
  684. def __validate_readonly_fields(self, mapping, schema):
  685. for field in (x for x in schema if x in mapping and
  686. self._resolve_rules_set(schema[x]).get('readonly')):
  687. self._validate_readonly(schema[field]['readonly'], field,
  688. mapping[field])
  689. def __normalize_default_fields(self, mapping, schema):
  690. fields = [x for x in schema if x not in mapping or
  691. mapping[x] is None and not schema[x].get('nullable', False)]
  692. try:
  693. fields_with_default = [x for x in fields if 'default' in schema[x]]
  694. except TypeError:
  695. raise _SchemaRuleTypeError
  696. for field in fields_with_default:
  697. self._normalize_default(mapping, schema, field)
  698. known_fields_states = set()
  699. fields = [x for x in fields if 'default_setter' in schema[x]]
  700. while fields:
  701. field = fields.pop(0)
  702. try:
  703. self._normalize_default_setter(mapping, schema, field)
  704. except KeyError:
  705. fields.append(field)
  706. except Exception as e:
  707. self._error(field, errors.SETTING_DEFAULT_FAILED, str(e))
  708. fields_state = tuple(fields)
  709. if fields_state in known_fields_states:
  710. for field in fields:
  711. self._error(field, errors.SETTING_DEFAULT_FAILED,
  712. 'Circular dependencies of default setters.')
  713. break
  714. else:
  715. known_fields_states.add(fields_state)
  716. def _normalize_default(self, mapping, schema, field):
  717. """ {'nullable': True} """
  718. mapping[field] = schema[field]['default']
  719. def _normalize_default_setter(self, mapping, schema, field):
  720. """ {'oneof': [
  721. {'type': 'callable'},
  722. {'type': 'string'}
  723. ]} """
  724. if 'default_setter' in schema[field]:
  725. setter = schema[field]['default_setter']
  726. if isinstance(setter, _str_type):
  727. setter = self.__get_rule_handler('normalize_default_setter',
  728. setter)
  729. mapping[field] = setter(mapping)
  730. # # Validating
  731. def validate(self, document, schema=None, update=False, normalize=True):
  732. """ Normalizes and validates a mapping against a validation-schema of
  733. defined rules.
  734. :param document: The document to normalize.
  735. :type document: any :term:`mapping`
  736. :param schema: The validation schema. Defaults to :obj:`None`. If not
  737. provided here, the schema must have been provided at
  738. class instantiation.
  739. :type schema: any :term:`mapping`
  740. :param update: If ``True``, required fields won't be checked.
  741. :type update: :class:`bool`
  742. :param normalize: If ``True``, normalize the document before validation.
  743. :type normalize: :class:`bool`
  744. :return: ``True`` if validation succeeds, otherwise ``False``. Check
  745. the :func:`errors` property for a list of processing errors.
  746. :rtype: :class:`bool`
  747. """
  748. self.update = update
  749. self._unrequired_by_excludes = set()
  750. self.__init_processing(document, schema)
  751. if normalize:
  752. self.__normalize_mapping(self.document, self.schema)
  753. for field in self.document:
  754. if self.ignore_none_values and self.document[field] is None:
  755. continue
  756. definitions = self.schema.get(field)
  757. if definitions is not None:
  758. self.__validate_definitions(definitions, field)
  759. else:
  760. self.__validate_unknown_fields(field)
  761. if not self.update:
  762. self.__validate_required_fields(self.document)
  763. self.error_handler.end(self)
  764. return not bool(self._errors)
  765. __call__ = validate
  766. def validated(self, *args, **kwargs):
  767. """ Wrapper around :meth:`~cerberus.Validator.validate` that returns
  768. the normalized and validated document or :obj:`None` if validation
  769. failed. """
  770. always_return_document = kwargs.pop('always_return_document', False)
  771. self.validate(*args, **kwargs)
  772. if self._errors and not always_return_document:
  773. return None
  774. else:
  775. return self.document
  776. def __validate_unknown_fields(self, field):
  777. if self.allow_unknown:
  778. value = self.document[field]
  779. if isinstance(self.allow_unknown, (Mapping, _str_type)):
  780. # validate that unknown fields matches the schema
  781. # for unknown_fields
  782. schema_crumb = 'allow_unknown' if self.is_child \
  783. else '__allow_unknown__'
  784. validator = self._get_child_validator(
  785. schema_crumb=schema_crumb,
  786. schema={field: self.allow_unknown})
  787. if not validator({field: value}, normalize=False):
  788. self._error(validator._errors)
  789. else:
  790. self._error(field, errors.UNKNOWN_FIELD)
  791. def __validate_definitions(self, definitions, field):
  792. """ Validate a field's value against its defined rules. """
  793. def validate_rule(rule):
  794. validator = self.__get_rule_handler('validate', rule)
  795. return validator(definitions.get(rule, None), field, value)
  796. definitions = self._resolve_rules_set(definitions)
  797. value = self.document[field]
  798. rules_queue = [x for x in self.priority_validations
  799. if x in definitions or x in self.mandatory_validations]
  800. rules_queue.extend(x for x in self.mandatory_validations
  801. if x not in rules_queue)
  802. rules_queue.extend(x for x in definitions
  803. if x not in rules_queue and
  804. x not in self.normalization_rules and
  805. x not in ('allow_unknown', 'required'))
  806. self._remaining_rules = rules_queue
  807. while self._remaining_rules:
  808. rule = self._remaining_rules.pop(0)
  809. try:
  810. result = validate_rule(rule)
  811. # TODO remove on next breaking release
  812. if result:
  813. break
  814. except _SchemaRuleTypeError:
  815. break
  816. self._drop_remaining_rules()
  817. # Remember to keep the validation methods below this line
  818. # sorted alphabetically
  819. _validate_allow_unknown = dummy_for_rule_validation(
  820. """ {'oneof': [{'type': 'boolean'},
  821. {'type': ['dict', 'string'],
  822. 'validator': 'bulk_schema'}]} """)
  823. def _validate_allowed(self, allowed_values, field, value):
  824. """ {'type': 'list'} """
  825. if isinstance(value, Iterable) and not isinstance(value, _str_type):
  826. unallowed = set(value) - set(allowed_values)
  827. if unallowed:
  828. self._error(field, errors.UNALLOWED_VALUES, list(unallowed))
  829. else:
  830. if value not in allowed_values:
  831. self._error(field, errors.UNALLOWED_VALUE, value)
  832. def _validate_dependencies(self, dependencies, field, value):
  833. """ {'type': ('dict', 'hashable', 'list'),
  834. 'validator': 'dependencies'} """
  835. if isinstance(dependencies, _str_type):
  836. dependencies = (dependencies,)
  837. if isinstance(dependencies, Sequence):
  838. self.__validate_dependencies_sequence(dependencies, field)
  839. elif isinstance(dependencies, Mapping):
  840. self.__validate_dependencies_mapping(dependencies, field)
  841. if self.document_error_tree.fetch_node_from(
  842. self.schema_path + (field, 'dependencies')) is not None:
  843. return True
  844. def __validate_dependencies_mapping(self, dependencies, field):
  845. validated_dependencies_counter = 0
  846. error_info = {}
  847. for dependency_name, dependency_values in dependencies.items():
  848. if (not isinstance(dependency_values, Sequence) or
  849. isinstance(dependency_values, _str_type)):
  850. dependency_values = [dependency_values]
  851. wanted_field, wanted_field_value = \
  852. self._lookup_field(dependency_name)
  853. if wanted_field_value in dependency_values:
  854. validated_dependencies_counter += 1
  855. else:
  856. error_info.update({dependency_name: wanted_field_value})
  857. if validated_dependencies_counter != len(dependencies):
  858. self._error(field, errors.DEPENDENCIES_FIELD_VALUE, error_info)
  859. def __validate_dependencies_sequence(self, dependencies, field):
  860. for dependency in dependencies:
  861. if self._lookup_field(dependency)[0] is None:
  862. self._error(field, errors.DEPENDENCIES_FIELD, dependency)
  863. def _validate_empty(self, empty, field, value):
  864. """ {'type': 'boolean'} """
  865. if isinstance(value, Iterable) and len(value) == 0:
  866. self._drop_remaining_rules(
  867. 'allowed', 'forbidden', 'items', 'minlength', 'maxlength',
  868. 'regex', 'validator')
  869. if not empty:
  870. self._error(field, errors.EMPTY_NOT_ALLOWED)
  871. def _validate_excludes(self, excludes, field, value):
  872. """ {'type': ('hashable', 'list'),
  873. 'schema': {'type': 'hashable'}} """
  874. if isinstance(excludes, Hashable):
  875. excludes = [excludes]
  876. # Save required field to be checked latter
  877. if 'required' in self.schema[field] and self.schema[field]['required']:
  878. self._unrequired_by_excludes.add(field)
  879. for exclude in excludes:
  880. if (exclude in self.schema and
  881. 'required' in self.schema[exclude] and
  882. self.schema[exclude]['required']):
  883. self._unrequired_by_excludes.add(exclude)
  884. if [True for key in excludes if key in self.document]:
  885. # Wrap each field in `excludes` list between quotes
  886. exclusion_str = ', '.join("'{0}'"
  887. .format(word) for word in excludes)
  888. self._error(field, errors.EXCLUDES_FIELD, exclusion_str)
  889. def _validate_forbidden(self, forbidden_values, field, value):
  890. """ {'type': 'list'} """
  891. if isinstance(value, _str_type):
  892. if value in forbidden_values:
  893. self._error(field, errors.FORBIDDEN_VALUE, value)
  894. elif isinstance(value, Sequence):
  895. forbidden = set(value) & set(forbidden_values)
  896. if forbidden:
  897. self._error(field, errors.FORBIDDEN_VALUES, list(forbidden))
  898. elif isinstance(value, int):
  899. if value in forbidden_values:
  900. self._error(field, errors.FORBIDDEN_VALUE, value)
  901. def _validate_items(self, items, field, values):
  902. """ {'type': 'list', 'validator': 'items'} """
  903. if len(items) != len(values):
  904. self._error(field, errors.ITEMS_LENGTH, len(items), len(values))
  905. else:
  906. schema = dict((i, definition) for i, definition in enumerate(items)) # noqa: E501
  907. validator = self._get_child_validator(document_crumb=field,
  908. schema_crumb=(field, 'items'), # noqa: E501
  909. schema=schema)
  910. if not validator(dict((i, value) for i, value in enumerate(values)),
  911. update=self.update, normalize=False):
  912. self._error(field, errors.BAD_ITEMS, validator._errors)
  913. def __validate_logical(self, operator, definitions, field, value):
  914. """ Validates value against all definitions and logs errors according
  915. to the operator. """
  916. valid_counter = 0
  917. _errors = errors.ErrorList()
  918. for i, definition in enumerate(definitions):
  919. schema = {field: definition.copy()}
  920. for rule in ('allow_unknown', 'type'):
  921. if rule not in schema[field] and rule in self.schema[field]:
  922. schema[field][rule] = self.schema[field][rule]
  923. if 'allow_unknown' not in schema[field]:
  924. schema[field]['allow_unknown'] = self.allow_unknown
  925. validator = self._get_child_validator(
  926. schema_crumb=(field, operator, i),
  927. schema=schema, allow_unknown=True)
  928. if validator(self.document, update=self.update, normalize=False):
  929. valid_counter += 1
  930. else:
  931. self._drop_nodes_from_errorpaths(validator._errors, [], [3])
  932. _errors.extend(validator._errors)
  933. return valid_counter, _errors
  934. def _validate_anyof(self, definitions, field, value):
  935. """ {'type': 'list', 'logical': 'anyof'} """
  936. valids, _errors = \
  937. self.__validate_logical('anyof', definitions, field, value)
  938. if valids < 1:
  939. self._error(field, errors.ANYOF, _errors,
  940. valids, len(definitions))
  941. def _validate_allof(self, definitions, field, value):
  942. """ {'type': 'list', 'logical': 'allof'} """
  943. valids, _errors = \
  944. self.__validate_logical('allof', definitions, field, value)
  945. if valids < len(definitions):
  946. self._error(field, errors.ALLOF, _errors,
  947. valids, len(definitions))
  948. def _validate_noneof(self, definitions, field, value):
  949. """ {'type': 'list', 'logical': 'noneof'} """
  950. valids, _errors = \
  951. self.__validate_logical('noneof', definitions, field, value)
  952. if valids > 0:
  953. self._error(field, errors.NONEOF, _errors,
  954. valids, len(definitions))
  955. def _validate_oneof(self, definitions, field, value):
  956. """ {'type': 'list', 'logical': 'oneof'} """
  957. valids, _errors = \
  958. self.__validate_logical('oneof', definitions, field, value)
  959. if valids != 1:
  960. self._error(field, errors.ONEOF, _errors,
  961. valids, len(definitions))
  962. def _validate_max(self, max_value, field, value):
  963. """ {'nullable': False } """
  964. try:
  965. if value > max_value:
  966. self._error(field, errors.MAX_VALUE)
  967. except TypeError:
  968. pass
  969. def _validate_min(self, min_value, field, value):
  970. """ {'nullable': False } """
  971. try:
  972. if value < min_value:
  973. self._error(field, errors.MIN_VALUE)
  974. except TypeError:
  975. pass
  976. def _validate_maxlength(self, max_length, field, value):
  977. """ {'type': 'integer'} """
  978. if isinstance(value, Iterable) and len(value) > max_length:
  979. self._error(field, errors.MAX_LENGTH, len(value))
  980. def _validate_minlength(self, min_length, field, value):
  981. """ {'type': 'integer'} """
  982. if isinstance(value, Iterable) and len(value) < min_length:
  983. self._error(field, errors.MIN_LENGTH, len(value))
  984. def _validate_nullable(self, nullable, field, value):
  985. """ {'type': 'boolean'} """
  986. if value is None:
  987. if not nullable:
  988. self._error(field, errors.NOT_NULLABLE)
  989. self._drop_remaining_rules(
  990. 'empty', 'forbidden', 'items', 'keyschema', 'min', 'max',
  991. 'minlength', 'maxlength', 'regex', 'schema', 'type',
  992. 'valueschema')
  993. def _validate_keyschema(self, schema, field, value):
  994. """ {'type': ['dict', 'string'], 'validator': 'bulk_schema',
  995. 'forbidden': ['rename', 'rename_handler']} """
  996. if isinstance(value, Mapping):
  997. validator = self._get_child_validator(
  998. document_crumb=field,
  999. schema_crumb=(field, 'keyschema'),
  1000. schema=dict(((k, schema) for k in value.keys())))
  1001. if not validator(dict(((k, k) for k in value.keys())),
  1002. normalize=False):
  1003. self._drop_nodes_from_errorpaths(validator._errors,
  1004. [], [2, 4])
  1005. self._error(field, errors.KEYSCHEMA, validator._errors)
  1006. def _validate_readonly(self, readonly, field, value):
  1007. """ {'type': 'boolean'} """
  1008. if readonly:
  1009. if not self._is_normalized:
  1010. self._error(field, errors.READONLY_FIELD)
  1011. # If the document was normalized (and therefore already been
  1012. # checked for readonly fields), we still have to return True
  1013. # if an error was filed.
  1014. has_error = errors.READONLY_FIELD in \
  1015. self.document_error_tree.fetch_errors_from(
  1016. self.document_path + (field,))
  1017. if self._is_normalized and has_error:
  1018. self._drop_remaining_rules()
  1019. def _validate_regex(self, pattern, field, value):
  1020. """ {'type': 'string'} """
  1021. if not isinstance(value, _str_type):
  1022. return
  1023. if not pattern.endswith('$'):
  1024. pattern += '$'
  1025. re_obj = re.compile(pattern)
  1026. if not re_obj.match(value):
  1027. self._error(field, errors.REGEX_MISMATCH)
  1028. _validate_required = dummy_for_rule_validation(""" {'type': 'boolean'} """)
  1029. def __validate_required_fields(self, document):
  1030. """ Validates that required fields are not missing.
  1031. :param document: The document being validated.
  1032. """
  1033. try:
  1034. required = set(field for field, definition in self.schema.items()
  1035. if self._resolve_rules_set(definition).
  1036. get('required') is True)
  1037. except AttributeError:
  1038. if self.is_child and self.schema_path[-1] == 'schema':
  1039. raise _SchemaRuleTypeError
  1040. else:
  1041. raise
  1042. required -= self._unrequired_by_excludes
  1043. missing = required - set(field for field in document
  1044. if document.get(field) is not None or
  1045. not self.ignore_none_values)
  1046. for field in missing:
  1047. self._error(field, errors.REQUIRED_FIELD)
  1048. # At least on field from self._unrequired_by_excludes should be
  1049. # present in document
  1050. if self._unrequired_by_excludes:
  1051. fields = set(field for field in document
  1052. if document.get(field) is not None)
  1053. if self._unrequired_by_excludes.isdisjoint(fields):
  1054. for field in self._unrequired_by_excludes - fields:
  1055. self._error(field, errors.REQUIRED_FIELD)
  1056. def _validate_schema(self, schema, field, value):
  1057. """ {'type': ['dict', 'string'],
  1058. 'anyof': [{'validator': 'schema'},
  1059. {'validator': 'bulk_schema'}]} """
  1060. if schema is None:
  1061. return
  1062. if isinstance(value, Sequence) and not isinstance(value, _str_type):
  1063. self.__validate_schema_sequence(field, schema, value)
  1064. elif isinstance(value, Mapping):
  1065. self.__validate_schema_mapping(field, schema, value)
  1066. def __validate_schema_mapping(self, field, schema, value):
  1067. schema = self._resolve_schema(schema)
  1068. allow_unknown = self.schema[field].get('allow_unknown',
  1069. self.allow_unknown)
  1070. validator = self._get_child_validator(document_crumb=field,
  1071. schema_crumb=(field, 'schema'),
  1072. schema=schema,
  1073. allow_unknown=allow_unknown)
  1074. try:
  1075. if not validator(value, update=self.update, normalize=False):
  1076. self._error(field, errors.MAPPING_SCHEMA, validator._errors)
  1077. except _SchemaRuleTypeError:
  1078. self._error(field, errors.BAD_TYPE_FOR_SCHEMA)
  1079. raise
  1080. def __validate_schema_sequence(self, field, schema, value):
  1081. schema = dict(((i, schema) for i in range(len(value))))
  1082. validator = self._get_child_validator(
  1083. document_crumb=field, schema_crumb=(field, 'schema'),
  1084. schema=schema, allow_unknown=self.allow_unknown)
  1085. validator(dict(((i, v) for i, v in enumerate(value))),
  1086. update=self.update, normalize=False)
  1087. if validator._errors:
  1088. self._drop_nodes_from_errorpaths(validator._errors, [], [2])
  1089. self._error(field, errors.SEQUENCE_SCHEMA, validator._errors)
  1090. def _validate_type(self, data_type, field, value):
  1091. """ {'type': ['string', 'list'],
  1092. 'validator': 'type'} """
  1093. if not data_type:
  1094. return
  1095. types = (data_type,) if isinstance(data_type, _str_type) else data_type
  1096. for _type in types:
  1097. # TODO remove this block on next major release
  1098. # this implementation still supports custom type validation methods
  1099. type_definition = self.types_mapping.get(_type)
  1100. if type_definition is not None:
  1101. matched = isinstance(value, type_definition.included_types) \
  1102. and not isinstance(value, type_definition.excluded_types)
  1103. else:
  1104. type_handler = self.__get_rule_handler('validate_type', _type)
  1105. matched = type_handler(value)
  1106. if matched:
  1107. return
  1108. # TODO uncomment this block on next major release
  1109. # when _validate_type_* methods were deprecated:
  1110. # type_definition = self.types_mapping[_type]
  1111. # if isinstance(value, type_definition.included_types) \
  1112. # and not isinstance(value, type_definition.excluded_types): # noqa 501
  1113. # return
  1114. self._error(field, errors.BAD_TYPE)
  1115. self._drop_remaining_rules()
  1116. def _validate_validator(self, validator, field, value):
  1117. """ {'oneof': [
  1118. {'type': 'callable'},
  1119. {'type': 'list',
  1120. 'schema': {'oneof': [{'type': 'callable'},
  1121. {'type': 'string'}]}},
  1122. {'type': 'string'}
  1123. ]} """
  1124. if isinstance(validator, _str_type):
  1125. validator = self.__get_rule_handler('validator', validator)
  1126. validator(field, value)
  1127. elif isinstance(validator, Iterable):
  1128. for v in validator:
  1129. self._validate_validator(v, field, value)
  1130. else:
  1131. validator(field, value, self._error)
  1132. def _validate_valueschema(self, schema, field, value):
  1133. """ {'type': ['dict', 'string'], 'validator': 'bulk_schema',
  1134. 'forbidden': ['rename', 'rename_handler']} """
  1135. schema_crumb = (field, 'valueschema')
  1136. if isinstance(value, Mapping):
  1137. validator = self._get_child_validator(
  1138. document_crumb=field, schema_crumb=schema_crumb,
  1139. schema=dict((k, schema) for k in value))
  1140. validator(value, update=self.update, normalize=False)
  1141. if validator._errors:
  1142. self._drop_nodes_from_errorpaths(validator._errors, [], [2])
  1143. self._error(field, errors.VALUESCHEMA, validator._errors)
  1144. RULE_SCHEMA_SEPARATOR = \
  1145. "The rule's arguments are validated against this schema:"
  1146. class InspectedValidator(type):
  1147. """ Metaclass for all validators """
  1148. def __new__(cls, *args):
  1149. if '__doc__' not in args[2]:
  1150. args[2].update({'__doc__': args[1][0].__doc__})
  1151. return super(InspectedValidator, cls).__new__(cls, *args)
  1152. def __init__(cls, *args):
  1153. def attributes_with_prefix(prefix):
  1154. return tuple(x.split('_', 2)[-1] for x in dir(cls)
  1155. if x.startswith('_' + prefix))
  1156. super(InspectedValidator, cls).__init__(*args)
  1157. cls._types_from_methods, cls.validation_rules = (), {}
  1158. for attribute in attributes_with_prefix('validate'):
  1159. # TODO remove inspection of type test methods in next major release
  1160. if attribute.startswith('type_'):
  1161. cls._types_from_methods += (attribute[len('type_'):],)
  1162. else:
  1163. cls.validation_rules[attribute] = \
  1164. cls.__get_rule_schema('_validate_' + attribute)
  1165. # TODO remove on next major release
  1166. if cls._types_from_methods:
  1167. warn("Methods for type testing are deprecated, use TypeDefinition "
  1168. "and the 'types_mapping'-property of a Validator-instance "
  1169. "instead.", DeprecationWarning)
  1170. cls.validators = tuple(x for x in attributes_with_prefix('validator'))
  1171. x = cls.validation_rules['validator']['oneof']
  1172. x[1]['schema']['oneof'][1]['allowed'] = x[2]['allowed'] = cls.validators
  1173. for rule in (x for x in cls.mandatory_validations if x != 'nullable'):
  1174. cls.validation_rules[rule]['required'] = True
  1175. cls.coercers, cls.default_setters, cls.normalization_rules = (), (), {}
  1176. for attribute in attributes_with_prefix('normalize'):
  1177. if attribute.startswith('coerce_'):
  1178. cls.coercers += (attribute[len('coerce_'):],)
  1179. elif attribute.startswith('default_setter_'):
  1180. cls.default_setters += (attribute[len('default_setter_'):],)
  1181. else:
  1182. cls.normalization_rules[attribute] = \
  1183. cls.__get_rule_schema('_normalize_' + attribute)
  1184. for rule in ('coerce', 'rename_handler'):
  1185. x = cls.normalization_rules[rule]['oneof']
  1186. x[1]['schema']['oneof'][1]['allowed'] = \
  1187. x[2]['allowed'] = cls.coercers
  1188. cls.normalization_rules['default_setter']['oneof'][1]['allowed'] = \
  1189. cls.default_setters
  1190. cls.rules = {}
  1191. cls.rules.update(cls.validation_rules)
  1192. cls.rules.update(cls.normalization_rules)
  1193. def __get_rule_schema(cls, method_name):
  1194. docstring = getattr(cls, method_name).__doc__
  1195. if docstring is None:
  1196. result = {}
  1197. else:
  1198. if RULE_SCHEMA_SEPARATOR in docstring:
  1199. docstring = docstring.split(RULE_SCHEMA_SEPARATOR)[1]
  1200. try:
  1201. result = literal_eval(docstring.strip())
  1202. except Exception:
  1203. result = {}
  1204. if not result:
  1205. warn("No validation schema is defined for the arguments of rule "
  1206. "'%s'" % method_name.split('_', 2)[-1])
  1207. return result
  1208. Validator = InspectedValidator('Validator', (BareValidator,), {})