Skip to content

Commit cab048a

Browse files
authored
Merge pull request yukinarit#122 from yukinarit/support-typing-any
feat: Support typing.any
2 parents acf5264 + 988a621 commit cab048a

File tree

7 files changed

+64
-4
lines changed

7 files changed

+64
-4
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ That's it! pyserde offers many more features. If you're interested, please read
147147
* [`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)
148148
* User defined class with [`@dataclass`](https://docs.python.org/3/library/dataclasses.html)
149149
* [`typing.NewType`](https://docs.python.org/3/library/typing.html#newtype) for primitive types
150+
* [`typing.Any`](https://docs.python.org/3/library/typing.html#the-any-type)
150151
* [Enum](https://docs.python.org/3/library/enum.html#enum.Enum) and [IntEnum](https://docs.python.org/3/library/enum.html#enum.IntEnum)
151152
* More types
152153
* [`pathlib.Path`](https://docs.python.org/3/library/pathlib.html)

examples/any.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from dataclasses import dataclass
2+
from typing import Any
3+
4+
from serde import deserialize, serialize
5+
from serde.json import from_json, to_json
6+
7+
8+
@deserialize
9+
@serialize
10+
@dataclass
11+
class Bar:
12+
v: float
13+
14+
15+
@deserialize
16+
@serialize
17+
@dataclass
18+
class Foo:
19+
a: Any
20+
b: Any
21+
c: Any
22+
d: Any
23+
24+
25+
def main():
26+
# Bar is serialized into dict.
27+
f = Foo(a=10, b=[1, 2], c={'foo': 'bar'}, d=Bar(100.0))
28+
print(f"Into Json: {to_json(f)}")
29+
30+
# However, pyserde can't deserialize the dict into Bar.
31+
# This is because there is no "Bar" annotation in "Foo" class.
32+
s = '{"a": 10, "b": [1, 2], "c": {"foo": "bar"}, "d": {"v": 100.0}}'
33+
print(f"From Json: {from_json(Foo, s)}")
34+
35+
36+
if __name__ == '__main__':
37+
main()

examples/runner.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import sys
22

3+
import any
34
import collection
45
import default
56
import env
@@ -15,6 +16,7 @@
1516

1617

1718
def run_all():
19+
run(any)
1820
run(simple)
1921
run(newtype)
2022
run(collection)

serde/compat.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import typing
77
from dataclasses import fields, is_dataclass
88
from itertools import zip_longest
9-
from typing import Dict, Iterator, List, Optional, Set, Tuple, Type, TypeVar, Union
9+
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Type, TypeVar, Union
1010

1111
import typing_inspect
1212

@@ -37,7 +37,7 @@ def get_args(typ):
3737

3838
def typename(typ) -> str:
3939
"""
40-
>>> from typing import List, Dict, Set
40+
>>> from typing import List, Dict, Set, Any
4141
>>> typename(int)
4242
'int'
4343
>>> class Foo: pass
@@ -55,6 +55,8 @@ def typename(typ) -> str:
5555
'Union[Optional[Foo], List[Foo], str, int]'
5656
>>> typename(Set[Foo])
5757
'Set[Foo]'
58+
>>> typename(Any)
59+
'Any'
5860
"""
5961
if is_opt(typ):
6062
return f'Optional[{typename(type_args(typ)[0])}]'
@@ -84,8 +86,14 @@ def typename(typ) -> str:
8486
return 'Dict'
8587
elif is_tuple(typ):
8688
return f'Tuple[{", ".join([typename(e) for e in type_args(typ)])}]'
89+
elif typ is Any:
90+
return 'Any'
8791
else:
88-
return typ.__name__
92+
name = getattr(typ, '_name', None)
93+
if name:
94+
return name
95+
else:
96+
return typ.__name__
8997

9098

9199
def type_args(typ):

serde/de.py

+5
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ def wrap(cls: Type):
125125
if typ is cls:
126126
continue
127127

128+
if typ is Any:
129+
continue
130+
128131
if is_dataclass(typ) or is_enum(typ) or not is_primitive(typ):
129132
scope.types[typ.__name__] = typ
130133

@@ -413,6 +416,8 @@ def render(self, arg: DeField) -> str:
413416
res = f"({arg.data} if isinstance({arg.data}, {arg.type.__name__}) else {from_iso}) if reuse_instances else {from_iso}"
414417
elif is_none(arg.type):
415418
res = "None"
419+
elif arg.type is Any:
420+
res = arg.data
416421
else:
417422
return f"raise_unsupported_type({arg.data})"
418423

serde/se.py

+6
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,16 @@ def wrap(cls: Type):
130130
g['is_dataclass'] = is_dataclass
131131
g['typename'] = typename # used in union functions
132132
g['is_instance'] = is_instance # used in union functions
133+
g['to_obj'] = to_obj
133134

134135
# Collect types used in the generated code.
135136
for typ in iter_types(cls):
136137
if typ is cls:
137138
continue
138139

140+
if typ is Any:
141+
continue
142+
139143
if is_dataclass(typ) or is_enum(typ) or not is_primitive(typ):
140144
scope.types[typ.__name__] = typ
141145

@@ -473,6 +477,8 @@ def render(self, arg: SeField) -> str:
473477
return f"{arg.varname} if reuse_instances else {arg.varname}.isoformat()"
474478
elif is_none(arg.type):
475479
return "None"
480+
elif arg.type is Any:
481+
return f"to_obj({arg.varname}, True, False, False)"
476482
else:
477483
return f"raise_unsupported_type({arg.varname})"
478484

tests/test_basics.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import uuid
1111
from dataclasses import dataclass, field
1212
from datetime import date, datetime
13-
from typing import Dict, List, NewType, Optional, Set, Tuple
13+
from typing import Any, Dict, List, NewType, Optional, Set, Tuple
1414

1515
import more_itertools
1616
import pytest
@@ -75,6 +75,7 @@
7575
(Pri(10, 'foo', 100.0, True), Optional[Pri]),
7676
(None, Optional[Pri]),
7777
(10, NewType('Int', int)), # NewType
78+
({'a': 1}, Any), # Any
7879
(pathlib.Path('/tmp/foo'), pathlib.Path), # Extended types
7980
(pathlib.Path('/tmp/foo'), Optional[pathlib.Path]),
8081
(None, Optional[pathlib.Path]),

0 commit comments

Comments
 (0)