Skip to content

Commit 105702c

Browse files
committed
Make unknown propagation override child schemas
In order to simplify, the value of `unknown` will not be respected in child schemas if `PROPAGATE` is used. This removes any need for the `auto_unknown` tracking, so there's less complexity added to schemas in order to implement this behavior. Adjust tests and changelog to match.
1 parent 08e1950 commit 105702c

File tree

5 files changed

+30
-67
lines changed

5 files changed

+30
-67
lines changed

CHANGELOG.rst

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,16 @@ Features:
88

99
- The behavior of the ``unknown`` option can be further customized with a new
1010
value, ``PROPAGATE``. If ``unknown=EXCLUDE | PROPAGATE`` is set, then the
11-
value of ``unknown=EXCLUDE | PROPAGATE`` will be passed to any nested
12-
schemas which do not explicitly set ``unknown`` in ``Nested`` or schema
13-
options. This works for ``INCLUDE | PROPAGATE`` and ``RAISE | PROPAGATE`` as
14-
well.
11+
value of ``unknown=EXCLUDE | PROPAGATE`` will be passed to any nested schemas.
12+
This works for ``INCLUDE | PROPAGATE`` and ``RAISE | PROPAGATE`` as well.
1513
(:issue:`1490`, :issue:`1428`)
16-
Thanks :user:`lmignon` and :user:`mahenzon`.
1714

1815
.. note::
1916

20-
When a schema is being loaded with ``unknown=... | PROPAGATE``, you can still
21-
set ``unknown`` explicitly on child schemas. However, be aware that such a
22-
value may turn off propagation at that point in the schema hierarchy.
23-
24-
For example, a schema which specifies ``unknown=EXCLUDE`` will set
25-
``EXCLUDE`` for itself. But because the value is ``EXCLUDE`` rather than
26-
``EXCLUDE | PROPAGATE``, that setting will not be propagated to its
27-
children, even if there is a parent schema which sets
28-
``unknown=EXCLUDE | PROPAGATE``.
17+
When a schema is being loaded with ``unknown=... | PROPAGATE``, this will
18+
override any values set for ``unknown`` in child schemas. Therefore,
19+
``PROPAGATE`` should only be used in cases in which you want to change
20+
the behavior of an entire schema heirarchy.
2921

3022
3.7.0 (2020-07-08)
3123
******************

src/marshmallow/base.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ def dump(self, obj, *, many: bool = None):
3838
def dumps(self, obj, *, many: bool = None):
3939
raise NotImplementedError
4040

41-
def load(
42-
self, data, *, many: bool = None, partial=None, unknown=None
43-
):
41+
def load(self, data, *, many: bool = None, partial=None, unknown=None):
4442
raise NotImplementedError
4543

4644
def loads(

src/marshmallow/fields.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -593,18 +593,12 @@ def _deserialize(self, value, attr, data, partial=None, unknown=None, **kwargs):
593593
Add ``partial`` parameter.
594594
"""
595595
self._test_collection(value)
596-
# check if self.unknown or self.schema.unknown is set
597-
# however, we should only respect `self.schema.unknown` if
598-
# `auto_unknown` is False, meaning that it was set explicitly on the
599-
# schema class or instance
600-
explicit_unknown = (
601-
self.unknown
602-
if self.unknown
603-
else (self.schema.unknown if not self.schema.auto_unknown else None)
596+
return self._load(
597+
value,
598+
data,
599+
partial=partial,
600+
unknown=unknown or self.unknown or self.schema.unknown,
604601
)
605-
if explicit_unknown:
606-
unknown = explicit_unknown
607-
return self._load(value, data, partial=partial, unknown=unknown,)
608602

609603

610604
class Pluck(Nested):

src/marshmallow/schema.py

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -228,15 +228,7 @@ def __init__(self, meta, ordered: bool = False):
228228
self.include = getattr(meta, "include", {})
229229
self.load_only = getattr(meta, "load_only", ())
230230
self.dump_only = getattr(meta, "dump_only", ())
231-
# self.unknown defaults to "RAISE", but note whether it was explicit or
232-
# not, so that when we're handling "propagate" we can decide
233-
# whether or not to propagate based on whether or not it was set
234-
# explicitly
235-
self.unknown = getattr(meta, "unknown", None)
236-
self.auto_unknown = self.unknown is None
237-
if self.auto_unknown:
238-
self.unknown = RAISE
239-
self.unknown = UnknownParam.parse_if_str(self.unknown)
231+
self.unknown = UnknownParam.parse_if_str(getattr(meta, "unknown", RAISE))
240232
self.register = getattr(meta, "register", True)
241233

242234

@@ -398,13 +390,8 @@ def __init__(
398390
self.dump_only = set(dump_only) or set(self.opts.dump_only)
399391
self.partial = partial
400392
self.unknown = (
401-
UnknownParam.parse_if_str(unknown)
402-
if unknown is not None
403-
else self.opts.unknown
393+
UnknownParam.parse_if_str(unknown) if unknown else self.opts.unknown
404394
)
405-
# if unknown was not set explicitly AND self.opts.auto_unknown is true,
406-
# then the value should be considered "automatic"
407-
self.auto_unknown = (not unknown) and self.opts.auto_unknown
408395
self.context = context or {}
409396
self._normalize_nested_options()
410397
#: Dictionary mapping field_names -> :class:`Field` objects
@@ -857,9 +844,7 @@ def _do_load(
857844
error_store = ErrorStore()
858845
errors = {} # type: typing.Dict[str, typing.List[str]]
859846
many = self.many if many is None else bool(many)
860-
unknown = UnknownParam.parse_if_str(
861-
unknown if unknown is not None else self.unknown
862-
)
847+
unknown = UnknownParam.parse_if_str(unknown or self.unknown)
863848
if partial is None:
864849
partial = self.partial
865850
# Run preprocessors

tests/test_schema.py

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2894,12 +2894,11 @@ class DefinitelyUniqueSchema(Schema):
28942894
assert SchemaClass is DefinitelyUniqueSchema
28952895

28962896

2897-
def test_propagate_unknown_stops_at_explicit_value_for_nested():
2898-
# PROPAGATE should traverse any "auto_unknown" values and
2899-
# replace them with the "unknown" value from the parent context (schema or
2900-
# load arguments)
2901-
# this test makes sure that it stops when a nested field or schema has
2902-
# "unknown" set explicitly (so auto_unknown=False)
2897+
def test_propagate_unknown_overrides_explicit_value_for_nested():
2898+
# PROPAGATE should traverse any schemas and replace them with the
2899+
# "unknown" value from the parent context (schema or load arguments)
2900+
# this test makes sure that it takes precedence when a nested field
2901+
# or schema has "unknown" set explicitly
29032902

29042903
class Bottom(Schema):
29052904
x = fields.Str()
@@ -2923,14 +2922,13 @@ class Top(Schema):
29232922
assert result == {
29242923
"x": "hi",
29252924
"y": "bye",
2926-
"child": {"x": "hi", "y": "bye", "child": {"x": "hi"}},
2925+
"child": {"x": "hi", "y": "bye", "child": {"x": "hi", "y": "bye"}},
29272926
}
29282927

29292928

2930-
def test_propagate_unknown_stops_at_explicit_value_for_meta():
2931-
# this is the same as the above test of unknown propagation stopping where
2932-
# auto_unknown=False, but it checks that this applies when `unknown` is set
2933-
# by means of `Meta`
2929+
def test_propagate_unknown_overrides_explicit_value_for_meta():
2930+
# this is the same as the above test of unknown propagation, but it checks that
2931+
# this applies when `unknown` is set by means of `Meta` as well
29342932

29352933
class Bottom(Schema):
29362934
x = fields.Str()
@@ -2939,25 +2937,21 @@ class Middle(Schema):
29392937
x = fields.Str()
29402938
child = fields.Nested(Bottom)
29412939

2942-
# set unknown explicitly here, so auto_unknown will be
2943-
# false going into Bottom, and also set propagate to make it propagate
2944-
# in this case
29452940
class Meta:
2946-
unknown = EXCLUDE | PROPAGATE
2941+
unknown = EXCLUDE
29472942

29482943
class Top(Schema):
29492944
x = fields.Str()
29502945
child = fields.Nested(Middle)
29512946

2952-
# sanity-check that auto-unknown is being set correctly
2953-
assert Top().auto_unknown
2954-
assert not Top(unknown=INCLUDE | PROPAGATE).auto_unknown
2955-
assert not Middle().auto_unknown
2956-
29572947
data = {
29582948
"x": "hi",
29592949
"y": "bye",
29602950
"child": {"x": "hi", "y": "bye", "child": {"x": "hi", "y": "bye"}},
29612951
}
29622952
result = Top(unknown=INCLUDE | PROPAGATE).load(data)
2963-
assert result == {"x": "hi", "y": "bye", "child": {"x": "hi", "child": {"x": "hi"}}}
2953+
assert result == {
2954+
"x": "hi",
2955+
"y": "bye",
2956+
"child": {"x": "hi", "y": "bye", "child": {"x": "hi", "y": "bye"}},
2957+
}

0 commit comments

Comments
 (0)