@@ -3,7 +3,7 @@ FROM funkyfuture/nest-of-serpents
ENTRYPOINT tox
WORKDIR /src

RUN pip3.6 install flake8 pytest tox PyYAML Sphinx \
RUN pip3.6 install flake8 pytest tox PyYAML Sphinx==1.5.6 \
&& mkdir /home/tox \
&& mv /root/.cache /home/tox/

@@ -159,6 +159,8 @@ def has_mapping_schema():

for rule in ('allof', 'anyof', 'items', 'noneof', 'oneof'):
if rule in schema[field]:
if not isinstance(schema[field][rule], Sequence):
continue
new_rules_definition = []
for item in schema[field][rule]:
new_rules_definition.append(cls.expand({0: item})[0])
@@ -240,6 +242,7 @@ class SchemaValidatorMixin(object):
validator. """
@property
def known_rules_set_refs(self):
""" The encountered references to rules set registry items. """
return self._config.get('known_rules_set_refs', ())

@known_rules_set_refs.setter
@@ -248,6 +251,7 @@ def known_rules_set_refs(self, value):

@property
def known_schema_refs(self):
""" The encountered references to schema registry items. """
return self._config.get('known_schema_refs', ())

@known_schema_refs.setter
@@ -266,11 +270,13 @@ def target_validator(self):

def _validate_logical(self, rule, field, value):
""" {'allowed': ('allof', 'anyof', 'noneof', 'oneof')} """
if not isinstance(value, Sequence):
self._error(field, errors.BAD_TYPE)
return

validator = self._get_child_validator(
document_crumb=rule,
schema=self.root_allow_unknown['schema'],
allow_unknown=self.root_allow_unknown['allow_unknown']
)
document_crumb=rule, allow_unknown=False,
schema=self.target_validator.validation_rules)

for constraints in value:
_hash = (mapping_hash({'turing': constraints}),
@@ -285,15 +291,15 @@ def _validate_logical(self, rule, field, value):
self.target_validator._valid_schemas.add(_hash)

def _validator_bulk_schema(self, field, value):
# resolve schema registry reference
if isinstance(value, _str_type):
if value in self.known_rules_set_refs:
return
else:
self.known_rules_set_refs += (value,)
definition = self.target_validator.rules_set_registry.get(value)
if definition is None:
path = self.document_path + (field,)
self._error(path, 'Rules set definition %s not found.' % value)
self._error(field, 'Rules set definition %s not found.' % value)
return
else:
value = definition
@@ -304,9 +310,8 @@ def _validator_bulk_schema(self, field, value):
return

validator = self._get_child_validator(
document_crumb=field,
schema=self.root_allow_unknown['schema'],
allow_unknown=self.root_allow_unknown['allow_unknown'])
document_crumb=field, allow_unknown=False,
schema=self.target_validator.rules)
validator(value, normalize=False)
if validator._errors:
self._error(validator._errors)
@@ -11,7 +11,7 @@ def assert_exception(exception, document={}, schema=None, validator=None,
""" Tests whether a specific exception is raised. Optionally also tests
whether the exception message is as expected. """
if validator is None:
validator = Validator(sample_schema)
validator = Validator()
if msg is None:
with pytest.raises(exception) as excinfo:
validator(document, schema)
@@ -6,6 +6,8 @@

from cerberus import TypeDefinition, Validator
from cerberus.tests import assert_fail, assert_success
from cerberus.utils import validator_factory
from cerberus.validator import BareValidator


def test_clear_cache(validator):
@@ -46,3 +48,29 @@ class MyValidator(Validator):
types_mapping['decimal'] = decimal_type
validator = MyValidator()
assert_success(document, schema, validator)


def test_mro():
assert Validator.__mro__ == (Validator, BareValidator, object), \
Validator.__mro__


def test_mixin_init():
class Mixin(object):
def __init__(self, *args, **kwargs):
kwargs['test'] = True
super(Mixin, self).__init__(*args, **kwargs)

MyValidator = validator_factory('MyValidator', Mixin)
validator = MyValidator()
assert validator._config['test']


def test_sub_init():
class MyValidator(Validator):
def __init__(self, *args, **kwargs):
kwargs['test'] = True
super(MyValidator, self).__init__(*args, **kwargs)

validator = MyValidator()
assert validator._config['test']
@@ -14,14 +14,30 @@ def test_coerce():
assert_normalized(document, expected, schema)


def test_coerce_in_subschema():
def test_coerce_in_dictschema():
schema = {'thing': {'type': 'dict',
'schema': {'amount': {'coerce': int}}}}
document = {'thing': {'amount': '2'}}
expected = {'thing': {'amount': 2}}
assert_normalized(document, expected, schema)


def test_coerce_in_listschema():
schema = {'things': {'type': 'list',
'schema': {'coerce': int}}}
document = {'things': ['1', '2', '3']}
expected = {'things': [1, 2, 3]}
assert_normalized(document, expected, schema)


def test_coerce_in_dictschema_in_listschema():
item_schema = {'type': 'dict', 'schema': {'amount': {'coerce': int}}}
schema = {'things': {'type': 'list', 'schema': item_schema}}
document = {'things': [{'amount': '2'}]}
expected = {'things': [{'amount': 2}]}
assert_normalized(document, expected, schema)


def test_coerce_not_destructive():
schema = {
'amount': {'coerce': int}
@@ -48,11 +48,16 @@ def test_bad_schema_definition(validator):
validator.schema = {field: 'this should really be a dict'}


def bad_of_rules():
def test_bad_of_rules():
schema = {'foo': {'anyof': {'type': 'string'}}}
assert_schema_error({}, schema)


def test_normalization_rules_are_invalid_in_of_rules():
schema = {0: {'anyof': [{'coerce': lambda x: x}]}}
assert_schema_error({}, schema)


def test_anyof_allof_schema_validate():
# make sure schema with 'anyof' and 'allof' constraints are checked
# correctly
@@ -83,7 +88,7 @@ def test_validated_schema_cache():
v = Validator({'foozifix': {'coerce': int}})
assert len(v._valid_schemas) == cache_size

max_cache_size = 140
max_cache_size = 143
assert cache_size <= max_cache_size, \
"There's an unexpected high amount (%s) of cached valid " \
"definition schemas. Unless you added further tests, " \
@@ -12,18 +12,19 @@
from cerberus.tests import \
(assert_bad_type, assert_fail, assert_has_error, assert_not_has_error,
assert_success, assert_document_error)
from cerberus.tests.conftest import sample_schema


def test_empty_document():
assert_document_error(None, None, None,
assert_document_error(None, sample_schema, None,
errors.DOCUMENT_MISSING)


def test_bad_document_type():
document = "not a dict"
assert_document_error(
document, None, None, errors.DOCUMENT_FORMAT.format(
document)
document, sample_schema, None,
errors.DOCUMENT_FORMAT.format(document)
)


@@ -90,30 +90,30 @@ def __delete__(self, instance):
raise RuntimeError('This is a readonly class property.')


def validator_factory(name, mixin=None, class_dict={}):
def validator_factory(name, bases=None, namespace={}):
""" Dynamically create a :class:`~cerberus.Validator` subclass.
Docstrings of mixin-classes will be added to the resulting
class' one if ``__doc__`` is not in :obj:`class_dict`.
class' one if ``__doc__`` is not in :obj:`namespace`.
:param name: The name of the new class.
:type name: :class:`str`
:param mixin: Class(es) with mixin-methods.
:type mixin: :class:`tuple` of or a single :term:`class`
:param class_dict: Attributes for the new class.
:type class_dict: :class:`dict`
:param bases: Class(es) with additional and overriding attributes.
:type bases: :class:`tuple` of or a single :term:`class`
:param namespace: Attributes for the new class.
:type namespace: :class:`dict`
:return: The created class.
"""
Validator = get_Validator_class()

if mixin is None:
if bases is None:
bases = (Validator,)
elif isinstance(mixin, tuple):
bases = (Validator,) + mixin
elif isinstance(bases, tuple):
bases += (Validator,)
else:
bases = (Validator, mixin)
bases = (bases, Validator)

docstrings = [x.__doc__ for x in bases if x.__doc__]
if len(docstrings) > 1 and '__doc__' not in class_dict:
class_dict.update({'__doc__': '\n'.join(docstrings)})
if len(docstrings) > 1 and '__doc__' not in namespace:
namespace.update({'__doc__': '\n'.join(docstrings)})

return type(name, bases, class_dict)
return type(name, bases, namespace)
@@ -49,7 +49,7 @@ class _SchemaRuleTypeError(Exception):
pass


class Validator(object):
class BareValidator(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`,
@@ -173,7 +173,10 @@ def __init__(self, *args, **kwargs):
during the validation of a field.
Type: :class:`list` """

def __init_error_handler(self, kwargs):
super(BareValidator, self).__init__()

@staticmethod
def __init_error_handler(kwargs):
error_handler = kwargs.pop('error_handler', errors.BasicErrorHandler)
if isinstance(error_handler, tuple):
error_handler, eh_config = error_handler
@@ -1266,19 +1269,19 @@ def _validate_type(self, data_type, field, value):
type_definition = self.types_mapping.get(_type)
if type_definition is not None:
matched = isinstance(value, type_definition.included_types) \
and not isinstance(value, type_definition.excluded_types)
and not isinstance(value, type_definition.excluded_types)
else:
type_handler = self.__get_rule_handler('validate_type', _type)
matched = type_handler(value)
if matched:
return

# TODO uncomment this block on next major release
# if _validate_type_* methods were deprecated:
# type_definition = self.types_mapping[_type]
# if isinstance(value, type_definition.included_types) \
# and not isinstance(value, type_definition.excluded_types):
# return
# TODO uncomment this block on next major release
# when _validate_type_* methods were deprecated:
# type_definition = self.types_mapping[_type]
# if isinstance(value, type_definition.included_types) \
# and not isinstance(value, type_definition.excluded_types): # noqa 501
# return

self._error(field, errors.BAD_TYPE)
self._drop_remaining_rules()
@@ -1394,4 +1397,4 @@ def __get_rule_schema(cls, method_name):
return result


Validator = InspectedValidator('Validator', (Validator,), {})
Validator = InspectedValidator('Validator', (BareValidator,), {})
@@ -351,7 +351,8 @@ Minimum and maximum length allowed for iterables.
noneof
------

Validates if *none* of the provided constraints validates the field. See `\*of-rules`_ for details.
Validates if *none* of the provided constraints validates the field. See
`\*of-rules`_ for details.

.. versionadded:: 0.9

@@ -384,8 +385,8 @@ defaults ``False``.
\*of-rules
----------

These rules allow you to list multiple sets of rules to validate against. The
field will be considered valid if it validates against the set in the list
These rules allow you to define different sets of rules to validate against.
The field will be considered valid if it validates against the set in the list
according to the prefixes logics ``all``, ``any``, ``one`` or ``none``.

========== ====================================================================
@@ -395,8 +396,8 @@ according to the prefixes logics ``all``, ``any``, ``one`` or ``none``.
``oneof`` Validates if *exactly one* of the provided constraints applies.
========== ====================================================================

For example, to verify that a property is a number between 0 and 10 or 100 and
110, you could do the following:
For example, to verify that a field's value is a number between 0 and 10 or 100
and 110, you could do the following:

.. doctest::

@@ -419,8 +420,8 @@ For example, to verify that a property is a number between 0 and 10 or 100 and
>>> v.errors # doctest: +SKIP
{'prop1': {'anyof': 'no definitions validated', 'definition 1': 'min value is 100', 'definition 0': 'max value is 10'}}
The ``anyof`` rule works by creating a new instance of a schema for each item
in the list. The above schema is equivalent to creating two separate schemas:
The ``anyof`` rule tests each rules set in the list. Hence, the above schema is
equivalent to creating two separate schemas:

.. doctest::

@@ -439,6 +440,11 @@ in the list. The above schema is equivalent to creating two separate schemas:
>>> v.validate(document, schema1) or v.validate(document, schema2)
False
.. attention::

:doc:`Normalization rules <normalization-rules>` cannot be defined within
these rule sets.

.. versionadded:: 0.9

\*of-rules typesaver
@@ -1,3 +1,3 @@
# requirements to build documentation
Sphinx
Sphinx~=1.5.6
sphinxcontrib-issuetracker
@@ -10,16 +10,14 @@ deps=flake8
commands=flake8 cerberus

[testenv:doclinks]
deps=Sphinx
sphinxcontrib-issuetracker
deps=-rrequirements-docs.txt
whitelist_externals=make
changedir=docs
commands=make linkcheck

[testenv:doctest]
deps=PyYAML
Sphinx
sphinxcontrib-issuetracker
-rrequirements-docs.txt
whitelist_externals=make
changedir=docs
commands=make doctest