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

Fix reuse_instances for non dataclass objects #486

Merged
merged 1 commit into from
Mar 9, 2024
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: 2 additions & 2 deletions serde/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,13 @@ def _generate_class(self, cls: Type[Any]) -> Type[Any]:
logger.debug(f"(de)serializing code for {class_name} was generated")
return wrapper

def serialize(self, cls: Type[Any], obj: Any) -> Any:
def serialize(self, cls: Type[Any], obj: Any, **kwargs: Any) -> Any:
"""
Serialize the specified type of object into dict or tuple.
"""
wrapper = self._get_class(cls)
scope: Scope = getattr(wrapper, SERDE_SCOPE)
data = scope.funcs[TO_DICT](wrapper(obj), reuse_instances=False, convert_sets=True)
data = scope.funcs[TO_DICT](wrapper(obj), **kwargs)

logging.debug(f"Intermediate value: {data}")

Expand Down
11 changes: 9 additions & 2 deletions serde/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,12 @@ def deserialize(cls, data: AnyStr, **opts: Any) -> Any:


def to_json(
obj: Any, cls: Optional[Any] = None, se: Type[Serializer[str]] = JsonSerializer, **opts: Any
obj: Any,
cls: Optional[Any] = None,
se: Type[Serializer[str]] = JsonSerializer,
reuse_instances: bool = False,
convert_sets: bool = True,
**opts: Any,
) -> str:
"""
Serialize the object into JSON str. [orjson](https://github.com/ijl/orjson)
Expand All @@ -67,7 +72,9 @@ def to_json(
If you want to use another json package, you can subclass `JsonSerializer` and implement
your own logic.
"""
return se.serialize(to_dict(obj, c=cls, reuse_instances=False, convert_sets=True), **opts)
return se.serialize(
to_dict(obj, c=cls, reuse_instances=reuse_instances, convert_sets=convert_sets), **opts
)


@overload
Expand Down
4 changes: 3 additions & 1 deletion serde/msgpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def to_msgpack(
se: Type[Serializer[bytes]] = MsgPackSerializer,
named: bool = True,
ext_dict: Optional[Dict[Type[Any], int]] = None,
reuse_instances: bool = False,
convert_sets: bool = True,
**opts: Any,
) -> bytes:
"""
Expand All @@ -68,7 +70,7 @@ def to_msgpack(
if ext_type_code is None:
raise SerdeError(f"Could not find type code for {obj_type.__name__} in ext_dict")

kwargs: Any = {"c": cls, "reuse_instances": False, "convert_sets": True}
kwargs: Any = {"c": cls, "reuse_instances": reuse_instances, "convert_sets": convert_sets}
dict_or_tuple = to_dict(obj, **kwargs) if named else to_tuple(obj, **kwargs)
return se.serialize(
dict_or_tuple,
Expand Down
11 changes: 9 additions & 2 deletions serde/pickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,16 @@ def deserialize(cls, data: bytes, **opts: Any) -> Any:


def to_pickle(
obj: Any, cls: Optional[Any] = None, se: Type[Serializer[bytes]] = PickleSerializer, **opts: Any
obj: Any,
cls: Optional[Any] = None,
se: Type[Serializer[bytes]] = PickleSerializer,
reuse_instances: bool = False,
convert_sets: bool = True,
**opts: Any,
) -> bytes:
return se.serialize(to_dict(obj, c=cls, reuse_instances=False), **opts)
return se.serialize(
to_dict(obj, c=cls, reuse_instances=reuse_instances, convert_sets=convert_sets), **opts
)


@overload
Expand Down
10 changes: 5 additions & 5 deletions serde/se.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ def serializable_to_obj(object: Any) -> Any:
if is_dataclass_without_se(o):
serialize(type(o))
return serializable_to_obj(o)
if is_serializable(o):
elif is_serializable(o):
return serializable_to_obj(o)
elif isinstance(o, list):
return [thisfunc(e) for e in o]
Expand All @@ -378,10 +378,10 @@ def serializable_to_obj(object: Any) -> Any:
return [thisfunc(e) for e in o]
elif isinstance(o, dict):
return {k: thisfunc(v) for k, v in o.items()}
elif is_str_serializable_instance(o):
return str(o)
elif is_datetime_instance(o):
return o.isoformat()
elif is_str_serializable_instance(o) or is_datetime_instance(o):
return CACHE.serialize(
c or o.__class__, o, reuse_instances=reuse_instances, convert_sets=convert_sets
)

return o

Expand Down
11 changes: 9 additions & 2 deletions serde/toml.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ def deserialize(cls, data: str, **opts: Any) -> Any:


def to_toml(
obj: Any, cls: Optional[Any] = None, se: Type[Serializer[str]] = TomlSerializer, **opts: Any
obj: Any,
cls: Optional[Any] = None,
se: Type[Serializer[str]] = TomlSerializer,
reuse_instances: bool = False,
convert_sets: bool = True,
**opts: Any,
) -> str:
"""
Serialize the object into TOML.
Expand All @@ -45,7 +50,9 @@ def to_toml(
If you want to use the other toml package, you can subclass `TomlSerializer` and implement
your own logic.
"""
return se.serialize(to_dict(obj, c=cls, reuse_instances=False), **opts)
return se.serialize(
to_dict(obj, c=cls, reuse_instances=reuse_instances, convert_sets=convert_sets), **opts
)


@overload
Expand Down
11 changes: 9 additions & 2 deletions serde/yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ def deserialize(cls, data: str, **opts: Any) -> Any:


def to_yaml(
obj: Any, cls: Optional[Any] = None, se: Type[Serializer[str]] = YamlSerializer, **opts: Any
obj: Any,
cls: Optional[Any] = None,
se: Type[Serializer[str]] = YamlSerializer,
reuse_instances: bool = False,
convert_sets: bool = True,
**opts: Any,
) -> str:
"""
Serialize the object into YAML.
Expand All @@ -37,7 +42,9 @@ def to_yaml(
If you want to use the other yaml package, you can subclass `YamlSerializer` and implement
your own logic.
"""
return se.serialize(to_dict(obj, c=cls, reuse_instances=False), **opts)
return se.serialize(
to_dict(obj, c=cls, reuse_instances=reuse_instances, convert_sets=convert_sets), **opts
)


@overload
Expand Down
17 changes: 13 additions & 4 deletions tests/test_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import enum
import logging
import uuid
import datetime
from beartype.roar import BeartypeCallHintViolation
from typing import (
ClassVar,
Expand Down Expand Up @@ -111,6 +112,16 @@ class C:
assert c == de(C, se(c, reuse_instances=True), reuse_instances=True)


def test_non_dataclass_reuse_instances() -> None:
dt = datetime.datetime.fromisoformat("2020-01-01 00:00:00+00:00")
assert "2020-01-01T00:00:00+00:00" == serde.to_dict(dt, reuse_instances=False) # type: ignore
assert dt == serde.to_dict(dt, reuse_instances=True)
assert dt is serde.to_dict(dt, reuse_instances=True)
assert "2020-01-01T00:00:00+00:00" == serde.to_tuple(dt, reuse_instances=False) # type: ignore
assert dt == serde.to_dict(dt, reuse_instances=True)
assert dt is serde.to_dict(dt, reuse_instances=True)


def test_non_dataclass():
@serde.serde
class Foo:
Expand Down Expand Up @@ -480,10 +491,8 @@ class Foo:
class Foo:
v: Set[int]

# TODO: Should raise SerdeError
with pytest.raises(TypeError):
f = Foo({1, 2, 3})
serde.toml.to_toml(f)
f = Foo({1, 2, 3})
serde.toml.to_toml(f)


@pytest.mark.parametrize("se,de", all_formats)
Expand Down
Loading