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

Bubble up the inner expression #199

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
20 changes: 19 additions & 1 deletion mashumaro/core/meta/code/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1225,7 +1225,25 @@ def _try_set_value(
) -> None:
with self.lines.indent("try:"):
self._set_value(field_name, unpacked_value, in_kwargs)
with self.lines.indent("except:"):
with self.lines.indent("except MissingField as inner:"):
self.lines.append(
f"path = '{field_name}' + '.' + inner.path if inner.path else '{field_name}'"
)
self.lines.append(
"raise MissingField("
"inner.field_name,inner.field_type,"
"inner.holder_class,path) from None"
)
with self.lines.indent("except InvalidFieldValue as inner:"):
self.lines.append(
f"path = '{field_name}' + '.' + inner.path if inner.path else '{field_name}'"
)
self.lines.append(
"raise InvalidFieldValue("
"inner.field_name,inner.field_type,inner.field_value,"
"inner.holder_class,inner.msg,path) from None"
)
with self.lines.indent("except Exception:"):
self.lines.append(
"raise InvalidFieldValue("
f"'{field_name}',{field_type_name},value,cls)"
Expand Down
17 changes: 14 additions & 3 deletions mashumaro/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@


class MissingField(LookupError):
def __init__(self, field_name: str, field_type: Type, holder_class: Type):
def __init__(
self,
field_name: str,
field_type: Type,
holder_class: Type,
path: Optional[str] = None,
):
self.field_name = field_name
self.field_type = field_type
self.holder_class = holder_class
self.path = path

@property
def field_type_name(self) -> str:
Expand All @@ -18,8 +25,9 @@ def holder_class_name(self) -> str:
return type_name(self.holder_class, short=True)

def __str__(self) -> str:
path = f' at path "{self.path}"' if self.path else ""
return (
f'Field "{self.field_name}" of type {self.field_type_name}'
f'Field "{self.field_name}"{path} of type {self.field_type_name}'
f" is missing in {self.holder_class_name} instance"
)

Expand Down Expand Up @@ -99,12 +107,14 @@ def __init__(
field_value: Any,
holder_class: Type,
msg: Optional[str] = None,
path: Optional[str] = None,
):
self.field_name = field_name
self.field_type = field_type
self.field_value = field_value
self.holder_class = holder_class
self.msg = msg
self.path = path

@property
def field_type_name(self) -> str:
Expand All @@ -115,8 +125,9 @@ def holder_class_name(self) -> str:
return type_name(self.holder_class, short=True)

def __str__(self) -> str:
path = f' at path "{self.path}"' if self.path else ""
s = (
f'Field "{self.field_name}" of type {self.field_type_name} '
f'Field "{self.field_name}"{path} of type {self.field_type_name} '
f"in {self.holder_class_name} has invalid value "
f"{repr(self.field_value)}"
)
Expand Down
39 changes: 39 additions & 0 deletions tests/test_data_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,45 @@ class Config(BaseConfig):
DataClass.from_dict({"x": "bad_value"})


@dataclass
class _InnerDeserializeTest(DataClassDictMixin):
x: int


@dataclass
class _MiddleDeserializeTest(DataClassDictMixin):
inner: _InnerDeserializeTest


def test_invalid_nested_field_value_deserialization():
@dataclass
class Outer(DataClassDictMixin):
middle: _MiddleDeserializeTest

with pytest.raises(InvalidFieldValue) as exc_info:
Outer.from_dict({"middle": {"inner": {"x": "bad_value"}}})

assert exc_info.value.field_name == "x"
assert exc_info.value.field_type is int
assert exc_info.value.holder_class == _InnerDeserializeTest
assert exc_info.value.field_value == "bad_value"
assert exc_info.value.path == "middle.inner"


def test_missing_nested_field_value_deserialization():
@dataclass
class Outer(DataClassDictMixin):
middle: _MiddleDeserializeTest

with pytest.raises(MissingField) as exc_info:
Outer.from_dict({"middle": {"inner": {}}})

assert exc_info.value.field_name == "x"
assert exc_info.value.field_type is int
assert exc_info.value.holder_class == _InnerDeserializeTest
assert exc_info.value.path == "middle.inner"


@pytest.mark.parametrize(
"value_info",
[
Expand Down
Loading