Skip to content

Commit

Permalink
Renames 'validator' rule to 'check_with'
Browse files Browse the repository at this point in the history
Closes #405
  • Loading branch information
funkyfuture authored and nicolaiarocci committed Jul 9, 2018
1 parent 2fda0ed commit 559ac17
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 142 deletions.
10 changes: 6 additions & 4 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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`` (`#405`_)
- **Python 2.6 and 3.3 are no longer supported**

Fixed
Expand All @@ -27,7 +28,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,16 +40,17 @@ 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:/pyeve/cerberus/issues/420
.. _`#406`: https:/pyeve/cerberus/issues/406
.. _`#405`: https:/pyeve/cerberus/issues/405
.. _`#404`: https:/pyeve/cerberus/issues/404
.. _`#402`: https:/pyeve/cerberus/issues/402
.. _`#389`: https:/pyeve/cerberus/issues/389
Expand Down
126 changes: 74 additions & 52 deletions cerberus/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from collections import Callable, Hashable, Iterable, 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 @@ -190,6 +195,23 @@ 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 (('validator', 'check_with'),):
for field, rules in schema.items():
if old in 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 +269,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 +300,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,7 +332,7 @@ 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):
Expand All @@ -352,24 +348,24 @@ 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):
def _check_with_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
not in self.target_validator.checkers + 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)
self._check_with_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 +384,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 +417,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
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:/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
Loading

0 comments on commit 559ac17

Please sign in to comment.