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

feat: Allow recursive types #290

Merged
merged 1 commit into from
Dec 5, 2022
Merged

feat: Allow recursive types #290

merged 1 commit into from
Dec 5, 2022

Conversation

yukinarit
Copy link
Owner

Like this.

@dataclass
class Foo:
    f: Optional['Foo']

serde(Foo)

Close #287

@yukinarit
Copy link
Owner Author

@masinc

Tried to support recursive data structures. Can you please confirm it works on your code?
I will merge after I add enough tests.

@masinc
Copy link

masinc commented Dec 2, 2022

@yukinarit
I tried example/recursive.py first because it didn't work with my code.
Serialization worked by setting the argument of to_dict to f instead of Foo.
Deserialization did not work well.

Output

Traceback (most recent call last):
  File "/home/masinc/sandbox/pyserde/examples/.venv/lib/python3.10/site-packages/serde/de.py", line 354, in from_obj
    res = serde_scope.funcs[func_name](c, maybe_generic=maybe_generic, data=o, reuse_instances=reuse_instances)
  File "<string>", line 11, in from_dict
NameError: name 'Foo' is not defined

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/masinc/sandbox/pyserde/examples/recursive.py", line 17, in <module>
    print(from_dict(Foo, {'f': {'f': {'f': None}}}))
  File "/home/masinc/sandbox/pyserde/examples/.venv/lib/python3.10/site-packages/serde/de.py", line 441, in from_dict
    return from_obj(cls, o, named=True, reuse_instances=reuse_instances)
  File "/home/masinc/sandbox/pyserde/examples/.venv/lib/python3.10/site-packages/serde/de.py", line 418, in from_obj
    raise SerdeError(e)
serde.compat.SerdeError: name 'Foo' is not defined

Specifically, it works well with non-recursive data, but seems to raise errors when the data is recursive.

from_dict(Foo, {'f': None}) # No Erorr
from_dict(Foo, {'f': {'f': None}}) # Error

@yukinarit yukinarit force-pushed the allow-recursive branch 2 times, most recently from 84e9783 to 3ff1cde Compare December 4, 2022 10:13
Like this.
```
@DataClass
class Foo:
    f: Optional['Foo']

serde(Foo)
```
@codecov
Copy link

codecov bot commented Dec 4, 2022

Codecov Report

Base: 90.77% // Head: 90.82% // Increases project coverage by +0.04% 🎉

Coverage data is based on head (debbe15) compared to base (e3f5c47).
Patch coverage: 89.91% of modified lines in pull request are covered.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #290      +/-   ##
==========================================
+ Coverage   90.77%   90.82%   +0.04%     
==========================================
  Files          11       11              
  Lines        1648     1678      +30     
  Branches      349      355       +6     
==========================================
+ Hits         1496     1524      +28     
- Misses        109      110       +1     
- Partials       43       44       +1     
Impacted Files Coverage Δ
serde/core.py 91.42% <77.77%> (-0.39%) ⬇️
serde/compat.py 83.24% <90.56%> (+0.91%) ⬆️
serde/de.py 97.46% <100.00%> (+0.01%) ⬆️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@yukinarit
Copy link
Owner Author

@masinc Can you try it again? Should be ok now 👍

@yukinarit yukinarit marked this pull request as ready for review December 4, 2022 11:53
@masinc
Copy link

masinc commented Dec 5, 2022

I tried a pattern that I think is commonly used in recursive data structures (JsonSchemaLike) and a complex pattern (Item*) that I extracted from my data structure.

from dataclasses import dataclass
from typing import Optional, Union, Dict, List, Literal
from serde import serde, Untagged, Strict, field, from_dict, to_dict

@dataclass
class JsonSchemaLike:
    id: Optional[str] = field(rename="$id")
    items: Optional[List["JsonSchemaLike"]] = field()
    properties: Optional[Dict[str, "JsonSchemaLike"]] = field()

serde(JsonSchemaLike, type_check=Strict)

print(
    to_dict(
        JsonSchemaLike(
            None,
            items=[
                JsonSchemaLike(
                    None,
                    items=None,
                    properties={
                        "a": JsonSchemaLike(
                            "a",
                            items=[
                                JsonSchemaLike("a1", None, None),
                                JsonSchemaLike("a2", None, None),
                            ],
                            properties={
                                "aa": JsonSchemaLike("aa", None, None),
                                "ab": JsonSchemaLike("ab", None, None),
                            },
                        ),
                        "b": JsonSchemaLike("b", None, None),
                    },
                )
            ],
            properties=None,
        )
    )
)

print(
    from_dict(
        JsonSchemaLike,
        {
            "items": [
                {
                    "properties": {
                        "a": {
                            "$id": "a",
                            "items": [{"$id": "a1"}, {"$id": "a2"}],
                            "properties": {"aa": {"$id": "aa"}, "ab": {"$id": "ab"}},
                        },
                        "b": {"$id": "b"},
                    }
                }
            ]
        },
    )
)


@dataclass
class ItemA:
    type_: Literal["A"] = field(rename="type")
    children: Optional[List[Union["ItemA", "ItemB", "ItemC"]]] = field()


@dataclass
class ItemB:
    type_: Literal["B"] = field(rename="type")
    children: Optional[List[Union["ItemA", "ItemB", "ItemC"]]] = field()


@dataclass
class ItemC:
    type_: Literal["C"] = field(rename="type")


serde(ItemA, type_check=Strict, tagging=Untagged)
serde(ItemB, type_check=Strict, tagging=Untagged)
serde(ItemC, type_check=Strict, tagging=Untagged)


print(to_dict(ItemA("A", [ItemB("B", [ItemC("C"), ItemA("A", [ItemB("B", None)])])])))

print(
    from_dict(
        Union[ItemA, ItemB, ItemC],
        {
            "type": "A",
            "children": [
                {
                    "type": "B",
                    "children": [
                        {"type": "C"},
                        {"type": "A", "children": [{"type": "B"}]},
                    ],
                }
            ],
        },
    ),
)

it seems good!

@yukinarit
Copy link
Owner Author

Awesome 🙂

@yukinarit yukinarit merged commit f6541e7 into master Dec 5, 2022
@yukinarit yukinarit deleted the allow-recursive branch December 5, 2022 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for recursive data?
2 participants