Skip to content

Commit 2489b41

Browse files
committed
clean up
1 parent c47463f commit 2489b41

2 files changed

Lines changed: 80 additions & 10 deletions

File tree

src/dataclass_extensions/decode.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import collections.abc
44
import dataclasses
55
import inspect
6-
import sys
76
import types
87
import typing
98
from datetime import datetime
@@ -15,11 +14,6 @@
1514
from .registrable import Registrable
1615
from .types import *
1716

18-
if sys.version_info >= (3, 11):
19-
from typing import Self # type: ignore
20-
else:
21-
from typing_extensions import Self # type: ignore
22-
2317
C = TypeVar("C", bound=Dataclass)
2418

2519

@@ -61,10 +55,15 @@ def _get_type_hints(obj: Any) -> dict[str, Any]:
6155

6256
def _resolve_type_hint(type_hint: Any, owner: Any) -> Any:
6357
if isinstance(type_hint, str):
58+
if not hasattr(typing_extensions, "evaluate_forward_ref"):
59+
raise ImportError(
60+
"evaluating string type hints (like forward references) "
61+
"requires a newer version of typing extensions"
62+
)
6463
type_hint = typing_extensions.evaluate_forward_ref( # type: ignore
6564
typing.ForwardRef(type_hint), owner=owner
6665
)
67-
elif type_hint is Self: # type: ignore
66+
elif type_hint is typing_extensions.Self or hasattr(typing, "Self") and type_hint is getattr(typing, "Self"): # type: ignore
6867
if inspect.isclass(owner):
6968
type_hint = owner
7069
else:
@@ -251,7 +250,7 @@ def _coerce(
251250
raise KeyError(
252251
f"type {allowed_type} has no field '{k}' (full key '{key}.{k}')"
253252
) from e
254-
kwargs[k] = _coerce(v, type_hint, custom_handlers, f"{key}.{k}", owner)
253+
kwargs[k] = _coerce(v, type_hint, custom_handlers, f"{key}.{k}", allowed_type)
255254
return allowed_type(**kwargs)
256255

257256
if Any in allowed_types:

src/test/decode_test.py

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ class Point2D(typing.TypedDict):
162162
],
163163
)
164164
def test_coerce_from_type_hints(value: Any, type_hint: Any, expected: Any):
165-
result = _coerce(value, type_hint, {}, "0", value)
165+
result = _coerce(value, type_hint, {}, "0", type(value))
166166
assert type(result) is type(expected)
167167
assert result == expected
168168

@@ -545,6 +545,30 @@ def test_recursive_decode():
545545
assert x.children[0].children[0].name == "l3"
546546

547547

548+
def test_nested_recursive_decode():
549+
@dataclass
550+
class Config:
551+
x: RecursiveType
552+
553+
c = decode(
554+
Config,
555+
{
556+
"x": {
557+
"name": "l1",
558+
"children": [{"name": "l2", "children": [{"name": "l3", "children": None}]}],
559+
}
560+
},
561+
)
562+
assert isinstance(c, Config)
563+
assert c.x.name == "l1"
564+
assert c.x.children is not None
565+
assert isinstance(c.x.children[0], RecursiveType)
566+
assert c.x.children[0].name == "l2"
567+
assert c.x.children[0].children is not None
568+
assert isinstance(c.x.children[0].children[0], RecursiveType)
569+
assert c.x.children[0].children[0].name == "l3"
570+
571+
548572
@dataclass
549573
class RecursiveType2:
550574
name: str
@@ -568,13 +592,36 @@ def test_recursive_decode_with_str_forward_reference():
568592
assert x.children[0].children[0].name == "l3"
569593

570594

595+
def test_nested_recursive_decode_with_str_forward_reference():
596+
@dataclass
597+
class Config:
598+
x: RecursiveType2
599+
600+
c = decode(
601+
Config,
602+
{
603+
"x": {
604+
"name": "l1",
605+
"children": [{"name": "l2", "children": [{"name": "l3", "children": None}]}],
606+
}
607+
},
608+
)
609+
assert isinstance(c, Config)
610+
assert c.x.name == "l1"
611+
assert c.x.children is not None
612+
assert isinstance(c.x.children[0], RecursiveType2)
613+
assert c.x.children[0].name == "l2"
614+
assert c.x.children[0].children is not None
615+
assert isinstance(c.x.children[0].children[0], RecursiveType2)
616+
assert c.x.children[0].children[0].name == "l3"
617+
618+
571619
@dataclass
572620
class RecursiveTypeWithSelf:
573621
name: str
574622
children: list[Self] | None
575623

576624

577-
# @pytest.mark.xfail
578625
def test_recursive_decode_with_self_type():
579626
x = decode(
580627
RecursiveTypeWithSelf,
@@ -590,3 +637,27 @@ def test_recursive_decode_with_self_type():
590637
assert x.children[0].children is not None
591638
assert isinstance(x.children[0].children[0], RecursiveTypeWithSelf)
592639
assert x.children[0].children[0].name == "l3"
640+
641+
642+
def test_nested_recursive_decode_with_self_type():
643+
@dataclass
644+
class Config:
645+
x: RecursiveTypeWithSelf
646+
647+
c = decode(
648+
Config,
649+
{
650+
"x": {
651+
"name": "l1",
652+
"children": [{"name": "l2", "children": [{"name": "l3", "children": None}]}],
653+
}
654+
},
655+
)
656+
assert isinstance(c, Config)
657+
assert c.x.name == "l1"
658+
assert c.x.children is not None
659+
assert isinstance(c.x.children[0], RecursiveTypeWithSelf)
660+
assert c.x.children[0].name == "l2"
661+
assert c.x.children[0].children is not None
662+
assert isinstance(c.x.children[0].children[0], RecursiveTypeWithSelf)
663+
assert c.x.children[0].children[0].name == "l3"

0 commit comments

Comments
 (0)