2
2
import json
3
3
import time
4
4
from concurrent .futures .thread import ThreadPoolExecutor
5
+ from decimal import Decimal
5
6
from io import BytesIO
6
7
from typing import Callable , Tuple , Optional , Any
7
8
9
+ import ijson
8
10
import requests
9
11
10
12
from . import globals
@@ -53,7 +55,7 @@ def get_dcs(self, on_complete: Callable, since_time=0, log_on_exception=False, i
53
55
tag = "download_config_specs" )
54
56
self ._context .source_api = self .__api_for_download_config_specs
55
57
if response is not None and self ._is_success_code (response .status_code ):
56
- on_complete (DataSource .NETWORK , response . json ( ) or {}, None )
58
+ on_complete (DataSource .NETWORK , self . _stream_response_into_result_dict ( response ) or {}, None )
57
59
return
58
60
on_complete (DataSource .NETWORK , None , None )
59
61
@@ -64,7 +66,7 @@ def get_dcs_fallback(self, on_complete: Callable, since_time=0, log_on_exception
64
66
tag = "download_config_specs" )
65
67
self ._context .source_api = STATSIG_CDN
66
68
if response is not None and self ._is_success_code (response .status_code ):
67
- on_complete (DataSource .STATSIG_NETWORK , response . json ( ) or {}, None )
69
+ on_complete (DataSource .STATSIG_NETWORK , self . _stream_response_into_result_dict ( response ) or {}, None )
68
70
return
69
71
on_complete (DataSource .STATSIG_NETWORK , None , None )
70
72
@@ -78,7 +80,7 @@ def get_id_lists(self, on_complete: Callable, log_on_exception=False, init_timeo
78
80
tag = "get_id_lists" ,
79
81
)
80
82
if response is not None and self ._is_success_code (response .status_code ):
81
- return on_complete (response . json ( ) or {}, None )
83
+ return on_complete (self . _stream_response_into_result_dict ( response ) or {}, None )
82
84
return on_complete (None , None )
83
85
84
86
def get_id_lists_fallback (self , on_complete : Callable , log_on_exception = False , init_timeout = None ):
@@ -91,7 +93,7 @@ def get_id_lists_fallback(self, on_complete: Callable, log_on_exception=False, i
91
93
tag = "get_id_lists" ,
92
94
)
93
95
if response is not None and self ._is_success_code (response .status_code ):
94
- return on_complete (response . json ( ) or {}, None )
96
+ return on_complete (self . _stream_response_into_result_dict ( response ) or {}, None )
95
97
return on_complete (None , None )
96
98
97
99
def get_id_list (self , on_complete , url , headers , log_on_exception = False ):
@@ -189,7 +191,7 @@ def _request(
189
191
timeout = self .__req_timeout
190
192
191
193
def request_task ():
192
- return requests .request (method , url , data = payload , headers = headers , timeout = timeout )
194
+ return requests .request (method , url , data = payload , headers = headers , timeout = timeout , stream = True )
193
195
194
196
response = None
195
197
if init_timeout is not None :
@@ -245,6 +247,32 @@ def request_task():
245
247
)
246
248
return None
247
249
250
+ def _stream_response_into_result_dict (self , response ):
251
+ result = {}
252
+ try :
253
+ if response .headers .get ("Content-Encoding" ) == "gzip" :
254
+ stream = gzip .GzipFile (fileobj = response .raw )
255
+ else :
256
+ stream = response .raw
257
+ for k , v in ijson .kvitems (stream , "" ):
258
+ v = self ._convert_decimals_to_floats (v )
259
+ result [k ] = v
260
+ return result
261
+ except Exception as e :
262
+ globals .logger .warning (
263
+ f"Failed to stream response into result dict from { response .url } . { e } "
264
+ )
265
+ return None
266
+
267
+ def _convert_decimals_to_floats (self , obj ):
268
+ if isinstance (obj , Decimal ):
269
+ return float (obj )
270
+ if isinstance (obj , dict ):
271
+ return {k : self ._convert_decimals_to_floats (v ) for k , v in obj .items ()}
272
+ if isinstance (obj , list ):
273
+ return [self ._convert_decimals_to_floats (v ) for v in obj ]
274
+ return obj
275
+
248
276
def _is_success_code (self , status_code : int ) -> bool :
249
277
return 200 <= status_code < 300
250
278
0 commit comments