Skip to content

Commit c9b8e71

Browse files
committed
Handle older Python versions
1 parent 71a093d commit c9b8e71

File tree

3 files changed

+35
-30
lines changed

3 files changed

+35
-30
lines changed

dacite/core.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
from dataclasses import is_dataclass
22
from itertools import zip_longest
3-
from typing import TypeVar, Type, Optional, get_type_hints, Mapping, Any, Collection, MutableMapping, get_origin
3+
from typing import TypeVar, Type, Optional, get_type_hints, Mapping, Any, Collection, MutableMapping
4+
5+
try:
6+
from typing import get_origin
7+
except ImportError:
8+
from typing_extensions import get_origin
49

510
from dacite.cache import cache
611
from dacite.config import Config
@@ -99,7 +104,9 @@ def _build_value(type_: Type, data: Any, config: Config) -> Any:
99104
elif cache(is_dataclass)(type_) and isinstance(data, Mapping):
100105
data = from_dict(data_class=type_, data=data, config=config)
101106
elif is_generic_subclass(type_) and is_dataclass(get_origin(type_)):
102-
data = from_dict(data_class=get_origin(type_), data=data, config=config)
107+
origin = get_origin(type_)
108+
assert origin is not None
109+
data = from_dict(data_class=origin, data=data, config=config)
103110
for cast_type in config.cast:
104111
if is_subclass(type_, cast_type):
105112
if is_generic_collection(type_):

dacite/types.py

+25-27
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
11
from dataclasses import InitVar
2-
from typing import (
3-
Type,
4-
Any,
5-
Optional,
6-
Union,
7-
Collection,
8-
TypeVar,
9-
Mapping,
10-
Tuple,
11-
get_origin,
12-
get_type_hints,
13-
get_args,
14-
cast as typing_cast,
15-
_GenericAlias, # Remove import and check for Generic in a different way
16-
)
2+
from typing import Type, Any, Optional, Union, Collection, TypeVar, Mapping, Tuple, get_type_hints, cast as typing_cast
3+
4+
try:
5+
from typing import get_origin, get_args
6+
except ImportError:
7+
from typing_extensions import get_origin, get_args
178
from inspect import isclass
189

1910
from dacite.cache import cache
@@ -53,14 +44,9 @@ def is_generic_subclass(type_: Type) -> bool:
5344
return is_generic(type_) and hasattr(type_, "__args__")
5445

5546

56-
@cache
57-
def is_generic_alias(type_: Type) -> bool:
58-
return type(type_.__args__) == _GenericAlias
59-
60-
6147
@cache
6248
def is_union(type_: Type) -> bool:
63-
if is_generic(type_) and type_.__origin__ == Union:
49+
if is_generic(type_) and get_origin(type_) == Union:
6450
return True
6551

6652
try:
@@ -81,7 +67,7 @@ def is_literal(type_: Type) -> bool:
8167
try:
8268
from typing import Literal # type: ignore
8369

84-
return is_generic(type_) and type_.__origin__ == Literal
70+
return is_generic(type_) and get_origin(type_) == Literal
8571
except ImportError:
8672
return False
8773

@@ -101,10 +87,22 @@ def is_init_var(type_: Type) -> bool:
10187
return isinstance(type_, InitVar) or type_ is InitVar
10288

10389

90+
@cache
91+
def is_generic_alias(type_: Type) -> bool:
92+
"""Since `typing._GenericAlias` is not explicitly exported, we instead rely on this check."""
93+
return str(type_) == "<class 'typing._GenericAlias'>"
94+
95+
96+
@cache
97+
def has_generic_alias_in_args(type_: Type) -> bool:
98+
return is_generic_alias(type(get_args(type_)))
99+
100+
104101
def is_valid_generic_class(value: Any, type_: Type) -> bool:
105-
if not isinstance(value, get_origin(type_)):
102+
origin = get_origin(type_)
103+
if not (origin and isinstance(value, origin)):
106104
return False
107-
type_hints = get_type_hints(value)
105+
type_hints = get_type_hints(type(value))
108106
for field_name, field_type in type_hints.items():
109107
if isinstance(field_type, TypeVar):
110108
return (
@@ -165,7 +163,7 @@ def is_instance(value: Any, type_: Type) -> bool:
165163
return value in extract_generic(type_)
166164
elif is_init_var(type_):
167165
return is_instance(value, extract_init_var(type_))
168-
elif isclass(type(type_)) and type(type_) == _GenericAlias:
166+
elif isclass(type(type_)) and is_generic_alias(type(type_)):
169167
return is_valid_generic_class(value, type_)
170168
elif isinstance(type_, TypeVar):
171169
if hasattr(type_, "__constraints__") and type_.__constraints__:
@@ -174,7 +172,7 @@ def is_instance(value: Any, type_: Type) -> bool:
174172
if isinstance(type_.__bound__, tuple):
175173
return any(is_instance(value, t) for t in type_.__bound__)
176174
if type_.__bound__ is not None and is_generic(type_.__bound__):
177-
return isinstance(value, type_.__bound__)
175+
return isinstance(value, extract_generic(type_.__bound__))
178176
return True
179177
elif is_type_generic(type_):
180178
return is_subclass(value, extract_generic(type_)[0])
@@ -218,6 +216,6 @@ def is_subclass(sub_type: Type, base_type: Type) -> bool:
218216
@cache
219217
def is_type_generic(type_: Type) -> bool:
220218
try:
221-
return type_.__origin__ in (type, Type)
219+
return get_origin(type_) in (type, Type)
222220
except AttributeError:
223221
return False

tests/core/test_base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ class X:
193193
assert result == X(s=MyStr("test"))
194194

195195

196-
def test_from_dict_generic():
196+
def test_from_dict_generic_valid():
197197
T = TypeVar("T", bound=Union[str, int])
198198

199199
@dataclass

0 commit comments

Comments
 (0)