Skip to content

Commit 95d1828

Browse files
mahenzonsirosen
authored andcommitted
Propagate unknown parameter to nested fields
1 parent 3be16c2 commit 95d1828

File tree

3 files changed

+78
-4
lines changed

3 files changed

+78
-4
lines changed

src/marshmallow/fields.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -571,16 +571,18 @@ def _test_collection(self, value):
571571
if many and not utils.is_collection(value):
572572
raise self.make_error("type", input=value, type=value.__class__.__name__)
573573

574-
def _load(self, value, data, partial=None):
574+
def _load(self, value, data, partial=None, unknown=None):
575575
try:
576-
valid_data = self.schema.load(value, unknown=self.unknown, partial=partial)
576+
valid_data = self.schema.load(
577+
value, unknown=unknown or self.unknown, partial=partial,
578+
)
577579
except ValidationError as error:
578580
raise ValidationError(
579581
error.messages, valid_data=error.valid_data
580582
) from error
581583
return valid_data
582584

583-
def _deserialize(self, value, attr, data, partial=None, **kwargs):
585+
def _deserialize(self, value, attr, data, partial=None, unknown=None, **kwargs):
584586
"""Same as :meth:`Field._deserialize` with additional ``partial`` argument.
585587
586588
:param bool|tuple partial: For nested schemas, the ``partial``
@@ -590,7 +592,7 @@ def _deserialize(self, value, attr, data, partial=None, **kwargs):
590592
Add ``partial`` parameter.
591593
"""
592594
self._test_collection(value)
593-
return self._load(value, data, partial=partial)
595+
return self._load(value, data, partial=partial, unknown=unknown)
594596

595597

596598
class Pluck(Nested):

src/marshmallow/schema.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,13 @@ def _deserialize(
659659
d_kwargs["partial"] = sub_partial
660660
else:
661661
d_kwargs["partial"] = partial
662+
663+
try:
664+
if self.context["propagate_unknown_to_nested"]:
665+
d_kwargs["unknown"] = unknown
666+
except KeyError:
667+
pass
668+
662669
getter = lambda val: field_obj.deserialize(
663670
val, field_name, data, **d_kwargs
664671
)
@@ -836,6 +843,7 @@ def _do_load(
836843
error_store = ErrorStore()
837844
errors = {} # type: typing.Dict[str, typing.List[str]]
838845
many = self.many if many is None else bool(many)
846+
self.context["propagate_unknown_to_nested"] = unknown is not None
839847
unknown = unknown or self.unknown
840848
if partial is None:
841849
partial = self.partial

tests/test_fields.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,70 @@ class MySchema(Schema):
296296
}
297297

298298

299+
class TestNestedFieldPropagatesUnknown:
300+
class SpamSchema(Schema):
301+
meat = fields.String()
302+
303+
class CanSchema(Schema):
304+
spam = fields.Nested("SpamSchema")
305+
306+
class ShelfSchema(Schema):
307+
can = fields.Nested("CanSchema")
308+
309+
@pytest.fixture
310+
def data_nested_unknown(self):
311+
return {
312+
"spam": {"meat": "pork", "add-on": "eggs"},
313+
}
314+
315+
@pytest.fixture
316+
def multi_nested_data_with_unknown(self, data_nested_unknown):
317+
return {
318+
"can": data_nested_unknown,
319+
"box": {"foo": "bar"},
320+
}
321+
322+
@pytest.mark.parametrize("schema_kw", ({}, {"unknown": INCLUDE}))
323+
def test_raises_when_unknown_passed_to_first_level_nested(
324+
self, schema_kw, data_nested_unknown,
325+
):
326+
with pytest.raises(ValidationError) as exc_info:
327+
self.CanSchema(**schema_kw).load(data_nested_unknown)
328+
assert exc_info.value.messages == {"spam": {"add-on": ["Unknown field."]}}
329+
330+
@pytest.mark.parametrize(
331+
"load_kw,expected_data",
332+
(
333+
({"unknown": INCLUDE}, {"spam": {"meat": "pork", "add-on": "eggs"}}),
334+
({"unknown": EXCLUDE}, {"spam": {"meat": "pork"}}),
335+
),
336+
)
337+
def test_processes_when_unknown_stated_directly(
338+
self, load_kw, data_nested_unknown, expected_data,
339+
):
340+
data = self.CanSchema().load(data_nested_unknown, **load_kw)
341+
assert data == expected_data
342+
343+
@pytest.mark.parametrize(
344+
"load_kw,expected_data",
345+
(
346+
(
347+
{"unknown": INCLUDE},
348+
{
349+
"can": {"spam": {"meat": "pork", "add-on": "eggs"}},
350+
"box": {"foo": "bar"},
351+
},
352+
),
353+
({"unknown": EXCLUDE}, {"can": {"spam": {"meat": "pork"}}}),
354+
),
355+
)
356+
def test_propagates_unknown_to_multi_nested_fields(
357+
self, load_kw, expected_data, multi_nested_data_with_unknown,
358+
):
359+
data = self.ShelfSchema().load(multi_nested_data_with_unknown, **load_kw)
360+
assert data == expected_data
361+
362+
299363
class TestListNested:
300364
@pytest.mark.parametrize("param", ("only", "exclude", "dump_only", "load_only"))
301365
def test_list_nested_only_exclude_dump_only_load_only_propagated_to_nested(

0 commit comments

Comments
 (0)