Skip to content

Commit

Permalink
Fix handling data_key in ValidationErrors raised in schema validators (
Browse files Browse the repository at this point in the history
  • Loading branch information
sloria authored Jan 19, 2025
1 parent a578ed2 commit 9f751e1
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 10 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ Features:
- Typing: Improve type coverage of `marshmallow.Schema.SchemaMeta` (:pr:`2761`).
- Typing: `marshmallow.Schema.loads` parameter allows `bytes` and `bytesarray` (:pr:`2769`).

Bug fixes:

- Respect ``data_key`` when schema validators raise a `ValidationError <marshmallow.exceptions.ValidationError>`
with a ``field_name`` argument (:issue:`2170`). Thanks :user:`matejsp` for reporting.

Documentation:

- Add :doc:`upgrading guides <upgrading>` for 3.24 and 3.26 (:pr:`2780`).
Expand Down
7 changes: 5 additions & 2 deletions src/marshmallow/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ def validate_age(self, data, **kwargs):

import functools
from collections import defaultdict
from typing import Any, Callable, cast
from typing import TYPE_CHECKING, Any, Callable, cast

if TYPE_CHECKING:
from marshmallow import types

PRE_DUMP = "pre_dump"
POST_DUMP = "post_dump"
Expand All @@ -92,7 +95,7 @@ def validates(field_name: str) -> Callable[..., Any]:


def validates_schema(
fn: Callable[..., Any] | None = None,
fn: types.SchemaValidator | None = None,
pass_many: bool = False, # noqa: FBT001, FBT002
pass_original: bool = False, # noqa: FBT001, FBT002
skip_on_field_errors: bool = True, # noqa: FBT001, FBT002
Expand Down
35 changes: 27 additions & 8 deletions src/marshmallow/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
VALIDATES_SCHEMA,
)
from marshmallow.error_store import ErrorStore
from marshmallow.exceptions import StringNotCollectionError, ValidationError
from marshmallow.exceptions import SCHEMA, StringNotCollectionError, ValidationError
from marshmallow.orderedset import OrderedSet
from marshmallow.utils import (
EXCLUDE,
Expand Down Expand Up @@ -824,23 +824,42 @@ def loads(

def _run_validator(
self,
validator_func,
validator_func: types.SchemaValidator,
output,
*,
original_data,
error_store,
many,
partial,
pass_original,
index=None,
error_store: ErrorStore,
many: bool,
partial: bool | types.StrSequenceOrSet | None,
pass_original: bool,
index: int | None = None,
):
try:
if pass_original: # Pass original, raw data (before unmarshalling)
validator_func(output, original_data, partial=partial, many=many)
else:
validator_func(output, partial=partial, many=many)
except ValidationError as err:
error_store.store_error(err.messages, err.field_name, index=index)
field_name = err.field_name
data_key: str
if field_name == SCHEMA:
data_key = SCHEMA
else:
field_obj: Field | None = None
try:
field_obj = self.fields[field_name]
except KeyError:
if field_name in self.declared_fields:
field_obj = self.declared_fields[field_name]
if field_obj:
data_key = (
field_obj.data_key
if field_obj.data_key is not None
else field_name
)
else:
data_key = field_name
error_store.store_error(err.messages, data_key, index=index)

def validate(
self,
Expand Down
11 changes: 11 additions & 0 deletions src/marshmallow/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@
Validator = typing.Callable[[typing.Any], typing.Any]


class SchemaValidator(typing.Protocol):
def __call__(
self,
output: typing.Any,
original_data: typing.Any = ...,
*,
partial: bool | StrSequenceOrSet | None = None,
many: bool = False,
) -> None: ...


class RenderModule(typing.Protocol):
def dumps(
self, obj: typing.Any, *args: typing.Any, **kwargs: typing.Any
Expand Down
28 changes: 28 additions & 0 deletions tests/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,34 @@ def validate_many(self, data, many, **kwargs):
assert "bar" in errors[0]
assert "_schema" not in errors

# https://github.com/marshmallow-code/marshmallow/issues/2170
def test_data_key_is_used_in_errors_dict(self):
class MySchema(Schema):
foo = fields.Int(data_key="fooKey")

@validates("foo")
def validate_foo(self, value, **kwargs):
raise ValidationError("from validates")

@validates_schema(skip_on_field_errors=False)
def validate_schema(self, data, **kwargs):
raise ValidationError("from validates_schema str", field_name="foo")

@validates_schema(skip_on_field_errors=False)
def validate_schema2(self, data, **kwargs):
raise ValidationError({"fooKey": "from validates_schema dict"})

with pytest.raises(ValidationError) as excinfo:
MySchema().load({"fooKey": 42})
exc = excinfo.value
assert exc.messages == {
"fooKey": [
"from validates",
"from validates_schema str",
"from validates_schema dict",
]
}


def test_decorator_error_handling():
class ExampleSchema(Schema):
Expand Down

0 comments on commit 9f751e1

Please sign in to comment.