Skip to content

Commit

Permalink
Merge pull request #565 from yukinarit/fix-de-internal-tagging
Browse files Browse the repository at this point in the history
Fix deserialization when internal tagging is used for non dataclass
  • Loading branch information
yukinarit authored Jun 26, 2024
2 parents d87205b + 3604f7b commit 96b1df2
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 4 deletions.
9 changes: 8 additions & 1 deletion serde/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def typename(typ: Any, with_typing_module: bool = False) -> str:
args = type_args(typ)
if not args:
raise TypeError("Literal type requires at least one literal argument")
return f'Literal[{", ".join(str(e) for e in args)}]'
return f'Literal[{", ".join(stringify_literal(e) for e in args)}]'
elif typ is Any:
return f"{mod}Any"
elif is_ellipsis(typ):
Expand All @@ -236,6 +236,13 @@ def typename(typ: Any, with_typing_module: bool = False) -> str:
raise SerdeError(f"Could not get a type name from: {typ}")


def stringify_literal(v: Any) -> str:
if isinstance(v, str):
return f"'{v}'"
else:
return str(v)


def type_args(typ: Any) -> tuple[type[Any], ...]:
"""
Wrapper to suppress type error for accessing private members.
Expand Down
8 changes: 6 additions & 2 deletions serde/de.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import jinja2
from collections.abc import Callable, Sequence, Iterable
from beartype import beartype, BeartypeConf
from beartype.door import is_bearable
from beartype.roar import BeartypeCallHintParamViolation
from dataclasses import dataclass, is_dataclass
from typing import overload, TypeVar, Generic, Any, Optional, Union, Literal
Expand Down Expand Up @@ -263,6 +264,7 @@ def wrap(cls: type[T]) -> type[T]:
g["_get_by_aliases"] = _get_by_aliases
g["class_deserializers"] = class_deserializers
g["BeartypeCallHintParamViolation"] = BeartypeCallHintParamViolation
g["is_bearable"] = is_bearable
if deserializer:
g["serde_legacy_custom_class_deserializer"] = functools.partial(
serde_legacy_custom_class_deserializer, custom=deserializer
Expand Down Expand Up @@ -1158,9 +1160,11 @@ def {{func}}(cls=cls, maybe_generic=None, maybe_generic_type_vars=None, data=Non
if not isinstance(fake_dict["fake_key"], {{typename(t)}}):
raise Exception("Not a type of {{typename(t)}}")
{% endif %}
return {{rvalue(arg(t))}}
res = {{rvalue(arg(t))}}
ensure(is_bearable(res, {{typename(t)}}), "object is not of type '{{typename(t)}}'")
return res
except Exception as e:
errors.append(f' Failed to deserialize into {{typename(t)}}: {e}')
errors.append(f" Failed to deserialize into {{typename(t)}}: {e}")
{% endfor %}
raise SerdeError("Can not deserialize " + repr(data) + " of type " + \
typename(type(data)) + " into {{union_name}}.\\nReasons:\\n" + "\\n".join(errors))
Expand Down
2 changes: 1 addition & 1 deletion tests/test_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class Foo(Generic[T]):
assert typename(dict[str, Foo]) == "dict[str, Foo]" # type: ignore
assert typename(set) == "set"
assert typename(set[int]) == "set[int]"
assert typename(Literal[1, True, "Hey"]) == "Literal[1, True, Hey]"
assert typename(Literal[1, True, "Hey"]) == "Literal[1, True, 'Hey']"


def test_iter_types() -> None:
Expand Down
16 changes: 16 additions & 0 deletions tests/test_union.py
Original file line number Diff line number Diff line change
Expand Up @@ -822,3 +822,19 @@ class Foo:
s = '{"a":"1","b":{"Expr":{"expr":"2"}},"c":"3"}'
assert s == to_json(f)
assert f == from_json(Foo, s)


def test_union_internal_tagging_for_non_dataclass() -> None:
@serde
class Bar:
a: int

@serde(tagging=InternalTagging("type"))
class Foo:
x: Union[list[int], Bar]

f = Foo(Bar(1))
assert f == from_json(Foo, to_json(f))

f = Foo([10])
assert f == from_json(Foo, to_json(f))

0 comments on commit 96b1df2

Please sign in to comment.