Skip to content

Commit

Permalink
Merge pull request #342 from yukinarit/fix-recursive
Browse files Browse the repository at this point in the history
Fix dataclass with recursive containers
  • Loading branch information
yukinarit committed May 13, 2023
2 parents e22867b + eefcc1d commit e2c8740
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 18 deletions.
9 changes: 0 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,3 @@ repos:
"--skip-string-normalization",
]

- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
args: [
"--profile=black",
"--line-length=120",
"--atomic",
]
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Thank you for considering contributing to Pyserde!
- List your Python and Pyserde versions. If possible, check if this issue is already fixed in the repository.

## Submitting patches
- Pyserde uses Black and isort to autoformat your code. This should be done for you as a git pre-commit hook, which gets installed when you run `make setup` but you can do it manually via `make fmt`.
- Pyserde uses Black to autoformat your code. This should be done for you as a git pre-commit hook, which gets installed when you run `make setup` but you can do it manually via `make fmt`.
- Include tests if your patch is supposed to solve a bug, and explain clearly under which circumstances the bug happens. Make sure the test fails without your patch.
- Include a string like “Fixes #123” in your commit message (where 123 is the issue you fixed). See [Closing issues using keywords](https://help.github.com/articles/creating-a-pull-request/).

Expand Down
25 changes: 25 additions & 0 deletions examples/recursive_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from dataclasses import dataclass
from typing import List

from serde import serde
from serde.json import from_json, to_json


@dataclass
class Node:
name: str
children: List["Node"]


serde(Node)


def main() -> None:
n = Node("a", [Node("b", [Node("c", [])])])
s = to_json(n)
print(f"Into Json: {s}")
print(f"From Json: {from_json(Node, s)}")


if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions examples/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import plain_dataclass
import plain_dataclass_class_attribute
import recursive
import recursive_list
import rename
import rename_all
import simple
Expand Down Expand Up @@ -78,6 +79,7 @@ def run_all():
run(class_var)
run(alias)
run(recursive)
run(recursive_list)
run(class_var)
run(plain_dataclass)
run(plain_dataclass_class_attribute)
Expand Down
10 changes: 5 additions & 5 deletions serde/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,11 +353,6 @@ def recursive(cls: TypeLike) -> None:
args = type_args(cls)
if args:
recursive(args[0])
elif is_set(cls):
lst.add(Set)
args = type_args(cls)
if args:
recursive(args[0])
elif is_tuple(cls):
lst.add(Tuple)
for arg in type_args(cls):
Expand All @@ -380,18 +375,23 @@ def iter_unions(cls: TypeLike) -> List[TypeLike]:
Iterate over all unions that are used in the dataclass
"""
lst: Set[TypeLike] = set()
stack: List[TypeLike] = [] # To prevent infinite recursion

def recursive(cls: TypeLike) -> None:
if cls in lst:
return
if cls in stack:
return

if is_union(cls):
lst.add(cls)
for arg in type_args(cls):
recursive(arg)
if is_dataclass(cls):
stack.append(cls)
for f in dataclass_fields(cls):
recursive(f.type)
stack.pop()
elif is_opt(cls):
args = type_args(cls)
if args:
Expand Down
2 changes: 0 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ addopts = -v
max-line-length = 120
ignore = E252,W503
max-complexity = 30
[isort]
line_length = 120
[mypy]
ignore_missing_imports = True
strict = True
6 changes: 5 additions & 1 deletion tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from typing import Any, Callable, DefaultDict, Dict, FrozenSet, Generic, List, NewType, Optional, Set, Tuple, TypeVar

import more_itertools

from serde import from_dict, from_tuple, serde, to_dict, to_tuple
from serde.json import from_json, to_json
from serde.msgpack import from_msgpack, to_msgpack
Expand Down Expand Up @@ -92,6 +91,11 @@ def toml_not_supported(se, de, opt) -> bool:
param(data.Pri(10, 'foo', 100.0, True), Optional[data.Pri]),
param(None, Optional[data.Pri], toml_not_supported),
param(data.Recur(data.Recur(None, None, None), None, None), data.Recur, toml_not_supported),
param(
data.RecurContainer([data.RecurContainer([], {})], {"c": data.RecurContainer([], {})}),
data.RecurContainer,
toml_not_supported,
),
param(data.Init(1), data.Init),
param(10, NewType('Int', int)), # NewType
param({'a': 1}, Any), # Any
Expand Down
9 changes: 9 additions & 0 deletions tests/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,15 @@ class Recur:
serde(Recur)


@dataclass(unsafe_hash=True)
class RecurContainer:
a: List['RecurContainer']
b: Dict[str, 'RecurContainer']


serde(Recur)


ListPri = List[Pri]

DictPri = Dict[str, Pri]
Expand Down

0 comments on commit e2c8740

Please sign in to comment.