9
9
import requests
10
10
from requests .exceptions import HTTPError
11
11
from requests .models import Response
12
- from stringcase import camelcase , snakecase
12
+ from stringcase import camelcase , snakecase , uppercase
13
13
14
14
from servicestack .dtos import *
15
15
from servicestack .fields import *
@@ -29,17 +29,65 @@ def _dump(obj):
29
29
print ("" )
30
30
31
31
32
+ def _get_type_vars_map (cls : Type , type_map : Dict [Union [type , TypeVar ], type ] = {}):
33
+ if hasattr (cls , '__orig_bases__' ):
34
+ for base_cls in cls .__orig_bases__ :
35
+ _get_type_vars_map (base_cls , type_map )
36
+ generic_def = get_origin (cls )
37
+ if generic_def is not None :
38
+ generic_type_args = get_args (cls )
39
+ i = 0
40
+ for t in generic_def .__parameters__ :
41
+ type_map [t ] = generic_type_args [i ]
42
+ i += 1
43
+ return type_map
44
+
45
+
46
+ def _dict_with_string_keys (d : dict ):
47
+ to = {}
48
+ for k , v in d .items ():
49
+ to [f"{ k } " ] = v
50
+ return to
51
+
52
+
53
+ def has_type_vars (cls : Type ):
54
+ if cls is None :
55
+ return None
56
+ return isinstance (cls , TypeVar ) or any (isinstance (x , TypeVar ) for x in cls .__args__ )
57
+
58
+
32
59
def _resolve_response_type (request ):
33
- if isinstance (request , IReturn ):
34
- for cls in request .__orig_bases__ :
35
- if hasattr (cls , '__args__' ):
36
- candidate = cls .__args__ [0 ]
37
- if type (candidate ) == ForwardRef :
38
- return _resolve_forwardref (candidate , type (request ))
39
- return candidate
40
- if isinstance (request , IReturnVoid ):
41
- return type (None )
42
- return None
60
+ t = type (request )
61
+
62
+ def resolve_response_type ():
63
+ if isinstance (request , IReturn ):
64
+ for cls in t .__orig_bases__ :
65
+ if get_origin (cls ) == IReturn and hasattr (cls , '__args__' ):
66
+ candidate = cls .__args__ [0 ]
67
+ if type (candidate ) == ForwardRef :
68
+ return _resolve_forwardref (candidate , type (request ))
69
+ return candidate
70
+ if isinstance (request , IReturnVoid ):
71
+ return type (None )
72
+ return None
73
+
74
+ if hasattr (t , 'response_type' ):
75
+ ret = t .response_type ()
76
+ if has_type_vars (ret ):
77
+ # avoid reifying type vars if request type has concrete type return marker
78
+ ret_candidate = resolve_response_type ()
79
+ if not has_type_vars (ret_candidate ):
80
+ return ret_candidate
81
+
82
+ type_map = _dict_with_string_keys (_get_type_vars_map (t ))
83
+ if isinstance (ret , TypeVar ):
84
+ return get_origin (ret ).__class_getitem__ (type_map [f"{ ret } " ])
85
+ reified_args = [type_map [f"{ x } " ] for x in ret .__args__ ]
86
+ reified_type = get_origin (ret ).__class_getitem__ (* reified_args )
87
+ return reified_type
88
+ return ret
89
+ else :
90
+ return resolve_response_type ()
43
91
44
92
45
93
def resolve_httpmethod (request ):
@@ -64,6 +112,10 @@ def qsvalue(arg):
64
112
if not arg :
65
113
return ""
66
114
arg_type = type (arg )
115
+ if is_list (arg_type ):
116
+ return "[" + ',' .join ([qsvalue (x ) for x in arg ]) + "]"
117
+ if is_dict (arg_type ):
118
+ return "{" + ',' .join ([k + ":" + qsvalue (v ) for k , v in arg ]) + "}"
67
119
if arg_type is str :
68
120
return quote_plus (arg )
69
121
if arg_type is bytes or arg_type is bytearray :
@@ -199,6 +251,27 @@ def dict_get(name: str, obj: dict, case: Callable[[str], str] = None):
199
251
return None
200
252
201
253
254
+ def sanitize_name (s : str ):
255
+ return s .replace ('_' , '' ).upper ()
256
+
257
+
258
+ def enum_get (cls : Enum , key : Union [str , int ]):
259
+ if type (key ) == int :
260
+ return cls [key ]
261
+ try :
262
+ return cls [key ]
263
+ except Exception as e :
264
+ try :
265
+ upper_snake = uppercase (snakecase (key ))
266
+ return cls [upper_snake ]
267
+ except Exception as e2 :
268
+ sanitize_key = sanitize_name (key )
269
+ for member in cls .__members__ .keys ():
270
+ if sanitize_key == sanitize_name (member ):
271
+ return cls [member ]
272
+ raise TypeError (f"{ key } is not a member of { nameof (Enum )} " )
273
+
274
+
202
275
def _resolve_type (cls : Type , substitute_types : Dict [Type , type ]):
203
276
if substitute_types is None :
204
277
return cls
@@ -211,16 +284,11 @@ def convert(into: Type, obj: Any, substitute_types: Dict[Type, type] = None):
211
284
into = unwrap (into )
212
285
into = _resolve_type (into , substitute_types )
213
286
if Log .debug_enabled ():
214
- Log .debug (f"convert({ into } , { obj } )" )
287
+ Log .debug (f"convert({ into } , { substitute_types } , { obj } )" )
215
288
216
289
generic_def = get_origin (into )
217
290
if generic_def is not None and is_dataclass (generic_def ):
218
- reified_types = {}
219
- generic_type_args = get_args (into )
220
- i = 0
221
- for t in generic_def .__parameters__ :
222
- reified_types [t ] = generic_type_args [i ]
223
- i += 1
291
+ reified_types = _get_type_vars_map (into )
224
292
return convert (generic_def , obj , reified_types )
225
293
226
294
if is_dataclass (into ):
@@ -261,6 +329,13 @@ def convert(into: Type, obj: Any, substitute_types: Dict[Type, type] = None):
261
329
except Exception as e :
262
330
Log .error (f"into().deserialize(obj) { into } ({ obj } )" , e )
263
331
raise e
332
+ elif issubclass (into , Enum ):
333
+ try :
334
+ return enum_get (into , obj )
335
+ except Exception as e :
336
+ print (into , type (into ), obj , type (obj ))
337
+ Log .error (f"Enum into[obj] { into } [{ obj } ]" , e )
338
+ raise e
264
339
else :
265
340
try :
266
341
return into (obj )
@@ -318,7 +393,8 @@ def exec(self):
318
393
# if "ss-tok" in self.session.cookies:
319
394
# ss_tok = self.session.cookies["ss-tok"]
320
395
# print('ss_tok', inspect_jwt(ss_tok))
321
- Log .debug (f"{ using } .request({ self .method } ): url={ self .url } , headers={ self .headers } , data={ self .body_string } " )
396
+ Log .debug (
397
+ f"{ using } .request({ self .method } ): url={ self .url } , headers={ self .headers } , data={ self .body_string } " )
322
398
323
399
response : Optional [Response ] = None
324
400
if has_request_body (self .method ):
@@ -409,10 +485,12 @@ def _get_cookie_value(self, name: str) -> Optional[str]:
409
485
return None
410
486
411
487
@property
412
- def token_cookie (self ): return self ._get_cookie_value (SS_TOKEN_COOKIE )
488
+ def token_cookie (self ):
489
+ return self ._get_cookie_value (SS_TOKEN_COOKIE )
413
490
414
491
@property
415
- def refresh_token_cookie (self ): return self ._get_cookie_value (SS_REFRESH_TOKEN_COOKIE )
492
+ def refresh_token_cookie (self ):
493
+ return self ._get_cookie_value (SS_REFRESH_TOKEN_COOKIE )
416
494
417
495
def create_url_from_dto (self , method : str , request : Any ):
418
496
url = urljoin (self .reply_base_url , nameof (request ))
@@ -467,7 +545,8 @@ def put_url(self, path: str, body: Any = None, response_as: Type = None, args: d
467
545
def patch_url (self , path : str , body : Any = None , response_as : Type = None , args : dict [str , Any ] = None ):
468
546
return self .send_url (path , "PATCH" , response_as , body , args )
469
547
470
- def send_url (self , path : str , method : str = None , response_as : Type = None , body : Any = None , args : dict [str , Any ] = None ):
548
+ def send_url (self , path : str , method : str = None , response_as : Type = None , body : Any = None ,
549
+ args : dict [str , Any ] = None ):
471
550
472
551
if body and not response_as :
473
552
response_as = _resolve_response_type (body )
0 commit comments