shell bypass 403
曾与蒿藜同雨露,한때 잡초와 쑥과 함께 비와 이슬을 나누던 곳이 이제는 소나무와 삼나무와 함께 서리와 눈을 견뎌내고 있다.终随松柏到冰霜.かつては雑草やヨモギと共に雨や露を分かち合っていたが、今では松やヒノキと共に霜や雪に耐えている。曾与蒿藜同雨露,Once sharing rain and dew with weeds and wormwood, now enduring frost and snow with pines and cypresses.终随松柏到冰霜.曾与蒿藜同雨露한때 잡초와 쑥과 함께 비와 이슬을 나누던 곳이 이제는 소나무와 삼나무와 함께 서리와 눈을 견뎌내고 있다.,终随松柏到冰霜.譖セ荳手珍阯懷酔髮ィ髴イ�檎サ磯囂譚セ譟丞芦蜀ー髴�曾与蒿藜同雨露,鏇句笌钂胯棞鍚岄洦闇诧紝缁堥殢鏉炬煆鍒板啺闇�终随松柏到冰霜.曾与蒿藜同雨露,한때 잡초와 쑥과 함께 비와 이슬을 나누던 곳이 이제는 소나무와 삼나무와 함께 서리와 눈을 견뎌내고 있다.终随松柏到冰霜.曾与蒿藜同雨露,终随松柏到冰霜.
"""
Extensible validation for Python dictionaries.
This module implements Cerberus Validator class
:copyright: 2012-2016 by Nicola Iarocci.
:license: ISC, see LICENSE for more details.
Full documentation is available at http://python-cerberus.org
"""
from __future__ import absolute_import
from ast import literal_eval
from collections import Hashable, Iterable, Mapping, Sequence
from copy import copy
from datetime import date, datetime
import re
from warnings import warn
from cerberus import errors
from cerberus.platform import _int_types, _str_type
from cerberus.schema import (schema_registry, rules_set_registry,
DefinitionSchema, SchemaError)
from cerberus.utils import drop_item_from_tuple, isclass
toy_error_handler = errors.ToyErrorHandler()
def dummy_for_rule_validation(rule_constraints):
def dummy(self, constraint, field, value):
raise RuntimeError('Dummy method called. Its purpose is to hold just'
'validation constraints for a rule in its '
'docstring.')
f = dummy
f.__doc__ = rule_constraints
return f
class DocumentError(Exception):
""" Raised when the target document is missing or has the wrong format """
pass
class _SchemaRuleTypeError(Exception):
""" Raised when a schema (list) validation encounters a mapping.
Not supposed to be used outside this module. """
pass
class Validator(object):
""" Validator class. Normalizes and/or validates any mapping against a
validation-schema which is provided as an argument at class instantiation
or upon calling the :meth:`~cerberus.Validator.validate`,
:meth:`~cerberus.Validator.validated` or
:meth:`~cerberus.Validator.normalized` method. An instance itself is
callable and executes a validation.
All instantiation parameters are optional.
There are the introspective properties :attr:`types`, :attr:`validators`,
:attr:`coercers`, :attr:`default_setters`, :attr:`rules`,
:attr:`normalization_rules` and :attr:`validation_rules`.
The attributes reflecting the available rules are assembled considering
constraints that are defined in the docstrings of rules' methods and is
effectively used as validation schema for :attr:`schema`.
:param schema: See :attr:`~cerberus.Validator.schema`.
Defaults to :obj:`None`.
:type schema: any :term:`mapping`
:param ignore_none_values: See :attr:`~cerberus.Validator.ignore_none_values`.
Defaults to ``False``.
:type ignore_none_values: :class:`bool`
:param allow_unknown: See :attr:`~cerberus.Validator.allow_unknown`.
Defaults to ``False``.
:type allow_unknown: :class:`bool` or any :term:`mapping`
:param purge_unknown: See :attr:`~cerberus.Validator.purge_unknown`.
Defaults to to ``False``.
:type purge_unknown: :class:`bool`
:param error_handler: The error handler that formats the result of
:attr:`~cerberus.Validator.errors`.
When given as two-value tuple with an error-handler
class and a dictionary, the latter is passed to the
initialization of the error handler.
Default: :class:`~cerberus.errors.BasicErrorHandler`.
:type error_handler: class or instance based on
:class:`~cerberus.errors.BaseErrorHandler` or
:class:`tuple`
""" # noqa
mandatory_validations = ('nullable', )
""" Rules that are evaluated on any field, regardless whether defined in
the schema or not.
Type: :class:`tuple` """
priority_validations = ('nullable', 'readonly', 'type')
""" Rules that will be processed in that order before any other and abort
validation of a document's field if return ``True``.
Type: :class:`tuple` """
_valid_schemas = set()
""" A :class:`set` of hashed validation schemas that are legit for a
particular ``Validator`` class. """
def __init__(self, *args, **kwargs):
""" The arguments will be treated as with this signature:
__init__(self, schema=None, ignore_none_values=False,
allow_unknown=False, purge_unknown=False,
error_handler=errors.BasicErrorHandler)
"""
self.document = None
""" The document that is or was recently processed.
Type: any :term:`mapping` """
self._errors = errors.ErrorList()
""" The list of errors that were encountered since the last document
processing was invoked.
Type: :class:`~cerberus.errors.ErrorList` """
self.recent_error = None
""" The last individual error that was submitted.
Type: :class:`~cerberus.errors.ValidationError` """
self.document_error_tree = errors.DocumentErrorTree()
""" A tree representiation of encountered errors following the
structure of the document.
Type: :class:`~cerberus.errors.DocumentErrorTree` """
self.schema_error_tree = errors.SchemaErrorTree()
""" A tree representiation of encountered errors following the
structure of the schema.
Type: :class:`~cerberus.errors.SchemaErrorTree` """
self.document_path = ()
""" The path within the document to the current sub-document.
Type: :class:`tuple` """
self.schema_path = ()
""" The path within the schema to the current sub-schema.
Type: :class:`tuple` """
self.update = False
self.error_handler = self.__init_error_handler(kwargs)
""" The error handler used to format :attr:`~cerberus.Validator.errors`
and process submitted errors with
:meth:`~cerberus.Validator._error`.
Type: :class:`~cerberus.errors.BaseErrorHandler` """
self.__store_config(args, kwargs)
self.schema = kwargs.get('schema', None)
self.allow_unknown = kwargs.get('allow_unknown', False)
def __init_error_handler(self, kwargs):
error_handler = kwargs.pop('error_handler', errors.BasicErrorHandler)
if isinstance(error_handler, tuple):
error_handler, eh_config = error_handler
else:
eh_config = {}
if isclass(error_handler) and \
issubclass(error_handler, errors.BaseErrorHandler):
return error_handler(**eh_config)
elif isinstance(error_handler, errors.BaseErrorHandler):
return error_handler
else:
raise RuntimeError('Invalid error_handler.')
def __store_config(self, args, kwargs):
""" Assign args to kwargs and store configuration. """
signature = ('schema', 'ignore_none_values', 'allow_unknown',
'purge_unknown')
for i, p in enumerate(signature[:len(args)]):
if p in kwargs:
raise TypeError("__init__ got multiple values for argument "
"'%s'" % p)
else:
kwargs[p] = args[i]
self._config = kwargs
""" This dictionary holds the configuration arguments that were used to
initialize the :class:`Validator` instance except the
``error_handler``. """
@classmethod
def clear_caches(cls):
""" Purge the cache of known valid schemas. """
cls._valid_schemas.clear()
def _error(self, *args):
""" Creates and adds one or multiple errors.
:param args: Accepts different argument's signatures.
*1. Bulk addition of errors:*
- :term:`iterable` of
:class:`~cerberus.errors.ValidationError`-instances
The errors will be added to
:attr:`~cerberus.Validator._errors`.
*2. Custom error:*
- the invalid field's name
- the error message
A custom error containing the message will be created and
added to :attr:`~cerberus.Validator._errors`.
There will however be fewer information contained in the
error (no reference to the violated rule and its
constraint).
*3. Defined error:*
- the invalid field's name
- the error-reference, see :mod:`cerberus.errors`
- arbitrary, supplemental information about the error
A :class:`~cerberus.errors.ValidationError` instance will
be created and added to
:attr:`~cerberus.Validator._errors`.
"""
if len(args) == 1:
self._errors.extend(args[0])
self._errors.sort()
for error in args[0]:
self.document_error_tree += error
self.schema_error_tree += error
self.error_handler.emit(error)
elif len(args) == 2 and isinstance(args[1], _str_type):
self._error(args[0], errors.CUSTOM, args[1])
elif len(args) >= 2:
field = args[0]
code = args[1].code
rule = args[1].rule
info = args[2:]
document_path = self.document_path + (field, )
schema_path = self.schema_path
if code != errors.UNKNOWN_FIELD.code and rule is not None:
schema_path += (field, rule)
if not rule:
constraint = None
else:
field_definitions = self._resolve_rules_set(self.schema[field])
if rule == 'nullable':
constraint = field_definitions.get(rule, False)
else:
constraint = field_definitions[rule]
value = self.document.get(field)
self.recent_error = errors.ValidationError(
document_path, schema_path, code, rule, constraint, value, info
)
self._error([self.recent_error])
def _get_child_validator(self, document_crumb=None, schema_crumb=None,
**kwargs):
""" Creates a new instance of Validator-(sub-)class. All initial
parameters of the parent are passed to the initialization, unless
a parameter is given as an explicit *keyword*-parameter.
:param document_crumb: Extends the
:attr:`~cerberus.Validator.document_path`
of the child-validator.
:type document_crumb: :class:`tuple` or :term:`hashable`
:param schema_crumb: Extends the
:attr:`~cerberus.Validator.schema_path`
of the child-validator.
:type schema_crumb: :class:`tuple` or hashable
:param kwargs: Overriding keyword-arguments for initialization.
:type kwargs: :class:`dict`
:return: an instance of ``self.__class__``
"""
child_config = self._config.copy()
child_config.update(kwargs)
if not self.is_child:
child_config['is_child'] = True
child_config['error_handler'] = toy_error_handler
child_config['root_allow_unknown'] = self.allow_unknown
child_config['root_document'] = self.document
child_config['root_schema'] = self.schema
child_validator = self.__class__(**child_config)
if document_crumb is None:
child_validator.document_path = self.document_path
else:
if not isinstance(document_crumb, tuple):
document_crumb = (document_crumb, )
child_validator.document_path = self.document_path + document_crumb
if schema_crumb is None:
child_validator.schema_path = self.schema_path
else:
if not isinstance(schema_crumb, tuple):
schema_crumb = (schema_crumb, )
child_validator.schema_path = self.schema_path + schema_crumb
return child_validator
def __get_rule_handler(self, domain, rule):
methodname = '_{0}_{1}'.format(domain, rule.replace(' ', '_'))
return getattr(self, methodname, None)
def _drop_nodes_from_errorpaths(self, _errors, dp_items, sp_items):
""" Removes nodes by index from an errorpath, relatively to the
basepaths of self.
:param errors: A list of :class:`errors.ValidationError` instances.
:param dp_items: A list of integers, pointing at the nodes to drop from
the :attr:`document_path`.
:param sp_items: Alike ``dp_items``, but for :attr:`schema_path`.
"""
dp_basedepth = len(self.document_path)
sp_basedepth = len(self.schema_path)
for error in _errors:
for i in sorted(dp_items, reverse=True):
error.document_path = \
drop_item_from_tuple(error.document_path, dp_basedepth + i)
for i in sorted(sp_items, reverse=True):
error.schema_path = \
drop_item_from_tuple(error.schema_path, sp_basedepth + i)
if error.child_errors:
self._drop_nodes_from_errorpaths(error.child_errors,
dp_items, sp_items)
def _lookup_field(self, path):
""" Searches for a field as defined by path. This method is used by the
``dependency`` evaluation logic.
:param path: Path elements are separated by a ``.``. A leading ``^``
indicates that the path relates to the document root,
otherwise it relates to the currently evaluated document,
which is possibly a subdocument.
The sequence ``^^`` at the start will be interpreted as a
literal ``^``.
:type path: :class:`str`
:returns: Either the found field name and its value or :obj:`None` for
both.
:rtype: A two-value :class:`tuple`.
"""
if path.startswith('^'):
path = path[1:]
context = self.document if path.startswith('^') \
else self.root_document
else:
context = self.document
parts = path.split('.')
for part in parts:
context = context.get(part)
if context is None:
return None, None
return parts[-1], context
def _resolve_rules_set(self, rules_set):
if isinstance(rules_set, Mapping):
return rules_set
elif isinstance(rules_set, _str_type):
return self.rules_set_registry.get(rules_set)
return None
def _resolve_schema(self, schema):
if isinstance(schema, Mapping):
return schema
elif isinstance(schema, _str_type):
return self.schema_registry.get(schema)
return None
# Properties
@property
def allow_unknown(self):
""" If ``True`` unknown fields that are not defined in the schema will
be ignored. If a mapping with a validation schema is given, any
undefined field will be validated against its rules.
Also see :ref:`allowing-the-unknown`.
Type: :class:`bool` or any :term:`mapping` """
return self._config.get('allow_unknown', False)
@allow_unknown.setter
def allow_unknown(self, value):
if not (self.is_child or isinstance(value, (bool, DefinitionSchema))):
DefinitionSchema(self, {'allow_unknown': value})
self._config['allow_unknown'] = value
@property
def errors(self):
""" The errors of the last processing formatted by the handler that is
bound to :attr:`~cerberus.Validator.error_handler`. """
return self.error_handler(self._errors)
@property
def ignore_none_values(self):
""" Whether to not process :obj:`None`-values in a document or not.
Type: :class:`bool` """
return self._config.get('ignore_none_values', False)
@ignore_none_values.setter
def ignore_none_values(self, value):
self._config['ignore_none_values'] = value
@property
def is_child(self):
""" ``True`` for child-validators obtained with
:meth:`~cerberus.Validator._get_child_validator`.
Type: :class:`bool` """
return self._config.get('is_child', False)
@property
def is_normalized(self):
""" ``True`` if the document is already normalized. """
return self._config.get('is_normalized', False)
@is_normalized.setter
def is_normalized(self, value):
self._config['is_normalized'] = value
@property
def purge_unknown(self):
""" If ``True`` unknown fields will be deleted from the document
unless a validation is called with disabled normalization.
Also see :ref:`purging-unknown-fields`. Type: :class:`bool` """
return self._config.get('purge_unknown', False)
@purge_unknown.setter
def purge_unknown(self, value):
self._config['purge_unknown'] = value
@property
def root_allow_unknown(self):
""" The :attr:`~cerberus.Validator.allow_unknown` attribute of the
first level ancestor of a child validator. """
return self._config.get('root_allow_unknown', self.allow_unknown)
@property
def root_document(self):
""" The :attr:`~cerberus.Validator.document` attribute of the
first level ancestor of a child validator. """
return self._config.get('root_document', self.document)
@property
def rules_set_registry(self):
""" The registry that holds referenced rules sets.
Type: :class:`~cerberus.Registry` """
return self._config.get('rules_set_registry', rules_set_registry)
@rules_set_registry.setter
def rules_set_registry(self, registry):
self._config['rules_set_registry'] = registry
@property
def root_schema(self):
""" The :attr:`~cerberus.Validator.schema` attribute of the
first level ancestor of a child validator. """
return self._config.get('root_schema', self.schema)
@property
def schema(self):
""" The validation schema of a validator. When a schema is passed to
a method, it replaces this attribute.
Type: any :term:`mapping` or :obj:`None` """
return self._schema
@schema.setter
def schema(self, schema):
if schema is None:
self._schema = None
elif self.is_child or isinstance(schema, DefinitionSchema):
self._schema = schema
else:
self._schema = DefinitionSchema(self, schema)
@property
def schema_registry(self):
""" The registry that holds referenced schemas.
Type: :class:`~cerberus.Registry` """
return self._config.get('schema_registry', schema_registry)
@schema_registry.setter
def schema_registry(self, registry):
self._config['schema_registry'] = registry
# Document processing
def __init_processing(self, document, schema=None):
self._errors = errors.ErrorList()
self.recent_error = None
self.document_error_tree = errors.DocumentErrorTree()
self.schema_error_tree = errors.SchemaErrorTree()
self.document = copy(document)
if schema is not None:
self.schema = DefinitionSchema(self, schema)
elif self.schema is None:
if isinstance(self.allow_unknown, Mapping):
self._schema = {}
else:
raise SchemaError(errors.SCHEMA_ERROR_MISSING)
if document is None:
raise DocumentError(errors.DOCUMENT_MISSING)
if not isinstance(document, Mapping):
raise DocumentError(
errors.DOCUMENT_FORMAT.format(document))
self.error_handler.start(self)
# # Normalizing
def normalized(self, document, schema=None, always_return_document=False):
""" Returns the document normalized according to the specified rules
of a schema.
:param document: The document to normalize.
:type document: any :term:`mapping`
:param schema: The validation schema. Defaults to :obj:`None`. If not
provided here, the schema must have been provided at
class instantiation.
:type schema: any :term:`mapping`
:param always_return_document: Return the document, even if an error
occurred. Defaults to: ``False``.
:type always_return_document: :class:`bool`
:return: A normalized copy of the provided mapping or :obj:`None` if an
error occurred during normalization.
"""
self.__init_processing(document, schema)
self.__normalize_mapping(self.document, self.schema)
self.error_handler.end(self)
if self._errors and not always_return_document:
return None
else:
return self.document
def __normalize_mapping(self, mapping, schema):
if isinstance(schema, _str_type):
schema = self._resolve_schema(schema)
schema = schema.copy()
for field in schema:
schema[field] = self._resolve_rules_set(schema[field])
self.__normalize_rename_fields(mapping, schema)
if self.purge_unknown:
self._normalize_purge_unknown(mapping, schema)
# Check `readonly` fields before applying default values because
# a field's schema definition might contain both `readonly` and
# `default`.
self.__validate_readonly_fields(mapping, schema)
self.__normalize_default_fields(mapping, schema)
self._normalize_coerce(mapping, schema)
self.__normalize_containers(mapping, schema)
return mapping
def _normalize_coerce(self, mapping, schema):
""" {'oneof': [
{'type': 'callable'},
{'type': 'list',
'schema': {'oneof': [{'type': 'callable'},
{'type': 'string'}]}},
{'type': 'string'}
]} """
error = errors.COERCION_FAILED
for field in mapping:
if field in schema and 'coerce' in schema[field]:
mapping[field] = self.__normalize_coerce(
schema[field]['coerce'], field, mapping[field], error)
elif isinstance(self.allow_unknown, Mapping) and \
'coerce' in self.allow_unknown:
mapping[field] = self.__normalize_coerce(
self.allow_unknown['coerce'], field, mapping[field], error)
def __normalize_coerce(self, processor, field, value, error):
if isinstance(processor, _str_type):
processor = self.__get_rule_handler('normalize_coerce', processor)
elif isinstance(processor, Iterable):
result = value
for p in processor:
result = self.__normalize_coerce(p, field, result, error)
if errors.COERCION_FAILED in \
self.document_error_tree.fetch_errors_from(
self.document_path + (field,)):
break
return result
try:
return processor(value)
except Exception as e:
self._error(field, error, str(e))
return value
def __normalize_containers(self, mapping, schema):
for field in mapping:
if field not in schema:
continue
# TODO: This check conflates validation and normalization
if isinstance(mapping[field], Mapping):
if 'keyschema' in schema[field]:
self.__normalize_mapping_per_keyschema(
field, mapping, schema[field]['keyschema'])
if 'valueschema' in schema[field]:
self.__normalize_mapping_per_valueschema(
field, mapping, schema[field]['valueschema'])
if set(schema[field]) & set(('allow_unknown', 'purge_unknown',
'schema')):
try:
self.__normalize_mapping_per_schema(
field, mapping, schema)
except _SchemaRuleTypeError:
pass
elif isinstance(mapping[field], _str_type):
continue
elif isinstance(mapping[field], Sequence) and \
'schema' in schema[field]:
self.__normalize_sequence(field, mapping, schema)
def __normalize_mapping_per_keyschema(self, field, mapping, property_rules):
schema = dict(((k, property_rules) for k in mapping[field]))
document = dict(((k, k) for k in mapping[field]))
validator = self._get_child_validator(
document_crumb=field, schema_crumb=(field, 'keyschema'),
schema=schema)
result = validator.normalized(document, always_return_document=True)
if validator._errors:
self._drop_nodes_from_errorpaths(validator._errors, [], [2, 4])
self._error(validator._errors)
for k in result:
if k == result[k]:
continue
if result[k] in mapping[field]:
warn("Normalizing keys of {path}: {key} already exists, "
"its value is replaced."
.format(path='.'.join(self.document_path + (field,)),
key=k))
mapping[field][result[k]] = mapping[field][k]
else:
mapping[field][result[k]] = mapping[field][k]
del mapping[field][k]
def __normalize_mapping_per_valueschema(self, field, mapping, value_rules):
schema = dict(((k, value_rules) for k in mapping[field]))
validator = self._get_child_validator(
document_crumb=field, schema_crumb=(field, 'valueschema'),
schema=schema)
mapping[field] = validator.normalized(mapping[field],
always_return_document=True)
if validator._errors:
self._drop_nodes_from_errorpaths(validator._errors, [], [2])
self._error(validator._errors)
def __normalize_mapping_per_schema(self, field, mapping, schema):
validator = self._get_child_validator(
document_crumb=field, schema_crumb=(field, 'schema'),
schema=schema[field]['schema'],
allow_unknown=schema[field].get('allow_unknown', self.allow_unknown), # noqa
purge_unknown=schema[field].get('purge_unknown', self.purge_unknown)) # noqa
mapping[field] = validator.normalized(mapping[field],
always_return_document=True)
if validator._errors:
self._error(validator._errors)
def __normalize_sequence(self, field, mapping, schema):
schema = dict(((k, schema[field]['schema'])
for k in range(len(mapping[field]))))
document = dict((k, v) for k, v in enumerate(mapping[field]))
validator = self._get_child_validator(
document_crumb=field, schema_crumb=(field, 'schema'),
schema=schema)
result = validator.normalized(document, always_return_document=True)
for i in result:
mapping[field][i] = result[i]
if validator._errors:
self._drop_nodes_from_errorpaths(validator._errors, [], [2])
self._error(validator._errors)
@staticmethod
def _normalize_purge_unknown(mapping, schema):
""" {'type': 'boolean'} """
for field in tuple(mapping):
if field not in schema:
del mapping[field]
return mapping
def __normalize_rename_fields(self, mapping, schema):
for field in tuple(mapping):
if field in schema:
self._normalize_rename(mapping, schema, field)
self._normalize_rename_handler(mapping, schema, field)
elif isinstance(self.allow_unknown, Mapping) and \
'rename_handler' in self.allow_unknown:
self._normalize_rename_handler(
mapping, {field: self.allow_unknown}, field)
return mapping
def _normalize_rename(self, mapping, schema, field):
""" {'type': 'hashable'} """
if 'rename' in schema[field]:
mapping[schema[field]['rename']] = mapping[field]
del mapping[field]
def _normalize_rename_handler(self, mapping, schema, field):
""" {'oneof': [
{'type': 'callable'},
{'type': 'list',
'schema': {'oneof': [{'type': 'callable'},
{'type': 'string'}]}},
{'type': 'string'}
]} """
if 'rename_handler' not in schema[field]:
return
new_name = self.__normalize_coerce(
schema[field]['rename_handler'], field, field,
errors.RENAMING_FAILED)
if new_name != field:
mapping[new_name] = mapping[field]
del mapping[field]
def __validate_readonly_fields(self, mapping, schema):
for field in (x for x in schema if x in mapping and
self._resolve_rules_set(schema[x]).get('readonly')):
self._validate_readonly(schema[field]['readonly'], field,
mapping[field])
def __normalize_default_fields(self, mapping, schema):
fields = [x for x in schema if x not in mapping or
mapping[x] is None and not schema[x].get('nullable', False)]
try:
fields_with_default = [x for x in fields if 'default' in schema[x]]
except TypeError:
raise _SchemaRuleTypeError
for field in fields_with_default:
self._normalize_default(mapping, schema, field)
known_fields_states = set()
fields = [x for x in fields if 'default_setter' in schema[x]]
while fields:
field = fields.pop(0)
try:
self._normalize_default_setter(mapping, schema, field)
except KeyError:
fields.append(field)
except Exception as e:
self._error(field, errors.SETTING_DEFAULT_FAILED, str(e))
fields_state = tuple(fields)
if fields_state in known_fields_states:
for field in fields:
self._error(field, errors.SETTING_DEFAULT_FAILED,
'Circular dependencies of default setters.')
break
else:
known_fields_states.add(fields_state)
def _normalize_default(self, mapping, schema, field):
""" {'nullable': True} """
mapping[field] = schema[field]['default']
def _normalize_default_setter(self, mapping, schema, field):
""" {'oneof': [
{'type': 'callable'},
{'type': 'string'}
]} """
if 'default_setter' in schema[field]:
setter = schema[field]['default_setter']
if isinstance(setter, _str_type):
setter = self.__get_rule_handler('normalize_default_setter',
setter)
mapping[field] = setter(mapping)
# # Validating
def validate(self, document, schema=None, update=False, normalize=True):
""" Normalizes and validates a mapping against a validation-schema of
defined rules.
:param document: The document to normalize.
:type document: any :term:`mapping`
:param schema: The validation schema. Defaults to :obj:`None`. If not
provided here, the schema must have been provided at
class instantiation.
:type schema: any :term:`mapping`
:param update: If ``True``, required fields won't be checked.
:type update: :class:`bool`
:param normalize: If ``True``, normalize the document before validation.
:type normalize: :class:`bool`
:return: ``True`` if validation succeeds, otherwise ``False``. Check
the :func:`errors` property for a list of processing errors.
:rtype: :class:`bool`
"""
self.update = update
self._unrequired_by_excludes = set()
self.__init_processing(document, schema)
if normalize:
self.__normalize_mapping(self.document, self.schema)
self.is_normalized = True
for field in self.document:
if self.ignore_none_values and self.document[field] is None:
continue
definitions = self.schema.get(field)
if definitions is not None:
self.__validate_definitions(definitions, field)
else:
self.__validate_unknown_fields(field)
if not self.update:
self.__validate_required_fields(self.document)
self.error_handler.end(self)
return not bool(self._errors)
__call__ = validate
def validated(self, *args, **kwargs):
""" Wrapper around :meth:`~cerberus.Validator.validate` that returns
the normalized and validated document or :obj:`None` if validation
failed. """
always_return_document = kwargs.pop('always_return_document', False)
self.validate(*args, **kwargs)
if self._errors and not always_return_document:
return None
else:
return self.document
def __validate_unknown_fields(self, field):
if self.allow_unknown:
value = self.document[field]
if isinstance(self.allow_unknown, (Mapping, _str_type)):
# validate that unknown fields matches the schema
# for unknown_fields
schema_crumb = 'allow_unknown' if self.is_child \
else '__allow_unknown__'
validator = self._get_child_validator(
schema_crumb=schema_crumb,
schema={field: self.allow_unknown})
if not validator({field: value}, normalize=False):
self._error(validator._errors)
else:
self._error(field, errors.UNKNOWN_FIELD)
# Remember to keep the validations method below this line
# sorted alphabetically
def __validate_definitions(self, definitions, field):
""" Validate a field's value against its defined rules. """
def validate_rule(rule):
validator = self.__get_rule_handler('validate', rule)
if validator:
return validator(definitions.get(rule, None), field, value)
definitions = self._resolve_rules_set(definitions)
value = self.document[field]
""" _validate_-methods must return True to abort validation. """
prior_rules = tuple((x for x in self.priority_validations
if x in definitions or
x in self.mandatory_validations))
for rule in prior_rules:
if validate_rule(rule):
return
rules = set(definitions)
rules |= set(self.mandatory_validations)
rules -= set(prior_rules + ('allow_unknown', 'required'))
rules -= set(self.normalization_rules)
for rule in rules:
try:
validate_rule(rule)
except _SchemaRuleTypeError:
break
_validate_allow_unknown = dummy_for_rule_validation(
""" {'oneof': [{'type': 'boolean'},
{'type': ['dict', 'string'],
'validator': 'bulk_schema'}]} """)
def _validate_allowed(self, allowed_values, field, value):
""" {'type': 'list'} """
if isinstance(value, Iterable) and not isinstance(value, _str_type):
unallowed = set(value) - set(allowed_values)
if unallowed:
self._error(field, errors.UNALLOWED_VALUES, list(unallowed))
else:
if value not in allowed_values:
self._error(field, errors.UNALLOWED_VALUE, value)
def _validate_dependencies(self, dependencies, field, value):
""" {'type': ['dict', 'hashable', 'hashables']} """
if isinstance(dependencies, _str_type):
dependencies = [dependencies]
if isinstance(dependencies, Sequence):
self.__validate_dependencies_sequence(dependencies, field)
elif isinstance(dependencies, Mapping):
self.__validate_dependencies_mapping(dependencies, field)
if self.document_error_tree.fetch_node_from(
self.schema_path + (field, 'dependencies')) is not None:
return True
def __validate_dependencies_mapping(self, dependencies, field):
validated_dependencies_counter = 0
error_info = {}
for dependency_name, dependency_values in dependencies.items():
if (not isinstance(dependency_values, Sequence) or
isinstance(dependency_values, _str_type)):
dependency_values = [dependency_values]
wanted_field, wanted_field_value = \
self._lookup_field(dependency_name)
if wanted_field_value in dependency_values:
validated_dependencies_counter += 1
else:
error_info.update({dependency_name: wanted_field_value})
if validated_dependencies_counter != len(dependencies):
self._error(field, errors.DEPENDENCIES_FIELD_VALUE, error_info)
def __validate_dependencies_sequence(self, dependencies, field):
for dependency in dependencies:
if self._lookup_field(dependency)[0] is None:
self._error(field, errors.DEPENDENCIES_FIELD, dependency)
def _validate_empty(self, empty, field, value):
""" {'type': 'boolean'} """
if isinstance(value, Iterable) and len(value) == 0 and not empty:
self._error(field, errors.EMPTY_NOT_ALLOWED)
def _validate_excludes(self, excludes, field, value):
""" {'type': ['hashable', 'hashables']} """
if isinstance(excludes, Hashable):
excludes = [excludes]
# Save required field to be checked latter
if 'required' in self.schema[field] and self.schema[field]['required']:
self._unrequired_by_excludes.add(field)
for exclude in excludes:
if (exclude in self.schema and
'required' in self.schema[exclude] and
self.schema[exclude]['required']):
self._unrequired_by_excludes.add(exclude)
if [True for key in excludes if key in self.document]:
# Wrap each field in `excludes` list between quotes
exclusion_str = ', '.join("'{0}'"
.format(word) for word in excludes)
self._error(field, errors.EXCLUDES_FIELD, exclusion_str)
def _validate_forbidden(self, forbidden_values, field, value):
""" {'type': 'list'} """
if isinstance(value, _str_type):
if value in forbidden_values:
self._error(field, errors.FORBIDDEN_VALUE, value)
elif isinstance(value, Sequence):
forbidden = set(value) & set(forbidden_values)
if forbidden:
self._error(field, errors.FORBIDDEN_VALUES, list(forbidden))
elif isinstance(value, int):
if value in forbidden_values:
self._error(field, errors.FORBIDDEN_VALUE, value)
def _validate_items(self, items, field, values):
""" {'type': 'list', 'validator': 'items'} """
if len(items) != len(values):
self._error(field, errors.ITEMS_LENGTH, len(items), len(values))
else:
schema = dict((i, definition) for i, definition in enumerate(items)) # noqa
validator = self._get_child_validator(document_crumb=field,
schema_crumb=(field, 'items'), # noqa
schema=schema)
if not validator(dict((i, value) for i, value in enumerate(values)),
update=self.update, normalize=False):
self._error(field, errors.BAD_ITEMS, validator._errors)
def __validate_logical(self, operator, definitions, field, value):
""" Validates value against all definitions and logs errors according
to the operator. """
valid_counter = 0
_errors = errors.ErrorList()
for i, definition in enumerate(definitions):
schema = {field: definition.copy()}
for rule in ('allow_unknown', 'type'):
if rule not in schema[field] and rule in self.schema[field]:
schema[field][rule] = self.schema[field][rule]
if 'allow_unknown' not in schema[field]:
schema[field]['allow_unknown'] = self.allow_unknown
validator = self._get_child_validator(
schema_crumb=(field, operator, i),
schema=schema, allow_unknown=True)
if validator(self.document, update=self.update, normalize=False):
valid_counter += 1
else:
self._drop_nodes_from_errorpaths(validator._errors, [], [3])
_errors.extend(validator._errors)
return valid_counter, _errors
def _validate_anyof(self, definitions, field, value):
""" {'type': 'list', 'logical': 'anyof'} """
valids, _errors = \
self.__validate_logical('anyof', definitions, field, value)
if valids < 1:
self._error(field, errors.ANYOF, _errors,
valids, len(definitions))
def _validate_allof(self, definitions, field, value):
""" {'type': 'list', 'logical': 'allof'} """
valids, _errors = \
self.__validate_logical('allof', definitions, field, value)
if valids < len(definitions):
self._error(field, errors.ALLOF, _errors,
valids, len(definitions))
def _validate_noneof(self, definitions, field, value):
""" {'type': 'list', 'logical': 'noneof'} """
valids, _errors = \
self.__validate_logical('noneof', definitions, field, value)
if valids > 0:
self._error(field, errors.NONEOF, _errors,
valids, len(definitions))
def _validate_oneof(self, definitions, field, value):
""" {'type': 'list', 'logical': 'oneof'} """
valids, _errors = \
self.__validate_logical('oneof', definitions, field, value)
if valids != 1:
self._error(field, errors.ONEOF, _errors,
valids, len(definitions))
def _validate_max(self, max_value, field, value):
""" {'nullable': False } """
try:
if value > max_value:
self._error(field, errors.MAX_VALUE)
except TypeError:
pass
def _validate_min(self, min_value, field, value):
""" {'nullable': False } """
try:
if value < min_value:
self._error(field, errors.MIN_VALUE)
except TypeError:
pass
def _validate_maxlength(self, max_length, field, value):
""" {'type': 'integer'} """
if isinstance(value, Iterable) and len(value) > max_length:
self._error(field, errors.MAX_LENGTH, len(value))
def _validate_minlength(self, min_length, field, value):
""" {'type': 'integer'} """
if isinstance(value, Iterable) and len(value) < min_length:
self._error(field, errors.MIN_LENGTH, len(value))
def _validate_nullable(self, nullable, field, value):
""" {'type': 'boolean'} """
if value is None:
if nullable:
return True
else:
self._error(field, errors.NOT_NULLABLE)
return True
def _validate_keyschema(self, schema, field, value):
""" {'type': ['dict', 'string'], 'validator': 'bulk_schema',
'forbidden': ['rename', 'rename_handler']} """
if isinstance(value, Mapping):
validator = self._get_child_validator(
document_crumb=field,
schema_crumb=(field, 'keyschema'),
schema=dict(((k, schema) for k in value.keys())))
if not validator(dict(((k, k) for k in value.keys())),
normalize=False):
self._drop_nodes_from_errorpaths(validator._errors,
[], [2, 4])
self._error(field, errors.KEYSCHEMA, validator._errors)
def _validate_readonly(self, readonly, field, value):
""" {'type': 'boolean'} """
if readonly:
if not self.is_normalized:
self._error(field, errors.READONLY_FIELD)
return True
# If the document was normalized (and therefore already been
# checked for readonly fields), we still have to return True
# if an error was filed.
has_error = errors.READONLY_FIELD in \
self.document_error_tree.fetch_errors_from(
self.document_path + (field,))
if self.is_normalized and has_error:
return True
def _validate_regex(self, pattern, field, value):
""" {'type': 'string'} """
if not isinstance(value, _str_type):
return
if not pattern.endswith('$'):
pattern += '$'
re_obj = re.compile(pattern)
if not re_obj.match(value):
self._error(field, errors.REGEX_MISMATCH)
_validate_required = dummy_for_rule_validation(""" {'type': 'boolean'} """)
def __validate_required_fields(self, document):
""" Validates that required fields are not missing.
:param document: The document being validated.
"""
try:
required = set(field for field, definition in self.schema.items()
if self._resolve_rules_set(definition).
get('required') is True)
except AttributeError:
if self.is_child and self.schema_path[-1] == 'schema':
raise _SchemaRuleTypeError
else:
raise
required -= self._unrequired_by_excludes
missing = required - set(field for field in document
if document.get(field) is not None or
not self.ignore_none_values)
for field in missing:
self._error(field, errors.REQUIRED_FIELD)
# At least on field from self._unrequired_by_excludes should be
# present in document
if self._unrequired_by_excludes:
fields = set(field for field in document
if document.get(field) is not None)
if self._unrequired_by_excludes.isdisjoint(fields):
for field in self._unrequired_by_excludes - fields:
self._error(field, errors.REQUIRED_FIELD)
def _validate_schema(self, schema, field, value):
""" {'type': ['dict', 'string'],
'anyof': [{'validator': 'schema'},
{'validator': 'bulk_schema'}]} """
if schema is None:
return
if isinstance(value, Sequence) and not isinstance(value, _str_type):
self.__validate_schema_sequence(field, schema, value)
elif isinstance(value, Mapping):
self.__validate_schema_mapping(field, schema, value)
def __validate_schema_mapping(self, field, schema, value):
schema = self._resolve_schema(schema)
allow_unknown = self.schema[field].get('allow_unknown',
self.allow_unknown)
validator = self._get_child_validator(document_crumb=field,
schema_crumb=(field, 'schema'),
schema=schema,
allow_unknown=allow_unknown)
try:
if not validator(value, update=self.update, normalize=False):
self._error(validator._errors)
except _SchemaRuleTypeError:
self._error(field, errors.BAD_TYPE_FOR_SCHEMA)
raise
def __validate_schema_sequence(self, field, schema, value):
schema = dict(((i, schema) for i in range(len(value))))
validator = self._get_child_validator(
document_crumb=field, schema_crumb=(field, 'schema'),
schema=schema, allow_unknown=self.allow_unknown)
validator(dict(((i, v) for i, v in enumerate(value))),
update=self.update, normalize=False)
if validator._errors:
self._drop_nodes_from_errorpaths(validator._errors, [], [2])
self._error(field, errors.SEQUENCE_SCHEMA, validator._errors)
def _validate_type(self, data_type, field, value):
""" {'type': ['string', 'list']} """
types = [data_type] if isinstance(data_type, _str_type) else data_type
if any(self.__get_rule_handler('validate_type', x)(value)
for x in types):
return
else:
self._error(field, errors.BAD_TYPE)
return True
def _validate_type_boolean(self, value):
if isinstance(value, bool):
return True
def _validate_type_date(self, value):
if isinstance(value, date):
return True
def _validate_type_datetime(self, value):
if isinstance(value, datetime):
return True
def _validate_type_dict(self, value):
if isinstance(value, Mapping):
return True
def _validate_type_float(self, value):
if isinstance(value, (float, _int_types)):
return True
def _validate_type_integer(self, value):
if isinstance(value, _int_types):
return True
def _validate_type_binary(self, value):
if isinstance(value, (bytes, bytearray)):
return True
def _validate_type_list(self, value):
if isinstance(value, Sequence) and not isinstance(
value, _str_type):
return True
def _validate_type_number(self, value):
if isinstance(value, (_int_types, float)) \
and not isinstance(value, bool):
return True
def _validate_type_set(self, value):
if isinstance(value, set):
return True
def _validate_type_string(self, value):
if isinstance(value, _str_type):
return True
def _validate_validator(self, validator, field, value):
""" {'oneof': [
{'type': 'callable'},
{'type': 'list',
'schema': {'oneof': [{'type': 'callable'},
{'type': 'string'}]}},
{'type': 'string'}
]} """
if isinstance(validator, _str_type):
validator = self.__get_rule_handler('validator', validator)
validator(field, value)
elif isinstance(validator, Iterable):
for v in validator:
self._validate_validator(v, field, value)
else:
validator(field, value, self._error)
def _validate_valueschema(self, schema, field, value):
""" {'type': ['dict', 'string'], 'validator': 'bulk_schema',
'forbidden': ['rename', 'rename_handler']} """
schema_crumb = (field, 'valueschema')
if isinstance(value, Mapping):
validator = self._get_child_validator(
document_crumb=field, schema_crumb=schema_crumb,
schema=dict((k, schema) for k in value))
validator(value, update=self.update, normalize=False)
if validator._errors:
self._drop_nodes_from_errorpaths(validator._errors, [], [2])
self._error(field, errors.VALUESCHEMA, validator._errors)
RULE_SCHEMA_SEPARATOR = \
"The rule's arguments are validated against this schema:"
class InspectedValidator(type):
""" Metaclass for all validators """
def __new__(cls, *args):
if '__doc__' not in args[2]:
args[2].update({'__doc__': args[1][0].__doc__})
return super(InspectedValidator, cls).__new__(cls, *args)
def __init__(cls, *args):
def attributes_with_prefix(prefix):
return tuple(x.split('_', 2)[-1] for x in dir(cls)
if x.startswith('_' + prefix))
super(InspectedValidator, cls).__init__(*args)
cls.types, cls.validation_rules = (), {}
for attribute in attributes_with_prefix('validate'):
if attribute.startswith('type_'):
cls.types += (attribute[len('type_'):],)
else:
cls.validation_rules[attribute] = \
cls.__get_rule_schema('_validate_' + attribute)
cls.validation_rules['type']['allowed'] = cls.types
cls.validators = tuple(x for x in attributes_with_prefix('validator'))
x = cls.validation_rules['validator']['oneof']
x[1]['schema']['oneof'][1]['allowed'] = x[2]['allowed'] = cls.validators
for rule in (x for x in cls.mandatory_validations if x != 'nullable'):
cls.validation_rules[rule]['required'] = True
cls.coercers, cls.default_setters, cls.normalization_rules = (), (), {}
for attribute in attributes_with_prefix('normalize'):
if attribute.startswith('coerce_'):
cls.coercers += (attribute[len('coerce_'):],)
elif attribute.startswith('default_setter_'):
cls.default_setters += (attribute[len('default_setter_'):],)
else:
cls.normalization_rules[attribute] = \
cls.__get_rule_schema('_normalize_' + attribute)
for rule in ('coerce', 'rename_handler'):
x = cls.normalization_rules[rule]['oneof']
x[1]['schema']['oneof'][1]['allowed'] = \
x[2]['allowed'] = cls.coercers
cls.normalization_rules['default_setter']['oneof'][1]['allowed'] = \
cls.default_setters
cls.rules = {}
cls.rules.update(cls.validation_rules)
cls.rules.update(cls.normalization_rules)
def __get_rule_schema(cls, method_name):
docstring = getattr(cls, method_name).__doc__
if docstring is None:
result = {}
else:
if RULE_SCHEMA_SEPARATOR in docstring:
docstring = docstring.split(RULE_SCHEMA_SEPARATOR)[1]
try:
result = literal_eval(docstring.strip())
except Exception:
result = {}
if not result:
warn("No validation schema is defined for the arguments of rule "
"'%s'" % method_name.split('_', 2)[-1])
return result
Validator = InspectedValidator('Validator', (Validator,), {})