diff --git a/CHANGES.rst b/CHANGES.rst index f814e5f2..03810d55 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,8 +6,10 @@ Here you can see the full list of changes between each Cerberus release. In Development -------------- -- Update README link. Make it point to the new PYPI website (Frank - Sachsenheim). +- New: The ``contains`` rule. (Frank Sachsenheim) + +- Update README link. Make it point to the new PyPI website. (Frank + Sachsenheim) Version 1.2 ----------- @@ -23,8 +25,8 @@ Released on April 12, 2018. (Frank Sachsenheim) - New: Add errors.MAPPING_SCHEMA on errors within subdocuments. (Frank Sachsenheim) -- New: Support for Types Definitions, which allow quick types check on the fly - (Frank Sachsenheim). +- New: Support for Types Definitions, which allow quick types check on the fly. + (Frank Sachsenheim) - Fix: Simplify the tests with Docker by using a volume for tox environments. (Frank Sachsenheim) @@ -43,7 +45,7 @@ Released on April 12, 2018. Closes :issue:`269`. (Frank Sachsenheim) - Fix: A dependency is not considered satisfied if it has a null value. Closes :issue:`305`. (Frank Sachsenheim) -- Override ``UnvalidatedSchema.copy`` (Peter Demin). +- Override ``UnvalidatedSchema.copy``. (Peter Demin) - Fix: README link. (Gabriel Wainer) - Fix: Regression: allow_unknown causes dictionary validation to fail with a KeyError. Closes :issue:`302`. (Frank Sachsenheim) diff --git a/cerberus/errors.py b/cerberus/errors.py index 4c497eeb..e7d66ea0 100644 --- a/cerberus/errors.py +++ b/cerberus/errors.py @@ -54,6 +54,7 @@ UNALLOWED_VALUES = ErrorDefinition(0x45, 'allowed') FORBIDDEN_VALUE = ErrorDefinition(0x46, 'forbidden') FORBIDDEN_VALUES = ErrorDefinition(0x47, 'forbidden') +MISSING_MEMBERS = ErrorDefinition(0x48, 'contains') # other NORMALIZATION = ErrorDefinition(0x60, None) @@ -461,6 +462,7 @@ class BasicErrorHandler(BaseErrorHandler): 0x45: "unallowed values {0}", 0x46: "unallowed value {value}", 0x47: "unallowed values {0}", + 0x48: "missing members {0}", 0x61: "field '{field}' cannot be coerced: {0}", 0x62: "field '{field}' cannot be renamed: {0}", diff --git a/cerberus/tests/test_validation.py b/cerberus/tests/test_validation.py index 1f828fac..e09b3c4f 100644 --- a/cerberus/tests/test_validation.py +++ b/cerberus/tests/test_validation.py @@ -1577,3 +1577,21 @@ def test_allow_unknown_with_oneof_rules(validator): # check that allow_unknown is actually applied document = {'test': {'known': 's', 'unknown': 'asd'}} assert_success(document, validator=validator) + + +@mark.parametrize('constraint', + (('Graham Chapman', 'Eric Idle'), 'Terry Gilliam')) +def test_contains(constraint): + validator = Validator({'actors': {'contains': constraint}}) + + document = {'actors': ('Graham Chapman', 'Eric Idle', 'Terry Gilliam')} + assert validator(document) + + document = { + 'actors': ('Eric idle', 'Terry Jones', 'John Cleese', 'Michael Palin') + } + assert not validator(document) + assert errors.MISSING_MEMBERS in validator.document_error_tree['actors'] + missing_actors = \ + validator.document_error_tree['actors'][errors.MISSING_MEMBERS].info[0] + assert any(x in missing_actors for x in ('Eric Idle', 'Terry Gilliam')) diff --git a/cerberus/validator.py b/cerberus/validator.py index 27a29053..39a0d8fc 100644 --- a/cerberus/validator.py +++ b/cerberus/validator.py @@ -964,6 +964,22 @@ def _validate_allowed(self, allowed_values, field, value): if value not in allowed_values: self._error(field, errors.UNALLOWED_VALUE, value) + def _validate_contains(self, expected_values, field, value): + # just to omit warnings about missing validation schema: + """ {'empty': True } """ + if not isinstance(value, Iterable): + return + + if (not isinstance(expected_values, Iterable) or + isinstance(expected_values, _str_type)): + expected_values = set((expected_values,)) + else: + expected_values = set(expected_values) + + missing_values = expected_values - set(value) + if missing_values: + self._error(field, errors.MISSING_MEMBERS, missing_values) + def _validate_dependencies(self, dependencies, field, value): """ {'type': ('dict', 'hashable', 'list'), 'validator': 'dependencies'} """ diff --git a/docs/validation-rules.rst b/docs/validation-rules.rst index e45bf3e6..67c7a5a6 100644 --- a/docs/validation-rules.rst +++ b/docs/validation-rules.rst @@ -61,6 +61,31 @@ Validates if *any* of the provided constraints validates the field. See `\*of-ru .. versionadded:: 0.9 +contains +-------- +This rule validates the a container object contains all of the defined items. + +.. doctest:: + + >>> document = {'states': ['peace', 'love', 'unity']} + + >>> schema = {'states': {'contains': 'peace'}} + >>> v.validate(document, schema) + True + + >>> schema = {'states': {'contains': 'greed'}} + >>> v.validate(document, schema) + False + + >>> schema = {'states': {'contains': ['love', 'unity']}} + >>> v.validate(document, schema) + True + + >>> schema = {'states': {'contains': ['love', 'laugh']}} + >>> v.validate(document, schema) + False + + .. _dependencies: dependencies