diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1a9b25ce..2508f187 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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", - ] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 14a2a6e0..cb9b7d94 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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/). diff --git a/examples/recursive_list.py b/examples/recursive_list.py new file mode 100644 index 00000000..d994bf0b --- /dev/null +++ b/examples/recursive_list.py @@ -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() diff --git a/examples/runner.py b/examples/runner.py index f2a3fbcc..dd65abfe 100644 --- a/examples/runner.py +++ b/examples/runner.py @@ -24,6 +24,7 @@ import plain_dataclass import plain_dataclass_class_attribute import recursive +import recursive_list import rename import rename_all import simple @@ -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) diff --git a/serde/compat.py b/serde/compat.py index da0d5bfc..09a95620 100644 --- a/serde/compat.py +++ b/serde/compat.py @@ -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): @@ -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: diff --git a/setup.cfg b/setup.cfg index 42c3def7..f2d5de13 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 diff --git a/tests/common.py b/tests/common.py index 07ffabcd..156c73bb 100644 --- a/tests/common.py +++ b/tests/common.py @@ -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 @@ -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 diff --git a/tests/data.py b/tests/data.py index d9581004..31ef2ff4 100644 --- a/tests/data.py +++ b/tests/data.py @@ -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]