Skip to content

Commit e2c8740

Browse files
authored
Merge pull request #342 from yukinarit/fix-recursive
Fix dataclass with recursive containers
2 parents e22867b + eefcc1d commit e2c8740

File tree

8 files changed

+47
-18
lines changed

8 files changed

+47
-18
lines changed

.pre-commit-config.yaml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,3 @@ repos:
1919
"--skip-string-normalization",
2020
]
2121

22-
- repo: https://github.com/PyCQA/isort
23-
rev: 5.12.0
24-
hooks:
25-
- id: isort
26-
args: [
27-
"--profile=black",
28-
"--line-length=120",
29-
"--atomic",
30-
]

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Thank you for considering contributing to Pyserde!
99
- List your Python and Pyserde versions. If possible, check if this issue is already fixed in the repository.
1010

1111
## Submitting patches
12-
- 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`.
12+
- 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`.
1313
- 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.
1414
- 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/).
1515

examples/recursive_list.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from dataclasses import dataclass
2+
from typing import List
3+
4+
from serde import serde
5+
from serde.json import from_json, to_json
6+
7+
8+
@dataclass
9+
class Node:
10+
name: str
11+
children: List["Node"]
12+
13+
14+
serde(Node)
15+
16+
17+
def main() -> None:
18+
n = Node("a", [Node("b", [Node("c", [])])])
19+
s = to_json(n)
20+
print(f"Into Json: {s}")
21+
print(f"From Json: {from_json(Node, s)}")
22+
23+
24+
if __name__ == "__main__":
25+
main()

examples/runner.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import plain_dataclass
2525
import plain_dataclass_class_attribute
2626
import recursive
27+
import recursive_list
2728
import rename
2829
import rename_all
2930
import simple
@@ -78,6 +79,7 @@ def run_all():
7879
run(class_var)
7980
run(alias)
8081
run(recursive)
82+
run(recursive_list)
8183
run(class_var)
8284
run(plain_dataclass)
8385
run(plain_dataclass_class_attribute)

serde/compat.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -353,11 +353,6 @@ def recursive(cls: TypeLike) -> None:
353353
args = type_args(cls)
354354
if args:
355355
recursive(args[0])
356-
elif is_set(cls):
357-
lst.add(Set)
358-
args = type_args(cls)
359-
if args:
360-
recursive(args[0])
361356
elif is_tuple(cls):
362357
lst.add(Tuple)
363358
for arg in type_args(cls):
@@ -380,18 +375,23 @@ def iter_unions(cls: TypeLike) -> List[TypeLike]:
380375
Iterate over all unions that are used in the dataclass
381376
"""
382377
lst: Set[TypeLike] = set()
378+
stack: List[TypeLike] = [] # To prevent infinite recursion
383379

384380
def recursive(cls: TypeLike) -> None:
385381
if cls in lst:
386382
return
383+
if cls in stack:
384+
return
387385

388386
if is_union(cls):
389387
lst.add(cls)
390388
for arg in type_args(cls):
391389
recursive(arg)
392390
if is_dataclass(cls):
391+
stack.append(cls)
393392
for f in dataclass_fields(cls):
394393
recursive(f.type)
394+
stack.pop()
395395
elif is_opt(cls):
396396
args = type_args(cls)
397397
if args:

setup.cfg

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ addopts = -v
66
max-line-length = 120
77
ignore = E252,W503
88
max-complexity = 30
9-
[isort]
10-
line_length = 120
119
[mypy]
1210
ignore_missing_imports = True
1311
strict = True

tests/common.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from typing import Any, Callable, DefaultDict, Dict, FrozenSet, Generic, List, NewType, Optional, Set, Tuple, TypeVar
1010

1111
import more_itertools
12-
1312
from serde import from_dict, from_tuple, serde, to_dict, to_tuple
1413
from serde.json import from_json, to_json
1514
from serde.msgpack import from_msgpack, to_msgpack
@@ -92,6 +91,11 @@ def toml_not_supported(se, de, opt) -> bool:
9291
param(data.Pri(10, 'foo', 100.0, True), Optional[data.Pri]),
9392
param(None, Optional[data.Pri], toml_not_supported),
9493
param(data.Recur(data.Recur(None, None, None), None, None), data.Recur, toml_not_supported),
94+
param(
95+
data.RecurContainer([data.RecurContainer([], {})], {"c": data.RecurContainer([], {})}),
96+
data.RecurContainer,
97+
toml_not_supported,
98+
),
9599
param(data.Init(1), data.Init),
96100
param(10, NewType('Int', int)), # NewType
97101
param({'a': 1}, Any), # Any

tests/data.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,15 @@ class Recur:
236236
serde(Recur)
237237

238238

239+
@dataclass(unsafe_hash=True)
240+
class RecurContainer:
241+
a: List['RecurContainer']
242+
b: Dict[str, 'RecurContainer']
243+
244+
245+
serde(Recur)
246+
247+
239248
ListPri = List[Pri]
240249

241250
DictPri = Dict[str, Pri]

0 commit comments

Comments
 (0)