1
1
from datetime import datetime , date , timedelta
2
2
import json
3
+ from re import L
4
+
3
5
from servicestack .utils import to_timespan
4
6
5
7
from requests .models import HTTPError , Response
6
8
7
- from servicestack .client_dtos import IDelete , IGet , IPatch , IPost , IPut , IReturn , IReturnVoid , ResponseStatus
9
+ from servicestack .client_dtos import IDelete , IGet , IPatch , IPost , IPut , IReturn , IReturnVoid , ResponseStatus , KeyValuePair
8
10
from servicestack .log import Log
11
+ from servicestack .utils import *
9
12
10
13
from typing import Callable , TypeVar , Generic , Optional , Dict , List , Tuple , get_args , Any , Type
11
- from dataclasses import dataclass , field , asdict , is_dataclass
12
- from dataclasses_json import dataclass_json , LetterCase , Undefined
14
+ from dataclasses import dataclass , field , fields , asdict , is_dataclass , Field
15
+ from dataclasses_json import dataclass_json , LetterCase , Undefined , config , mm
13
16
from urllib .parse import urljoin , urlencode , quote_plus
17
+ from stringcase import camelcase , snakecase
18
+ import marshmallow .fields as mf
14
19
import requests
15
20
import base64
21
+ import decimal
16
22
17
23
JSON_MIME_TYPE = "application/json"
18
24
25
+ class Bytes (mf .Field ):
26
+ def _serialize (self , value , attr , obj , ** kwargs ):
27
+ return to_bytearray (value )
28
+
29
+ def _deserialize (self , value , attr , data , ** kwargs ):
30
+ return from_bytearray (value )
31
+
32
+ mm .TYPES [timedelta ] = mf .DateTime
33
+ mm .TYPES [KeyValuePair ] = KeyValuePair [str ,str ]
34
+ mm .TYPES [bytes ] = Bytes
35
+ mm .TYPES [Bytes ] = Bytes
36
+
37
+ @dataclass
38
+ class A :
39
+ l : list
40
+ li : list [int ]
41
+ gl : List [int ]
42
+ ol : Optional [List [int ]]
43
+ od : Optional [Dict [int ,str ]]
44
+ int_ :int = field (metadata = config (field_name = "@name" ), default = 0 )
45
+ int_string_map : Optional [Dict [int ,str ]] = None
46
+
19
47
def dump (obj ):
20
48
print ("" )
21
49
for attr in dir (obj ):
22
- print ("obj.%s = %r" % ( attr , getattr (obj , attr )) )
50
+ print (f "obj.{ attr } = { getattr (obj , attr )} " )
23
51
print ("" )
24
52
25
53
def _resolve_response_type (request ):
@@ -103,9 +131,97 @@ def _json_encoder(obj:Any):
103
131
104
132
def json_encode (obj :Any ):
105
133
if is_dataclass (obj ):
106
- return json .dumps (clean_any (asdict (obj )), default = _json_encoder )
134
+ # return obj.to_json()
135
+ return json .dumps (clean_any (obj .to_dict ()), default = _json_encoder )
107
136
return json .dumps (obj , default = _json_encoder )
108
137
138
+ def _json_decoder (obj :Any ):
139
+ # print('ZZZZZZZZZZZZZZZZZ')
140
+ # print(type(obj))
141
+ return obj
142
+
143
+ class TypeConverters :
144
+ converters : dict [Type , Callable [[Any ],Any ]]
145
+
146
+ def register (type :Type , converter :Callable [[Any ],Any ]):
147
+ TypeConverters .converters [type ] = converter
148
+
149
+ TypeConverters .converters = {
150
+ mf .Integer : int ,
151
+ mf .Float : float ,
152
+ mf .Decimal : decimal .Decimal ,
153
+ mf .String : str ,
154
+ mf .Boolean : bool ,
155
+ mf .DateTime : from_datetime ,
156
+ mf .TimeDelta : from_timespan ,
157
+ Bytes : from_bytearray ,
158
+ bytes : from_bytearray ,
159
+ }
160
+
161
+ def is_optional (cls :Type ): return f"{ cls } " .startswith ("typing.Optional" )
162
+ def is_list (cls :Type ): return f"{ cls } " .startswith ("typing.List" )
163
+ def is_dict (cls :Type ): return f"{ cls } " .startswith ("typing.Dict" )
164
+
165
+ def generic_arg (cls :Type ): return cls .__args__ [0 ]
166
+ def generic_args (cls :Type ): return cls .__args__
167
+ def unwrap (cls :Type ):
168
+ if is_optional (cls ):
169
+ return generic_arg (cls )
170
+ return cls
171
+
172
+ def dict_get (name :str , obj :dict , case :Callable [[str ],str ] = None ):
173
+ if name in obj : return obj [name ]
174
+ if case :
175
+ nameCase = case (name )
176
+ if nameCase in obj : return obj [nameCase ]
177
+ nameSnake = snakecase (name )
178
+ if nameSnake in obj : return obj [nameSnake ]
179
+ nameCamel = camelcase (name )
180
+ if nameCamel in obj : return obj [nameCamel ]
181
+ if name .endswith ('_' ):
182
+ return dict_get (name .rstrip ('_' ), obj , case )
183
+ return None
184
+
185
+ def convert (into :Type , obj :Any ):
186
+ if obj is None : return None
187
+ into = unwrap (into )
188
+ if is_dataclass (into ):
189
+ to = {}
190
+ for f in fields (into ):
191
+ val = dict_get (f .name , obj )
192
+ to [f .name ] = convert (f .type , val )
193
+ # print(f"to[{f.name}] = {to[f.name]}")
194
+ return into (** to )
195
+ elif is_list (into ):
196
+ el_type = generic_arg (into )
197
+ to = []
198
+ for item in obj :
199
+ to .append (convert (el_type , item ))
200
+ return to
201
+ elif is_dict (into ):
202
+ key_type , val_type = generic_args (into )
203
+ to = {}
204
+ for key , val in obj .items ():
205
+ to_key = convert (key_type , key )
206
+ to_val = convert (val_type , val )
207
+ to [to_key ] = to_val
208
+ return to
209
+ else :
210
+ if into in TypeConverters .converters :
211
+ converter = TypeConverters .converters [into ]
212
+ try :
213
+ return converter (obj )
214
+ except Exception as e :
215
+ Log .error (f"ERROR converter(obj) { into } ({ obj } )" , e )
216
+ raise e
217
+ else :
218
+ # print(f"TRY {obj} into {into}")
219
+ try :
220
+ return into (obj )
221
+ except Exception as e :
222
+ Log .error (f"ERROR into(obj) { into } ({ obj } )" , e )
223
+ raise e
224
+
109
225
def ex_message (e :Exception ):
110
226
if hasattr (e ,'message' ):
111
227
return e .message
@@ -260,14 +376,23 @@ def _create_response(self, response:Response, info:SendContext):
260
376
return response .content
261
377
262
378
json_str = response .text
379
+ if Log .debug_enabled : Log .debug (f"json_str: { json_str } " )
263
380
264
381
if not into :
265
382
return json .loads (json_str )
266
383
267
384
if into is str :
268
385
return json_str
269
386
270
- res_dto = into .schema ().loads (json_str )
387
+ try :
388
+ # res_dto = into.schema().loads(json_str, object_hook=_json_decoder)
389
+
390
+ json_obj = json .loads (json_str )
391
+ res_dto = convert (into , json_obj )
392
+ except Exception as e :
393
+ Log .error (f"Failed to deserialize into { into } : { e } " , e )
394
+ raise e
395
+
271
396
return res_dto
272
397
273
398
def _raise_error (self , e :Exception ):
@@ -284,7 +409,7 @@ def _handle_error(self, hold_res:Response, e:Exception):
284
409
285
410
if e is HTTPError :
286
411
web_ex .status_code = e .response .status_code
287
- print ( dump ( e ) )
412
+ if Log . debug_enabled : Log . debug ( f" { e } " )
288
413
289
414
if hold_res :
290
415
pass
@@ -309,7 +434,7 @@ def send_request(self, info:SendContext):
309
434
if info .args :
310
435
url = append_querystring (url , info .args )
311
436
except Exception as e :
312
- if ( Log .debug_enabled ): Log .debug (f"send_request(): { ex_message (e )} " )
437
+ if Log .debug_enabled ( ): Log .debug (f"send_request(): { ex_message (e )} " )
313
438
return self ._handle_error (None , e )
314
439
315
440
info .url = url
@@ -331,9 +456,12 @@ def send_request(self, info:SendContext):
331
456
try :
332
457
response = self ._resend_request (info )
333
458
res_dto = self ._create_response (response ,info )
459
+
460
+ if Log .debug_enabled (): Log .debug (f"res_dto = { type (res_dto )} " )
461
+
334
462
return res_dto
335
463
except Exception as e :
336
- if ( Log .debug_enabled ): Log .debug (f"send_request() create_response: { ex_message (e )} " )
464
+ if Log .debug_enabled ( ): Log .debug (f"send_request() create_response: { ex_message (e )} " )
337
465
return self ._handle_error (response , e )
338
466
339
467
0 commit comments