Skip to content

Commit

Permalink
Merge branch 'rename_three_rules_#422'
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolaiarocci committed Jul 9, 2018
2 parents 2fda0ed + a0bca9b commit 38614ce
Show file tree
Hide file tree
Showing 15 changed files with 336 additions and 251 deletions.
16 changes: 12 additions & 4 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ New
- The ``contains`` rule (`#358`_)
- All fields that are defined as ``readonly`` are removed from a document
when a validator has the ``purge_readonly`` flag set to ``True`` (`#240`_)
- The ``validator`` rule is renamed to ``check_with``. The old name is
deprecated and will not be available in the next major release of Cerberus
(`#405`_)
- The rules ``keyschema`` and ``valueschema`` are renamed to ``keysrules`` and
``valuesrules``. The old names are deprecated and will not be available in
the next major release of Cerbers (`385`_)
- **Python 2.6 and 3.3 are no longer supported**

Fixed
Expand All @@ -27,7 +33,7 @@ Improved
- Change ``allowed`` rule to use containers instead of lists (`#384`_)
- Remove ``Registry`` from top level namespace (`#354`_)
- Remove ``utils.is_class``
- Check the ``empty`` rule against values of type ``Sized``.
- Check the ``empty`` rule against values of type ``Sized``

Docs
~~~~
Expand All @@ -39,19 +45,21 @@ Docs
- Add feature freeze note to CONTRIBUTING and note on Python support in
README
- Add the intent of a ``dataclasses`` module to ROADMAP.md
- Update README link. Make it point to the new PYPI website
- Update README link. Make it point to the new PyPI website
- Update README with elaborations on versioning and testing
- Fix misspellings and missing pronouns
- Remove redundant hint from ``*of-rules``.
- Add usage reccommendation regarding the ``*ok-rules``
- Add usage recommendation regarding the ``*of-rules``
- Add a few clarifications to the GitHub issue template
- Update README link. Make it point to the new PYPI website
- Update README link. Make it point to the new PyPI website

.. _`#420`: https://github.com/pyeve/cerberus/issues/420
.. _`#406`: https://github.com/pyeve/cerberus/issues/406
.. _`#405`: https://github.com/pyeve/cerberus/issues/405
.. _`#404`: https://github.com/pyeve/cerberus/issues/404
.. _`#402`: https://github.com/pyeve/cerberus/issues/402
.. _`#389`: https://github.com/pyeve/cerberus/issues/389
.. _`#385`: https://github.com/pyeve/cerberus/issues/385
.. _`#384`: https://github.com/pyeve/cerberus/issues/384
.. _`#382`: https://github.com/pyeve/cerberus/issues/382
.. _`#361`: https://github.com/pyeve/cerberus/pull/361
Expand Down
5 changes: 3 additions & 2 deletions cerberus/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@
ERROR_GROUP = ErrorDefinition(0x80, None)
MAPPING_SCHEMA = ErrorDefinition(0x81, 'schema')
SEQUENCE_SCHEMA = ErrorDefinition(0x82, 'schema')
KEYSCHEMA = ErrorDefinition(0x83, 'keyschema')
VALUESCHEMA = ErrorDefinition(0x84, 'valueschema')
# TODO remove KEYSCHEMA AND VALUESCHEMA with next major release
KEYSRULES = KEYSCHEMA = ErrorDefinition(0x83, 'keysrules')
VALUESRULES = VALUESCHEMA = ErrorDefinition(0x84, 'valuesrules')
BAD_ITEMS = ErrorDefinition(0x8f, 'items')

LOGICAL = ErrorDefinition(0x90, None)
Expand Down
151 changes: 86 additions & 65 deletions cerberus/schema.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from __future__ import absolute_import

from collections import Callable, Hashable, Iterable, Mapping, MutableMapping, Sequence
from collections import Callable, Hashable, Mapping, MutableMapping, Sequence
from copy import copy
from warnings import warn

from cerberus import errors
from cerberus.platform import _str_type
Expand Down Expand Up @@ -116,6 +117,10 @@ def expand(cls, schema):
schema = cls._expand_subschemas(schema)
except Exception:
pass

# TODO remove this with the next major release
schema = cls._rename_deprecated_rulenames(schema)

return schema

@classmethod
Expand Down Expand Up @@ -163,7 +168,7 @@ def has_mapping_schema():
else: # assumes schema-constraints for a sequence
schema[field]['schema'] = cls.expand({0: schema[field]['schema']})[0]

for rule in ('keyschema', 'valueschema'):
for rule in ('keysrules', 'valuesrules'):
if rule in schema[field]:
schema[field][rule] = cls.expand({0: schema[field][rule]})[0]

Expand All @@ -190,6 +195,35 @@ def update(self, schema):
else:
self.schema = _new_schema

# TODO remove with next major release
@staticmethod
def _rename_deprecated_rulenames(schema):
for old, new in (
('keyschema', 'keysrules'),
('validator', 'check_with'),
('valueschema', 'valuesrules'),
):
for field, rules in schema.items():
if old not in rules:
continue

if new in rules:
raise RuntimeError(
"The rule '{new}' is also present with its old "
"name '{old}' in the same set of rules."
)

warn(
"The rule '{old}' was renamed to '{new}'. The old name will "
"not be available in the next major release of "
"Cerberus".format(old=old, new=new),
DeprecationWarning,
)
schema[field][new] = schema[field][old]
schema[field].pop(old)

return schema

def regenerate_validation_schema(self):
self.validation_schema = SchemaValidationSchema(self.validator)

Expand Down Expand Up @@ -247,7 +281,7 @@ def __init__(self, validator):


class SchemaValidatorMixin(object):
""" This validator is extended to validate schemas passed to a Cerberus
""" This validator mixin provides mechanics to validate schemas passed to a Cerberus
validator. """

@property
Expand Down Expand Up @@ -278,33 +312,7 @@ def target_validator(self):
""" The validator whose schema is being validated. """
return self._config['target_validator']

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,
allow_unknown=False,
schema=self.target_validator.validation_rules,
)

for constraints in value:
_hash = (
mapping_hash({'turing': constraints}),
mapping_hash(self.target_validator.types_mapping),
)
if _hash in self.target_validator._valid_schemas:
continue

validator(constraints, normalize=False)
if validator._errors:
self._error(validator._errors)
else:
self.target_validator._valid_schemas.add(_hash)

def _validator_bulk_schema(self, field, value):
def _check_with_bulk_schema(self, field, value):
# resolve schema registry reference
if isinstance(value, _str_type):
if value in self.known_rules_set_refs:
Expand Down Expand Up @@ -336,13 +344,13 @@ def _validator_bulk_schema(self, field, value):
else:
self.target_validator._valid_schemas.add(_hash)

def _validator_dependencies(self, field, value):
def _check_with_dependencies(self, field, value):
if isinstance(value, _str_type):
pass
elif isinstance(value, Mapping):
validator = self._get_child_validator(
document_crumb=field,
schema={'valueschema': {'type': 'list'}},
schema={'valuesrules': {'type': 'list'}},
allow_unknown=True,
)
if not validator(value, normalize=False):
Expand All @@ -352,24 +360,11 @@ def _validator_dependencies(self, field, value):
path = self.document_path + (field,)
self._error(path, 'All dependencies must be a hashable type.')

def _validator_handler(self, field, value):
if isinstance(value, Callable):
return
if isinstance(value, _str_type):
if (
value
not in self.target_validator.validators + self.target_validator.coercers
):
self._error(field, '%s is no valid coercer' % value)
elif isinstance(value, Iterable):
for handler in value:
self._validator_handler(field, handler)

def _validator_items(self, field, value):
def _check_with_items(self, field, value):
for i, schema in enumerate(value):
self._validator_bulk_schema((field, i), schema)
self._check_with_bulk_schema((field, i), schema)

def _validator_schema(self, field, value):
def _check_with_schema(self, field, value):
try:
value = self._handle_schema_reference_for_validator(field, value)
except _Abort:
Expand All @@ -388,6 +383,25 @@ def _validator_schema(self, field, value):
else:
self.target_validator._valid_schemas.add(_hash)

def _check_with_type(self, field, value):
value = (value,) if isinstance(value, _str_type) else value
invalid_constraints = ()
for constraint in value:
if constraint not in self.target_validator.types:
invalid_constraints += (constraint,)
if invalid_constraints:
path = self.document_path + (field,)
self._error(path, 'Unsupported types: %s' % invalid_constraints)

def _expand_rules_set_refs(self, schema):
result = {}
for k, v in schema.items():
if isinstance(v, _str_type):
result[k] = self.target_validator.rules_set_registry.get(v)
else:
result[k] = v
return result

def _handle_schema_reference_for_validator(self, field, value):
if not isinstance(value, _str_type):
return value
Expand All @@ -402,24 +416,31 @@ def _handle_schema_reference_for_validator(self, field, value):
raise _Abort
return definition

def _expand_rules_set_refs(self, schema):
result = {}
for k, v in schema.items():
if isinstance(v, _str_type):
result[k] = self.target_validator.rules_set_registry.get(v)
else:
result[k] = v
return result
def _validate_logical(self, rule, field, value):
""" {'allowed': ('allof', 'anyof', 'noneof', 'oneof')} """
if not isinstance(value, Sequence):
self._error(field, errors.BAD_TYPE)
return

def _validator_type(self, field, value):
value = (value,) if isinstance(value, _str_type) else value
invalid_constraints = ()
for constraint in value:
if constraint not in self.target_validator.types:
invalid_constraints += (constraint,)
if invalid_constraints:
path = self.document_path + (field,)
self._error(path, 'Unsupported types: %s' % invalid_constraints)
validator = self._get_child_validator(
document_crumb=rule,
allow_unknown=False,
schema=self.target_validator.validation_rules,
)

for constraints in value:
_hash = (
mapping_hash({'turing': constraints}),
mapping_hash(self.target_validator.types_mapping),
)
if _hash in self.target_validator._valid_schemas:
continue

validator(constraints, normalize=False)
if validator._errors:
self._error(validator._errors)
else:
self.target_validator._valid_schemas.add(_hash)


####
Expand Down
6 changes: 1 addition & 5 deletions cerberus/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,7 @@ def validator():
'city': {'type': 'string', 'required': True},
},
},
'a_dict_with_valueschema': {'type': 'dict', 'valueschema': {'type': 'integer'}},
'a_dict_with_keyschema': {
'type': 'dict',
'keyschema': {'type': 'string', 'regex': '[a-z]+'},
},
'a_dict_with_valuesrules': {'type': 'dict', 'valuesrules': {'type': 'integer'}},
'a_list_length': {
'type': 'list',
'schema': {'type': 'integer'},
Expand Down
26 changes: 24 additions & 2 deletions cerberus/tests/test_customization.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-

from pytest import mark

import cerberus
from cerberus.tests import assert_fail, assert_success
from cerberus.tests.conftest import sample_schema
Expand Down Expand Up @@ -41,13 +43,33 @@ def _validate_bar(self, value):
assert 'bar' in CustomValidator.validation_rules


def test_issue_265():
# TODO remove 'validator' as rule parameter with the next major release
@mark.parametrize('rule', ('check_with', 'validator'))
def test_check_with_method(rule):
# https://github.com/pyeve/cerberus/issues/265
class MyValidator(cerberus.Validator):
def _check_with_oddity(self, field, value):
if not value & 1:
self._error(field, "Must be an odd number")

v = MyValidator(schema={'amount': {rule: 'oddity'}})
assert_success(document={'amount': 1}, validator=v)
assert_fail(
document={'amount': 2},
validator=v,
error=('amount', (), cerberus.errors.CUSTOM, None, ('Must be an odd number',)),
)


# TODO remove test with the next major release
@mark.parametrize('rule', ('check_with', 'validator'))
def test_validator_method(rule):
class MyValidator(cerberus.Validator):
def _validator_oddity(self, field, value):
if not value & 1:
self._error(field, "Must be an odd number")

v = MyValidator(schema={'amount': {'validator': 'oddity'}})
v = MyValidator(schema={'amount': {rule: 'oddity'}})
assert_success(document={'amount': 1}, validator=v)
assert_fail(
document={'amount': 2},
Expand Down

0 comments on commit 38614ce

Please sign in to comment.