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