Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass unknown parameter to pre_load, post_load, and validates_schema methods #1632

Merged
merged 5 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ Thanks :user:`ddelange` for the PR.
UserSchema().dump({"name": "foo"})
# {'name_suffixed': 'foobar'}

- Methods decorated with `marshmallow.pre_load`, `marshmallow.post_load`, `marshmallow.validates_schema`,
receive ``unknown`` as a keyword argument (:pr:`1632`).
Thanks :user:`jforand` for the PR.

Deprecations/Removals:

- *Backwards-incompatible*: Remove implicit field creation, i.e. using the ``fields`` or ``additional`` class Meta options with undeclared fields (:issue:`1356`).
Expand Down
7 changes: 6 additions & 1 deletion src/marshmallow/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,11 @@ def validates_schema(

.. versionchanged:: 3.0.0b1
``skip_on_field_errors`` defaults to `True`.

.. versionchanged:: 3.0.0
``partial`` and ``many`` are always passed as keyword arguments to
the decorated method.
.. versionchanged:: 4.0.0
``unknown`` is passed as a keyword argument to the decorated method.
"""
return set_hook(
fn,
Expand Down Expand Up @@ -175,6 +176,8 @@ def pre_load(
.. versionchanged:: 3.0.0
``partial`` and ``many`` are always passed as keyword arguments to
the decorated method.
.. versionchanged:: 4.0.0
``unknown`` is passed as a keyword argument to the decorated method.
"""
return set_hook(fn, PRE_LOAD, many=pass_collection)

Expand All @@ -197,6 +200,8 @@ def post_load(
.. versionchanged:: 3.0.0
``partial`` and ``many`` are always passed as keyword arguments to
the decorated method.
.. versionchanged:: 4.0.0
``unknown`` is passed as a keyword argument to the decorated method.
"""
return set_hook(fn, POST_LOAD, many=pass_collection, pass_original=pass_original)

Expand Down
23 changes: 20 additions & 3 deletions src/marshmallow/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -767,14 +767,17 @@ def _run_validator(
error_store,
many,
partial,
unknown,
pass_original,
index=None,
):
try:
if pass_original: # Pass original, raw data (before unmarshalling)
validator_func(output, original_data, partial=partial, many=many)
validator_func(
output, original_data, partial=partial, many=many, unknown=unknown
)
else:
validator_func(output, partial=partial, many=many)
validator_func(output, partial=partial, many=many, unknown=unknown)
except ValidationError as err:
error_store.store_error(err.messages, err.field_name, index=index)

Expand Down Expand Up @@ -850,7 +853,12 @@ def _do_load(
if self._hooks[PRE_LOAD]:
try:
processed_data = self._invoke_load_processors(
PRE_LOAD, data, many=many, original_data=data, partial=partial
PRE_LOAD,
data,
many=many,
original_data=data,
partial=partial,
unknown=unknown,
)
except ValidationError as err:
errors = err.normalized_messages()
Expand Down Expand Up @@ -880,6 +888,7 @@ def _do_load(
original_data=data,
many=many,
partial=partial,
unknown=unknown,
field_errors=field_errors,
)
self._invoke_schema_validators(
Expand All @@ -889,6 +898,7 @@ def _do_load(
original_data=data,
many=many,
partial=partial,
unknown=unknown,
field_errors=field_errors,
)
errors = error_store.errors
Expand All @@ -901,6 +911,7 @@ def _do_load(
many=many,
original_data=data,
partial=partial,
unknown=unknown,
)
except ValidationError as err:
errors = err.normalized_messages()
Expand Down Expand Up @@ -1080,6 +1091,7 @@ def _invoke_load_processors(
many: bool,
original_data,
partial: bool | types.StrSequenceOrSet | None,
unknown: str,
):
# This has to invert the order of the dump processors, so run the pass_collection
# processors first.
Expand All @@ -1090,6 +1102,7 @@ def _invoke_load_processors(
many=many,
original_data=original_data,
partial=partial,
unknown=unknown,
)
data = self._invoke_processors(
tag,
Expand All @@ -1098,6 +1111,7 @@ def _invoke_load_processors(
many=many,
original_data=original_data,
partial=partial,
unknown=unknown,
)
return data

Expand Down Expand Up @@ -1157,6 +1171,7 @@ def _invoke_schema_validators(
many: bool,
partial: bool | types.StrSequenceOrSet | None,
field_errors: bool = False,
unknown: str,
):
for attr_name, hook_many, validator_kwargs in self._hooks[VALIDATES_SCHEMA]:
if hook_many != pass_collection:
Expand All @@ -1175,6 +1190,7 @@ def _invoke_schema_validators(
error_store=error_store,
many=many,
partial=partial,
unknown=unknown,
index=idx,
pass_original=pass_original,
)
Expand All @@ -1187,6 +1203,7 @@ def _invoke_schema_validators(
many=many,
pass_original=pass_original,
partial=partial,
unknown=unknown,
)

def _invoke_processors(
Expand Down
33 changes: 33 additions & 0 deletions tests/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -881,3 +881,36 @@ class ExampleSchema(Schema):

schema = ExampleSchema()
assert schema.load(data) == data


@pytest.mark.parametrize("usage_location", ["meta", "init", "load"])
@pytest.mark.parametrize("unknown_val", (EXCLUDE, INCLUDE))
def test_load_processors_receive_unknown(usage_location, unknown_val):
class ExampleSchema(Schema):
foo = fields.Int()

@validates_schema
def check_unknown_validates(self, data, unknown, **kwargs):
assert unknown == unknown_val

@pre_load
def check_unknown_pre(self, data, unknown, **kwargs):
assert unknown == unknown_val
return data

@post_load
def check_unknown_post(self, data, unknown, **kwargs):
assert unknown == unknown_val
return data

if usage_location == "meta":

class ExampleSchemaChild(ExampleSchema):
class Meta:
unknown = unknown_val

ExampleSchemaChild().load({"foo": 42})
if usage_location == "init":
ExampleSchema(unknown=unknown_val).load({"foo": 42})
else:
ExampleSchema().load({"foo": 42}, unknown=unknown_val)
Loading