diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 70e97e60a..bfe5bf52f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,12 @@ Features: - Raise a `StringNotCollectionError` if ``only`` or ``exclude`` is passed as a string to ``fields.Nested`` (:pr:`931`). +Other changes: + +- *Backwards-incompatible*: ``Nested`` field now defaults to ``unknown=RAISE`` + instead of ``EXCLUDE``. This harmonizes behavior with ``Schema`` that + already defaults to ``RAISE`` (:issue:`908`). Thanks :user:`tuukkamustonen`. + 3.0.0b13 (2018-08-04) +++++++++++++++++++++ diff --git a/marshmallow/fields.py b/marshmallow/fields.py index 16e2f33da..b5a142f40 100755 --- a/marshmallow/fields.py +++ b/marshmallow/fields.py @@ -12,7 +12,7 @@ from marshmallow import validate, utils, class_registry from marshmallow.base import FieldABC, SchemaABC -from marshmallow.utils import EXCLUDE, is_collection +from marshmallow.utils import RAISE, is_collection from marshmallow.utils import missing as missing_ from marshmallow.compat import text_type, basestring from marshmallow.exceptions import ValidationError, StringNotCollectionError @@ -393,7 +393,7 @@ def __init__(self, nested, default=missing_, exclude=tuple(), only=None, **kwarg self.only = only self.exclude = exclude self.many = kwargs.get('many', False) - self.unknown = kwargs.get('unknown', EXCLUDE) + self.unknown = kwargs.get('unknown', RAISE) self.__schema = None # Cached Schema instance self.__updated_fields = False super(Nested, self).__init__(default=default, **kwargs) diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 59f2c360c..676f10434 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -471,7 +471,7 @@ def validate_schema(self, data, original_data, many=False): raise ValidationError('Method called') class MySchema(Schema): - nested = fields.Nested(NestedSchema, required=True, many=True) + nested = fields.Nested(NestedSchema, required=True, many=True, unknown=EXCLUDE) schema = MySchema() errors = schema.validate({'nested': data}) diff --git a/tests/test_deserialization.py b/tests/test_deserialization.py index 54f643b25..a0eb9c7f7 100644 --- a/tests/test_deserialization.py +++ b/tests/test_deserialization.py @@ -942,7 +942,7 @@ def test_exclude(self): def test_nested_single_deserialization_to_dict(self): class SimpleBlogSerializer(Schema): title = fields.String() - author = fields.Nested(SimpleUserSchema) + author = fields.Nested(SimpleUserSchema, unknown=EXCLUDE) blog_dict = { 'title': 'Gimme Shelter', diff --git a/tests/test_schema.py b/tests/test_schema.py index f3fd9eb58..a831ebb68 100755 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -10,7 +10,8 @@ import pytest -from marshmallow import Schema, fields, utils, validates, validates_schema, EXCLUDE +from marshmallow import Schema, fields, utils, validates, validates_schema, \ + EXCLUDE, INCLUDE, RAISE from marshmallow.exceptions import ValidationError, StringNotCollectionError from tests.base import ( @@ -1132,13 +1133,13 @@ class ChildSchema(Schema): str_dump_only = fields.String() str_load_only = fields.String() str_regular = fields.String() - grand_child = fields.Nested(GrandChildSchema) + grand_child = fields.Nested(GrandChildSchema, unknown=EXCLUDE) class ParentSchema(Schema): str_dump_only = fields.String() str_load_only = fields.String() str_regular = fields.String() - child = fields.Nested(ChildSchema) + child = fields.Nested(ChildSchema, unknown=EXCLUDE) return ParentSchema( dump_only=('str_dump_only', 'child.str_dump_only', 'child.grand_child.str_dump_only'), @@ -1205,7 +1206,7 @@ class ParentSchema(Schema): str_dump_only = fields.String() str_load_only = fields.String() str_regular = fields.String() - child = fields.List(fields.Nested(ChildSchema)) + child = fields.List(fields.Nested(ChildSchema, unknown=EXCLUDE)) return ParentSchema( dump_only=('str_dump_only', 'child.str_dump_only'), @@ -1972,6 +1973,33 @@ class ParentSchema(Schema): assert data == {'foo': {'bar': 42}, 'bar': 42} assert errors == {'foo': {'foo': ['Not a valid integer.']}} + @pytest.mark.parametrize('data', ({'child': {'num': 1, 'extra': 1}},)) + @pytest.mark.parametrize( + 'child_unknown,errors,load_raises,load_out', + ( + (None, {'child': {'extra': ['Unknown field.']}}, True, None), + (RAISE, {'child': {'extra': ['Unknown field.']}}, True, None), + (INCLUDE, {}, False, {'child': {'num': 1, 'extra': 1}}), + (EXCLUDE, {}, False, {'child': {'num': 1}}), + ), + ) + def test_nested_unknown_validation(self, child_unknown, errors, load_raises, load_out, data): + + class ChildSchema(Schema): + num = fields.Int() + + class ParentSchema(Schema): + child = fields.Nested(ChildSchema, unknown=child_unknown) + + errs = ParentSchema().validate(data) + assert errs == errors + if load_raises: + with pytest.raises(ValidationError): + ParentSchema().load(data) + if load_out: + out = ParentSchema().load(data) + assert out == load_out + class TestPluckSchema: