Skip to content

Commit

Permalink
Add support for reporting field errors from whole-schema validator
Browse files Browse the repository at this point in the history
  • Loading branch information
maximkulkin committed May 18, 2016
1 parent f2b3659 commit 8018037
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 28 deletions.
37 changes: 16 additions & 21 deletions marshmallow/marshalling.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from __future__ import unicode_literals

from marshmallow.utils import SCHEMA, is_collection, missing
from marshmallow.utils import SCHEMA, is_collection, merge_errors, missing
from marshmallow.compat import iteritems
from marshmallow.exceptions import (
ValidationError,
Expand Down Expand Up @@ -182,29 +182,24 @@ def run_validator(self, validator_func, output,
except ValidationError as err:
errors = self.get_errors(index=index)
self.error_kwargs.update(err.kwargs)

# Store or reraise errors
if err.field_names:
field_names = err.field_names
field_objs = [fields_dict[each] if each in fields_dict else None
for each in field_names]
all_errors = {}
for field_name in err.field_names:
all_errors[field_name] = err.messages

self.error_field_names = err.field_names
self.error_fields = \
[fields_dict[each] if each in fields_dict else None
for each in err.field_names]
else:
field_names = [SCHEMA]
field_objs = []
self.error_field_names = field_names
self.error_fields = field_objs
for field_name in field_names:
if isinstance(err.messages, (list, tuple)):
# self.errors[field_name] may be a dict if schemas are nested
if isinstance(errors.get(field_name), dict):
errors[field_name].setdefault(
SCHEMA, []
).extend(err.messages)
else:
errors.setdefault(field_name, []).extend(err.messages)
elif isinstance(err.messages, dict):
errors.setdefault(field_name, []).append(err.messages)
else:
errors.setdefault(field_name, []).append(text_type(err))
all_errors = err.messages

self.error_field_names = [SCHEMA]
self.error_fields = []

errors.update(merge_errors(errors, all_errors))

def deserialize(self, data, fields_dict, many=False, partial=False,
dict_class=dict, index_errors=True, index=None):
Expand Down
9 changes: 5 additions & 4 deletions marshmallow/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from marshmallow.orderedset import OrderedSet
from marshmallow.decorators import (PRE_DUMP, POST_DUMP, PRE_LOAD, POST_LOAD,
VALIDATES, VALIDATES_SCHEMA)
from marshmallow.utils import merge_errors


#: Return type of :meth:`Schema.dump` including serialized data and errors
Expand Down Expand Up @@ -621,12 +622,12 @@ def _do_load(self, data, many=None, partial=None, postprocess=True):
self._invoke_validators(pass_many=True, data=result, original_data=data, many=many,
field_errors=field_errors)
except ValidationError as err:
errors.update(err.messages)
errors = merge_errors(errors, err.messages)
try:
self._invoke_validators(pass_many=False, data=result, original_data=data, many=many,
field_errors=field_errors)
except ValidationError as err:
errors.update(err.messages)
errors = merge_errors(errors, err.messages)
if errors:
# TODO: Remove self.__error_handler__ in a later release
if self.__error_handler__ and callable(self.__error_handler__):
Expand Down Expand Up @@ -827,14 +828,14 @@ def _invoke_validators(self, pass_many, data, original_data, many, field_errors=
item, original_data, self.fields, many=many,
index=idx, pass_original=pass_original)
except ValidationError as err:
errors.update(err.messages)
errors = merge_errors(errors, err.messages)
else:
try:
self._unmarshal.run_validator(validator,
data, original_data, self.fields, many=many,
pass_original=pass_original)
except ValidationError as err:
errors.update(err.messages)
errors = merge_errors(errors, err.messages)
if errors:
raise ValidationError(errors)
return None
Expand Down
36 changes: 33 additions & 3 deletions tests/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ def check_unknown_fields(self, data, original_data, many):
def check(datum):
for key, val in datum.items():
if key not in self.fields:
raise ValidationError({'code': 'invalid_field'})
raise ValidationError('invalid_field')
if many:
for each in original_data:
check(each)
Expand All @@ -446,7 +446,7 @@ def check(datum):
errors = schema.validate({'foo': 4, 'baz': 42})
assert '_schema' in errors
assert len(errors['_schema']) == 1
assert errors['_schema'][0] == {'code': 'invalid_field'}
assert errors['_schema'][0] == 'invalid_field'

errors = schema.validate({'foo': '4'})
assert '_schema' in errors
Expand All @@ -457,7 +457,7 @@ def check(datum):
errors = schema.validate([{'foo': 4, 'baz': 42}], many=True)
assert '_schema' in errors
assert len(errors['_schema']) == 1
assert errors['_schema'][0] == {'code': 'invalid_field'}
assert errors['_schema'][0] == 'invalid_field'

# https://github.com/marshmallow-code/marshmallow/issues/273
def test_allow_arbitrary_field_names_in_error(self):
Expand Down Expand Up @@ -518,3 +518,33 @@ def validate_many(self, data, many):
errors = schema.validate([{'foo': 3, 'bar': 'not an int'}], many=True)
assert 'bar' in errors[0]
assert '_schema' not in errors

def test_allow_reporting_field_errors_in_schema_validator(self):

class NestedSchema(Schema):
baz = fields.Int(required=True)

class MySchema(Schema):
foo = fields.Int(required=True)
bar = fields.Nested(NestedSchema, required=True)
bam = fields.Int(required=True)

@validates_schema(skip_on_field_errors=True)
def consistency_validation(self, data):
errors = {}
if data['bar']['baz'] != data['foo']:
errors['bar'] = {'baz': 'Non-matching value'}

if data['bam'] > data['foo']:
errors['bam'] = 'Value should be less than foo'

if errors:
raise ValidationError(errors)

schema = MySchema()
errors = schema.validate({'foo': 2, 'bar': {'baz': 5}, 'bam': 6})
assert 'bar' in errors
assert 'baz' in errors['bar']
assert errors['bar']['baz'] == 'Non-matching value'
assert 'bam' in errors
assert errors['bam'] == 'Value should be less than foo'

0 comments on commit 8018037

Please sign in to comment.