1
1
import typing
2
- from typing import Any , Dict , List , Mapping
2
+ from typing import Any , Dict , List , Mapping , Set
3
3
4
4
from .exceptions import InvalidRequest
5
5
@@ -11,12 +11,17 @@ def recast_object(
11
11
) -> None :
12
12
if not isinstance (json_data , dict ):
13
13
raise InvalidRequest (f"Can only parse dict items, not { type (json_data )} " )
14
+ # if type is Any, we leave it as is
15
+ if cls == typing .Any :
16
+ return
14
17
for k , v in json_data .items ():
15
18
if isinstance (v , dict ):
16
19
child_cls = _field_to_type (cls .__dataclass_fields__ [k ].type , k , classes )
17
20
recast_object (child_cls , v , classes )
18
21
elif isinstance (v , list ):
19
22
json_data [k ] = _recast_lists (cls , k , v , classes )
23
+ elif isinstance (v , set ):
24
+ json_data [k ] = _recast_sets (cls , k , v , classes )
20
25
elif isinstance (v , str ):
21
26
dest_type = _field_to_type (cls .__dataclass_fields__ [k ].type , k , classes )
22
27
json_data [k ] = _recast_primitive (dest_type , k , v )
@@ -25,24 +30,46 @@ def recast_object(
25
30
26
31
27
32
def _recast_lists (cls : Any , k : str , v : List [Any ], classes : Dict [str , Any ]) -> List [Any ]:
33
+ # Leave as is if type is Any
34
+ if cls == typing .Any :
35
+ return v
28
36
casted_list : List [Any ] = []
29
- if k in cls .__dataclass_fields__ :
37
+ if "__dataclass_fields__" not in dir (cls ):
38
+ pass
39
+ elif k in cls .__dataclass_fields__ :
30
40
cls = _field_to_type (cls .__dataclass_fields__ [k ].type , k , classes )
31
41
for item in v :
32
- if isinstance (item , str ):
33
- casted_item : Any = _recast_primitive (cls , k , item )
34
- elif isinstance (item , list ):
35
- casted_item = _recast_lists (cls , k , item , classes )
36
- elif isinstance (item , dict ):
37
- recast_object (cls , item , classes )
38
- casted_item = item
39
- else :
40
- raise InvalidRequest (f"Unsupported type: { type (v )} for { k } " )
41
- casted_list .append (casted_item )
42
+ casted_list .append (cast_sequence_item (cls , k , item , classes ))
42
43
return casted_list
43
44
44
45
46
+ def _recast_sets (cls : Any , k : str , v : Set [Any ], classes : Dict [str , Any ]) -> Set [Any ]:
47
+ casted_set : Set [Any ] = set ()
48
+ if "__dataclass_fields__" in dir (cls ):
49
+ cls = _field_to_type (cls .__dataclass_fields__ [k ].type , k , classes )
50
+ for item in v :
51
+ casted_set .add (cast_sequence_item (cls , k , item , classes ))
52
+ return casted_set
53
+
54
+
55
+ def cast_sequence_item (cls : Any , k : str , item : Any , classes : Dict [str , Any ]) -> Any :
56
+ if isinstance (item , str ):
57
+ return _recast_primitive (cls , k , item )
58
+ if isinstance (item , list ):
59
+ return _recast_lists (cls , k , item , classes )
60
+ if isinstance (item , set ):
61
+ return _recast_sets (cls , k , item , classes )
62
+ if isinstance (item , dict ):
63
+ recast_object (cls , item , classes )
64
+ return item
65
+ raise InvalidRequest (f"Unsupported type: { type (item )} for { k } " )
66
+
67
+
45
68
def _recast_primitive (cls : Any , k : str , v : str ) -> Any :
69
+ if cls == typing .Any :
70
+ # If the type is Any, we cannot guess what the original type was, so we leave
71
+ # it as a string
72
+ return v
46
73
if cls == bool :
47
74
if v .lower () == "true" :
48
75
return True
@@ -53,7 +80,7 @@ def _recast_primitive(cls: Any, k: str, v: str) -> Any:
53
80
54
81
55
82
def _field_to_type (field : Any , key : str , classes : Dict [str , Any ]) -> Any :
56
- if field in [int , float , str , bool ]:
83
+ if field in [int , float , str , bool , typing . Any ]:
57
84
return field
58
85
# If it's a ForwardRef we need to find base type
59
86
if isinstance (field , get_forward_ref_type ()):
@@ -64,26 +91,31 @@ def _field_to_type(field: Any, key: str, classes: Dict[str, Any]) -> Any:
64
91
try :
65
92
possible_types = field .__args__
66
93
except AttributeError :
67
- raise InvalidRequest (f"Cannot process type { field . __repr__ () } for field { key } " )
94
+ raise InvalidRequest (f"Cannot process type { field } for field { key } " )
68
95
# Assuming that the union is generated from typing.Optional, so only
69
96
# contains one type and None
70
97
# pylint: disable=unidiomatic-typecheck
71
- fields = [t for t in possible_types if type (None ) != t ]
98
+ fields = [t for t in possible_types if type (None ) != t ] if possible_types else []
72
99
if len (fields ) != 1 :
73
- raise InvalidRequest (f"Cannot process type { field . __repr__ () } for field { key } " )
100
+ raise InvalidRequest (f"Cannot process type { field } for field { key } " )
74
101
field = fields [0 ]
75
102
# If it's a primitive we're done
76
- if field in [int , float , str , bool ]:
103
+ if field in [int , float , str , bool , typing . Any ]:
77
104
return field
78
105
# If it's a ForwardRef we need to find base type
79
106
if isinstance (field , get_forward_ref_type ()):
80
107
# Assuming codegen added an _ as a prefix, removing it and then getting the
81
108
# class from model classes
82
109
return classes [field .__forward_arg__ [1 :]]
83
- # If it's not a type we don't know how to handle we bail
84
- if not str (field ).startswith ("typing.Sequence" ):
85
- raise InvalidRequest (f"Cannot process type { field } for field { key } " )
86
- return _field_to_type (field .__args__ [0 ], key , classes )
110
+ # reduce Sequence/AbstractSet to inner type
111
+ if str (field ).startswith ("typing.Sequence" ) or str (field ).startswith (
112
+ "typing.AbstractSet"
113
+ ):
114
+ return _field_to_type (field .__args__ [0 ], key , classes )
115
+ if str (field ).startswith ("typing.MutableMapping" ):
116
+ return _field_to_type (field .__args__ [1 ], key , classes )
117
+ # If it's a type we don't know how to handle, we bail
118
+ raise InvalidRequest (f"Cannot process type { field } for field { key } " )
87
119
88
120
89
121
# pylint: disable=protected-access,no-member
0 commit comments