From c552573e3320a0ab80159bf971f6a004b58ff2f8 Mon Sep 17 00:00:00 2001 From: Pavel Los Date: Wed, 31 Aug 2022 16:44:45 +0300 Subject: [PATCH 1/5] Add more tests for nested partial --- tests/test_deserialization.py | 59 +++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/tests/test_deserialization.py b/tests/test_deserialization.py index 4ee5ba5b1..f3dcf0f6c 100644 --- a/tests/test_deserialization.py +++ b/tests/test_deserialization.py @@ -2019,7 +2019,7 @@ class Sch(Schema): assert "equal" in errors assert errors["equal"] == ["Must be equal to False."] - def test_nested_partial_load(self): + def test_nested_partial_using_bool(self): class SchemaA(Schema): x = fields.Integer(required=True) y = fields.Integer() @@ -2039,7 +2039,7 @@ class SchemaB(Schema): assert "z" in errors assert "x" in errors["z"] - def test_deeply_nested_partial_load(self): + def test_deeply_nested_partial_using_bool(self): class SchemaC(Schema): x = fields.Integer(required=True) y = fields.Integer() @@ -2063,7 +2063,7 @@ class SchemaA(Schema): assert "c" in errors["b"] assert "x" in errors["b"]["c"] - def test_nested_partial_tuple(self): + def test_nested_partial_using_tuple(self): class SchemaA(Schema): x = fields.Integer(required=True) y = fields.Integer(required=True) @@ -2079,6 +2079,59 @@ class SchemaB(Schema): with pytest.raises(ValidationError): SchemaB().load(b_dict, partial=("z.y",)) + def test_nested_fully_partial_using_tuple(self): + class SchemaA(Schema): + x = fields.Integer(required=True) + y = fields.Integer(required=True) + + class SchemaB(Schema): + z = fields.Nested(SchemaA) + + b_dict = {"z": {"y": 42}} + + result = SchemaB().load(b_dict, partial=("z",)) + assert "z" in result + assert "y" in result["z"] + assert "x" not in result["z"] + assert result["z"]["y"] == 42 + + def test_nested_partial_by_init(self): + class SchemaA(Schema): + x = fields.Integer(required=True) + y = fields.Integer(required=True) + + class SchemaB(Schema): + z = fields.Nested(SchemaA(partial=True)) + + b_dict = {"z": {"y": 42}} + + result = SchemaB().load(b_dict) + assert "z" in result + assert "y" in result["z"] + assert "x" not in result["z"] + assert result["z"]["y"] == 42 + + def test_nested_partial_using_tuple_with_data_keys(self): + class SchemaA(Schema): + x = fields.Integer(required=True, data_key="x_key") + y = fields.Integer(required=True, data_key="y_key") + + class SchemaB(Schema): + z = fields.Nested(SchemaA, data_key="z_key") + + b_dict = {"z_key": {"y_key": 42}} + + result = SchemaB().load(b_dict, partial=("z.x",)) + assert result["z"]["y"] == 42 + + with pytest.raises(ValidationError) as exc: + SchemaB().load(b_dict, partial=("z.y",)) + + data, errors = exc.value.valid_data, exc.value.messages + assert data["z"]["y"] == 42 + assert "z_key" in errors + assert "x_key" in errors["z_key"] + @pytest.mark.parametrize("FieldClass", ALL_FIELDS) def test_required_field_failure(FieldClass): # noqa From 473254bdd7ee30e55e960b1736582a91ba8feced Mon Sep 17 00:00:00 2001 From: Pavel Los Date: Wed, 31 Aug 2022 16:49:06 +0300 Subject: [PATCH 2/5] Fix nested partial by init --- src/marshmallow/schema.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/marshmallow/schema.py b/src/marshmallow/schema.py index 92152696f..9428eed49 100644 --- a/src/marshmallow/schema.py +++ b/src/marshmallow/schema.py @@ -373,7 +373,7 @@ def __init__( context: dict | None = None, load_only: types.StrSequenceOrSet = (), dump_only: types.StrSequenceOrSet = (), - partial: bool | types.StrSequenceOrSet = False, + partial: bool | types.StrSequenceOrSet | None = None, unknown: str | None = None, ): # Raise error if only or exclude is passed as string, not list of strings @@ -1080,7 +1080,7 @@ def _invoke_load_processors( *, many: bool, original_data, - partial: bool | types.StrSequenceOrSet, + partial: bool | types.StrSequenceOrSet | None, ): # This has to invert the order of the dump processors, so run the pass_many # processors first. @@ -1157,7 +1157,7 @@ def _invoke_schema_validators( data, original_data, many: bool, - partial: bool | types.StrSequenceOrSet, + partial: bool | types.StrSequenceOrSet | None, field_errors: bool = False, ): for attr_name in self._hooks[(VALIDATES_SCHEMA, pass_many)]: From 27b65f7092de2a3a5baeb2a274abcba48211a0e9 Mon Sep 17 00:00:00 2001 From: Pavel Los Date: Wed, 31 Aug 2022 16:50:29 +0300 Subject: [PATCH 3/5] Fix nested partial with data keys --- src/marshmallow/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/marshmallow/schema.py b/src/marshmallow/schema.py index 9428eed49..d24b7de89 100644 --- a/src/marshmallow/schema.py +++ b/src/marshmallow/schema.py @@ -650,7 +650,7 @@ def _deserialize( d_kwargs = {} # Allow partial loading of nested schemas. if partial_is_collection: - prefix = field_name + "." + prefix = f"{attr_name}." len_prefix = len(prefix) sub_partial = [ f[len_prefix:] for f in partial if f.startswith(prefix) From 1794666a005586363e991120db3f745305d83825 Mon Sep 17 00:00:00 2001 From: Pavel Los Date: Wed, 31 Aug 2022 16:51:43 +0300 Subject: [PATCH 4/5] Fix nested fully partial --- src/marshmallow/schema.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/marshmallow/schema.py b/src/marshmallow/schema.py index d24b7de89..f2b3bf0e3 100644 --- a/src/marshmallow/schema.py +++ b/src/marshmallow/schema.py @@ -647,15 +647,18 @@ def _deserialize( partial_is_collection and attr_name in partial ): continue - d_kwargs = {} + d_kwargs = {} # type: typing.Dict[str, typing.Any] # Allow partial loading of nested schemas. if partial_is_collection: - prefix = f"{attr_name}." - len_prefix = len(prefix) - sub_partial = [ - f[len_prefix:] for f in partial if f.startswith(prefix) - ] - d_kwargs["partial"] = sub_partial + if attr_name in partial: + d_kwargs["partial"] = True + else: + prefix = f"{attr_name}." + len_prefix = len(prefix) + sub_partial = [ + f[len_prefix:] for f in partial if f.startswith(prefix) + ] + d_kwargs["partial"] = sub_partial else: d_kwargs["partial"] = partial getter = lambda val: field_obj.deserialize( From 7455a23ef7a9b0f264ce2ddb1fcbcb0a9bc016e9 Mon Sep 17 00:00:00 2001 From: Pavel Los Date: Wed, 31 Aug 2022 19:06:50 +0300 Subject: [PATCH 5/5] Refactor nested partial tests using `parametrize` --- tests/test_deserialization.py | 53 ++++++++--------------------------- 1 file changed, 12 insertions(+), 41 deletions(-) diff --git a/tests/test_deserialization.py b/tests/test_deserialization.py index f3dcf0f6c..08d872edd 100644 --- a/tests/test_deserialization.py +++ b/tests/test_deserialization.py @@ -2019,17 +2019,18 @@ class Sch(Schema): assert "equal" in errors assert errors["equal"] == ["Must be equal to False."] - def test_nested_partial_using_bool(self): + @pytest.mark.parametrize("partial", (True, {"z.x"}, {"z"})) + def test_nested_partial(self, partial): class SchemaA(Schema): x = fields.Integer(required=True) - y = fields.Integer() + y = fields.Integer(required=True) class SchemaB(Schema): z = fields.Nested(SchemaA) b_dict = {"z": {"y": 42}} # Partial loading shouldn't generate any errors. - result = SchemaB().load(b_dict, partial=True) + result = SchemaB().load(b_dict, partial=partial) assert result["z"]["y"] == 42 # Non partial loading should complain about missing values. with pytest.raises(ValidationError) as excinfo: @@ -2039,10 +2040,11 @@ class SchemaB(Schema): assert "z" in errors assert "x" in errors["z"] - def test_deeply_nested_partial_using_bool(self): + @pytest.mark.parametrize("partial", (True, {"b.c.x"}, {"b.c"}, {"b"})) + def test_deeply_nested_partial(self, partial): class SchemaC(Schema): x = fields.Integer(required=True) - y = fields.Integer() + y = fields.Integer(required=True) class SchemaB(Schema): c = fields.Nested(SchemaC) @@ -2052,7 +2054,7 @@ class SchemaA(Schema): a_dict = {"b": {"c": {"y": 42}}} # Partial loading shouldn't generate any errors. - result = SchemaA().load(a_dict, partial=True) + result = SchemaA().load(a_dict, partial=partial) assert result["b"]["c"]["y"] == 42 # Non partial loading should complain about missing values. with pytest.raises(ValidationError) as excinfo: @@ -2063,45 +2065,14 @@ class SchemaA(Schema): assert "c" in errors["b"] assert "x" in errors["b"]["c"] - def test_nested_partial_using_tuple(self): - class SchemaA(Schema): - x = fields.Integer(required=True) - y = fields.Integer(required=True) - - class SchemaB(Schema): - z = fields.Nested(SchemaA) - - b_dict = {"z": {"y": 42}} - # If we ignore the missing z.x, z.y should still load. - result = SchemaB().load(b_dict, partial=("z.x",)) - assert result["z"]["y"] == 42 - # If we ignore a missing z.y we should get a validation error. - with pytest.raises(ValidationError): - SchemaB().load(b_dict, partial=("z.y",)) - - def test_nested_fully_partial_using_tuple(self): - class SchemaA(Schema): - x = fields.Integer(required=True) - y = fields.Integer(required=True) - - class SchemaB(Schema): - z = fields.Nested(SchemaA) - - b_dict = {"z": {"y": 42}} - - result = SchemaB().load(b_dict, partial=("z",)) - assert "z" in result - assert "y" in result["z"] - assert "x" not in result["z"] - assert result["z"]["y"] == 42 - - def test_nested_partial_by_init(self): + @pytest.mark.parametrize("partial", (True, {"x"})) + def test_nested_partial_from_schema_init(self, partial): class SchemaA(Schema): x = fields.Integer(required=True) y = fields.Integer(required=True) class SchemaB(Schema): - z = fields.Nested(SchemaA(partial=True)) + z = fields.Nested(SchemaA(partial=partial)) b_dict = {"z": {"y": 42}} @@ -2111,7 +2082,7 @@ class SchemaB(Schema): assert "x" not in result["z"] assert result["z"]["y"] == 42 - def test_nested_partial_using_tuple_with_data_keys(self): + def test_nested_with_data_keys_partial_using_tuple(self): class SchemaA(Schema): x = fields.Integer(required=True, data_key="x_key") y = fields.Integer(required=True, data_key="y_key")