synmax-api-python-client 0.0.44__py3-none-any.whl → 0.0.45__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- DATA/fips_lookup.csv +3196 -3196
- synmax/common/__init__.py +2 -2
- synmax/common/api_client.py +309 -309
- synmax/common/model.py +33 -31
- synmax/config/__init__.py +5 -5
- synmax/hyperion/__init__.py +68 -68
- synmax/hyperion/hyperion_client.py +181 -171
- {synmax_api_python_client-0.0.44.dist-info → synmax_api_python_client-0.0.45.dist-info}/METADATA +10 -13
- synmax_api_python_client-0.0.45.dist-info/RECORD +17 -0
- {synmax_api_python_client-0.0.44.dist-info → synmax_api_python_client-0.0.45.dist-info}/WHEEL +1 -1
- test/hyperion_test.py +187 -187
- test/ip_rate_example.py +50 -50
- synmax_api_python_client-0.0.44.dist-info/RECORD +0 -17
- {synmax_api_python_client-0.0.44.dist-info → synmax_api_python_client-0.0.45.dist-info}/top_level.txt +0 -0
synmax/common/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
from .api_client import ApiClient, ApiClientAsync, PayloadModelBase
|
|
2
|
-
from .model import PayloadModelBase
|
|
1
|
+
from .api_client import ApiClient, ApiClientAsync, PayloadModelBase
|
|
2
|
+
from .model import PayloadModelBase
|
synmax/common/api_client.py
CHANGED
|
@@ -1,309 +1,309 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import logging
|
|
3
|
-
from typing import List, Dict
|
|
4
|
-
|
|
5
|
-
import aiohttp
|
|
6
|
-
import pandas
|
|
7
|
-
import requests
|
|
8
|
-
from aioretry import (
|
|
9
|
-
retry,
|
|
10
|
-
# Tuple[bool, Union[int, float]]
|
|
11
|
-
RetryPolicyStrategy,
|
|
12
|
-
RetryInfo
|
|
13
|
-
)
|
|
14
|
-
from requests.adapters import HTTPAdapter
|
|
15
|
-
from tqdm import tqdm
|
|
16
|
-
from urllib3 import Retry
|
|
17
|
-
|
|
18
|
-
from synmax.common.model import PayloadModelBase
|
|
19
|
-
|
|
20
|
-
logging.basicConfig(level=logging.INFO)
|
|
21
|
-
LOGGER = logging.getLogger(__name__)
|
|
22
|
-
|
|
23
|
-
_api_timeout = 600
|
|
24
|
-
PARALLEL_REQUESTS = 25
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class ApiClientBase:
|
|
28
|
-
def __init__(self, access_token):
|
|
29
|
-
self.access_key = access_token
|
|
30
|
-
self.session = requests.Session()
|
|
31
|
-
self.session.verify = False
|
|
32
|
-
# update headers
|
|
33
|
-
self.session.headers.update(self.headers)
|
|
34
|
-
|
|
35
|
-
# HTTPAdapter
|
|
36
|
-
retry_strategy = Retry(
|
|
37
|
-
total=10,
|
|
38
|
-
backoff_factor=2,
|
|
39
|
-
status_forcelist=[408, 429, 500, 502, 503, 504, 505],
|
|
40
|
-
method_whitelist=["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"],
|
|
41
|
-
|
|
42
|
-
)
|
|
43
|
-
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
44
|
-
self.session.mount("https://", adapter)
|
|
45
|
-
self.session.mount("http://", adapter)
|
|
46
|
-
|
|
47
|
-
@property
|
|
48
|
-
def headers(self):
|
|
49
|
-
return {
|
|
50
|
-
'Content-Type': 'application/json',
|
|
51
|
-
'access_key': self.access_key,
|
|
52
|
-
'User-Agent': "Synmax-api-client/1.0.1/python",
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
@staticmethod
|
|
56
|
-
def _return_response(response, return_json=False):
|
|
57
|
-
"""
|
|
58
|
-
|
|
59
|
-
:param response:
|
|
60
|
-
:param return_json:
|
|
61
|
-
:return:
|
|
62
|
-
"""
|
|
63
|
-
# response.raise_for_status()
|
|
64
|
-
if not response.ok:
|
|
65
|
-
# logging.error('Error in response. %s')
|
|
66
|
-
return None
|
|
67
|
-
|
|
68
|
-
if return_json:
|
|
69
|
-
json_data = response.json()
|
|
70
|
-
if 'error' in json_data:
|
|
71
|
-
# raise Exception(json_data['error'])
|
|
72
|
-
logging.error(json_data['error'])
|
|
73
|
-
return None
|
|
74
|
-
return json_data
|
|
75
|
-
|
|
76
|
-
return response
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
class ApiClient(ApiClientBase):
|
|
80
|
-
|
|
81
|
-
def get(self, url, params=None, return_json=False, **kwargs) -> pandas.DataFrame:
|
|
82
|
-
r"""Sends a GET request.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
:param url: URL for the new :class:`Request` object.
|
|
86
|
-
:param params: (optional) Dictionary, list of tuples or bytes to send
|
|
87
|
-
in the query string for the :class:`Request`.
|
|
88
|
-
:param return_json:
|
|
89
|
-
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
|
90
|
-
:return: :class:`Response <Response>` object
|
|
91
|
-
:rtype: requests.Response
|
|
92
|
-
"""
|
|
93
|
-
LOGGER.info(url)
|
|
94
|
-
response = self.session.get(url, params=params, timeout=_api_timeout, **kwargs)
|
|
95
|
-
json_result = self._return_response(response, return_json)
|
|
96
|
-
|
|
97
|
-
if json_result:
|
|
98
|
-
df = pandas.DataFrame(json_result['data'])
|
|
99
|
-
return df
|
|
100
|
-
|
|
101
|
-
return None
|
|
102
|
-
|
|
103
|
-
def post(self, url, payload: PayloadModelBase = None, return_json=False, **kwargs) -> pandas.DataFrame:
|
|
104
|
-
r"""Sends a POST request.
|
|
105
|
-
|
|
106
|
-
:param url: URL for the new :class:`Request` object.
|
|
107
|
-
:param payload: (optional) Dictionary, list of tuples, bytes, or file-like
|
|
108
|
-
object to send in the body of the :class:`Request`.
|
|
109
|
-
:param return_json:
|
|
110
|
-
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
|
111
|
-
:return: :class:`Response <Response>` object
|
|
112
|
-
:rtype: requests.Response
|
|
113
|
-
"""
|
|
114
|
-
|
|
115
|
-
LOGGER.info('Payload data: %s', payload)
|
|
116
|
-
|
|
117
|
-
data_list: List[Dict] = []
|
|
118
|
-
got_first_page = False
|
|
119
|
-
total_count = -1
|
|
120
|
-
|
|
121
|
-
with tqdm(desc=F"Querying API {url} pages", total=1, dynamic_ncols=True, miniters=0) as progress_bar:
|
|
122
|
-
while not got_first_page or total_count >= pagination['start'] + pagination['page_size']:
|
|
123
|
-
try:
|
|
124
|
-
progress_bar.refresh()
|
|
125
|
-
response = self.session.post(url, data=payload.payload(), timeout=_api_timeout, **kwargs)
|
|
126
|
-
if response.status_code == 401:
|
|
127
|
-
progress_bar.update()
|
|
128
|
-
LOGGER.error(response.text)
|
|
129
|
-
return pandas.DataFrame()
|
|
130
|
-
|
|
131
|
-
json_result = self._return_response(response, return_json)
|
|
132
|
-
pagination = json_result['pagination']
|
|
133
|
-
|
|
134
|
-
if not got_first_page:
|
|
135
|
-
total_count = pagination['total_count']
|
|
136
|
-
total_pages = pagination['total_count'] // pagination['page_size']
|
|
137
|
-
total_pages = total_pages + 1 if total_count % pagination['page_size'] > 0 else 0
|
|
138
|
-
progress_bar.reset(total=total_pages)
|
|
139
|
-
LOGGER.info('Total data size: %s, total pages to scan: %s', total_count, total_pages)
|
|
140
|
-
got_first_page = True
|
|
141
|
-
|
|
142
|
-
data_list.extend(json_result['data'])
|
|
143
|
-
payload.pagination_start = pagination['start'] + pagination['page_size']
|
|
144
|
-
progress_bar.update()
|
|
145
|
-
except:
|
|
146
|
-
pass
|
|
147
|
-
payload.pagination_start = 0
|
|
148
|
-
LOGGER.info('Total response data: %s', len(data_list))
|
|
149
|
-
df = pandas.DataFrame(data_list)
|
|
150
|
-
return df
|
|
151
|
-
|
|
152
|
-
def post_v1(self, url, payload: PayloadModelBase = None, return_json=False, **kwargs) -> pandas.DataFrame:
|
|
153
|
-
r"""Sends a POST request.
|
|
154
|
-
|
|
155
|
-
:param url: URL for the new :class:`Request` object.
|
|
156
|
-
:param payload: (optional) Dictionary, list of tuples, bytes, or file-like
|
|
157
|
-
object to send in the body of the :class:`Request`.
|
|
158
|
-
:param return_json:
|
|
159
|
-
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
|
160
|
-
:return: :class:`Response <Response>` object
|
|
161
|
-
:rtype: requests.Response
|
|
162
|
-
"""
|
|
163
|
-
|
|
164
|
-
LOGGER.info('Payload data: %s', payload)
|
|
165
|
-
|
|
166
|
-
data_list: List[Dict] = []
|
|
167
|
-
|
|
168
|
-
response = self.session.post(url, data=payload.payload(), timeout=_api_timeout, **kwargs)
|
|
169
|
-
json_result = self._return_response(response, return_json)
|
|
170
|
-
data_list.extend(json_result['data'])
|
|
171
|
-
|
|
172
|
-
pagination = json_result['pagination']
|
|
173
|
-
total_count = pagination['total_count']
|
|
174
|
-
total_pages = pagination['total_count'] // pagination['page_size']
|
|
175
|
-
total_pages = total_pages + 1 if total_count % pagination['page_size'] > 0 else 0
|
|
176
|
-
|
|
177
|
-
# first page fetched in the above
|
|
178
|
-
total_pages -= 1
|
|
179
|
-
|
|
180
|
-
LOGGER.info('Total data size: %s, total pages to scan: %s', total_count, total_pages)
|
|
181
|
-
|
|
182
|
-
with tqdm(desc=F"Querying API {url} pages", total=total_pages, dynamic_ncols=True, miniters=0) as progress_bar:
|
|
183
|
-
while total_count >= pagination['start'] + pagination['page_size']:
|
|
184
|
-
try:
|
|
185
|
-
progress_bar.refresh()
|
|
186
|
-
payload.pagination_start = pagination['start'] + pagination['page_size']
|
|
187
|
-
response = self.session.post(url, data=payload.payload(), timeout=_api_timeout, **kwargs)
|
|
188
|
-
json_result = self._return_response(response, return_json)
|
|
189
|
-
|
|
190
|
-
pagination = json_result['pagination']
|
|
191
|
-
data_list.extend(json_result['data'])
|
|
192
|
-
progress_bar.update()
|
|
193
|
-
except:
|
|
194
|
-
pass
|
|
195
|
-
payload.pagination_start = 0
|
|
196
|
-
LOGGER.info('Total response data: %s', len(data_list))
|
|
197
|
-
df = pandas.DataFrame(data_list)
|
|
198
|
-
return df
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def retry_policy(info: RetryInfo) -> RetryPolicyStrategy:
|
|
202
|
-
"""
|
|
203
|
-
- It will always retry until succeeded
|
|
204
|
-
- If fails for the first time, it will retry immediately,
|
|
205
|
-
- If it fails again,
|
|
206
|
-
aioretry will perform a 100ms delay before the second retry,
|
|
207
|
-
200ms delay before the 3rd retry,
|
|
208
|
-
the 4th retry immediately,
|
|
209
|
-
100ms delay before the 5th retry,
|
|
210
|
-
etc...
|
|
211
|
-
"""
|
|
212
|
-
# LOGGER.info('retry_policy: since -> %s, going to sleep sec --> %s', info.since, info.fails)
|
|
213
|
-
# return False, (info.fails - 1) % 3 * 0.1
|
|
214
|
-
|
|
215
|
-
return False, info.fails
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
class ApiClientAsync(ApiClientBase):
|
|
219
|
-
|
|
220
|
-
async def _post_async(self, url, payload: PayloadModelBase, data_list, progress_bar, page_size, total_pages,
|
|
221
|
-
connector: aiohttp.TCPConnector):
|
|
222
|
-
"""
|
|
223
|
-
|
|
224
|
-
:param url:
|
|
225
|
-
:param payload:
|
|
226
|
-
:param data_list:
|
|
227
|
-
:param progress_bar:
|
|
228
|
-
:param page_size:
|
|
229
|
-
:param total_pages:
|
|
230
|
-
:param connector:
|
|
231
|
-
:return:
|
|
232
|
-
"""
|
|
233
|
-
|
|
234
|
-
semaphore = asyncio.Semaphore(PARALLEL_REQUESTS)
|
|
235
|
-
session = aiohttp.ClientSession(connector=connector, headers=self.headers)
|
|
236
|
-
|
|
237
|
-
@retry(retry_policy)
|
|
238
|
-
async def fetch_from_api(page_number):
|
|
239
|
-
async with semaphore:
|
|
240
|
-
_data = payload.payload(pagination_start=page_number * page_size)
|
|
241
|
-
async with session.post(url, data=_data, timeout=_api_timeout, verify_ssl=False) as async_resp:
|
|
242
|
-
async_resp.raise_for_status()
|
|
243
|
-
json_data = await async_resp.json()
|
|
244
|
-
if 'error' in json_data:
|
|
245
|
-
# raise Exception(json_data['error'])
|
|
246
|
-
logging.error(json_data['error'])
|
|
247
|
-
return None
|
|
248
|
-
return json_data
|
|
249
|
-
|
|
250
|
-
tasks = [
|
|
251
|
-
fetch_from_api(_page) for _page in range(1, total_pages + 1)
|
|
252
|
-
]
|
|
253
|
-
for task in asyncio.as_completed(tasks):
|
|
254
|
-
try:
|
|
255
|
-
json_result = await task
|
|
256
|
-
if json_result:
|
|
257
|
-
data_list.extend(json_result['data'])
|
|
258
|
-
except Exception as e:
|
|
259
|
-
LOGGER.exception(e, exc_info=True)
|
|
260
|
-
progress_bar.update()
|
|
261
|
-
await session.close()
|
|
262
|
-
|
|
263
|
-
def post(self, url, payload: PayloadModelBase = None, return_json=False, **kwargs) -> pandas.DataFrame:
|
|
264
|
-
r"""
|
|
265
|
-
Sends a POST request.
|
|
266
|
-
|
|
267
|
-
:param url: URL for the new :class:`Request` object.
|
|
268
|
-
:param payload: (optional) Dictionary, list of tuples, bytes, or file-like
|
|
269
|
-
object to send in the body of the :class:`Request`.
|
|
270
|
-
:param return_json:
|
|
271
|
-
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
|
272
|
-
:return: :class:`Response <Response>` object
|
|
273
|
-
:rtype: requests.Response
|
|
274
|
-
"""
|
|
275
|
-
|
|
276
|
-
LOGGER.info('Payload data: %s', payload)
|
|
277
|
-
|
|
278
|
-
data_list: List[Dict] = []
|
|
279
|
-
|
|
280
|
-
with tqdm(desc=F"Querying API {url} pages", total=1, dynamic_ncols=True, miniters=0) as progress_bar:
|
|
281
|
-
response = self.session.post(url, data=payload.payload(), timeout=_api_timeout, **kwargs)
|
|
282
|
-
if response.status_code == 401:
|
|
283
|
-
progress_bar.update()
|
|
284
|
-
LOGGER.error(response.text)
|
|
285
|
-
return pandas.DataFrame()
|
|
286
|
-
|
|
287
|
-
json_result = self._return_response(response, return_json)
|
|
288
|
-
pagination = json_result['pagination']
|
|
289
|
-
total_count = pagination['total_count']
|
|
290
|
-
total_pages = pagination['total_count'] // pagination['page_size']
|
|
291
|
-
total_pages = total_pages + 1 if total_count % pagination['page_size'] > 0 else 0
|
|
292
|
-
page_size = pagination['page_size']
|
|
293
|
-
progress_bar.reset(total=total_pages)
|
|
294
|
-
|
|
295
|
-
LOGGER.info('Total data size: %s, total pages to scan: %s', total_count, total_pages)
|
|
296
|
-
|
|
297
|
-
data_list.extend(json_result['data'])
|
|
298
|
-
|
|
299
|
-
if total_pages > 1:
|
|
300
|
-
connector = aiohttp.TCPConnector(limit=PARALLEL_REQUESTS, limit_per_host=PARALLEL_REQUESTS)
|
|
301
|
-
loop = asyncio.get_event_loop()
|
|
302
|
-
loop.run_until_complete(
|
|
303
|
-
self._post_async(url, payload, data_list, progress_bar, page_size, total_pages, connector))
|
|
304
|
-
connector.close()
|
|
305
|
-
|
|
306
|
-
payload.pagination_start = 0
|
|
307
|
-
LOGGER.info('Total response data: %s', len(data_list))
|
|
308
|
-
df = pandas.DataFrame(data_list)
|
|
309
|
-
return df
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from typing import List, Dict
|
|
4
|
+
|
|
5
|
+
import aiohttp
|
|
6
|
+
import pandas
|
|
7
|
+
import requests
|
|
8
|
+
from aioretry import (
|
|
9
|
+
retry,
|
|
10
|
+
# Tuple[bool, Union[int, float]]
|
|
11
|
+
RetryPolicyStrategy,
|
|
12
|
+
RetryInfo
|
|
13
|
+
)
|
|
14
|
+
from requests.adapters import HTTPAdapter
|
|
15
|
+
from tqdm import tqdm
|
|
16
|
+
from urllib3 import Retry
|
|
17
|
+
|
|
18
|
+
from synmax.common.model import PayloadModelBase
|
|
19
|
+
|
|
20
|
+
logging.basicConfig(level=logging.INFO)
|
|
21
|
+
LOGGER = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
_api_timeout = 600
|
|
24
|
+
PARALLEL_REQUESTS = 25
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ApiClientBase:
|
|
28
|
+
def __init__(self, access_token):
|
|
29
|
+
self.access_key = access_token
|
|
30
|
+
self.session = requests.Session()
|
|
31
|
+
self.session.verify = False
|
|
32
|
+
# update headers
|
|
33
|
+
self.session.headers.update(self.headers)
|
|
34
|
+
|
|
35
|
+
# HTTPAdapter
|
|
36
|
+
retry_strategy = Retry(
|
|
37
|
+
total=10,
|
|
38
|
+
backoff_factor=2,
|
|
39
|
+
status_forcelist=[408, 429, 500, 502, 503, 504, 505],
|
|
40
|
+
method_whitelist=["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"],
|
|
41
|
+
|
|
42
|
+
)
|
|
43
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
44
|
+
self.session.mount("https://", adapter)
|
|
45
|
+
self.session.mount("http://", adapter)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def headers(self):
|
|
49
|
+
return {
|
|
50
|
+
'Content-Type': 'application/json',
|
|
51
|
+
'access_key': self.access_key,
|
|
52
|
+
'User-Agent': "Synmax-api-client/1.0.1/python",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def _return_response(response, return_json=False):
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
:param response:
|
|
60
|
+
:param return_json:
|
|
61
|
+
:return:
|
|
62
|
+
"""
|
|
63
|
+
# response.raise_for_status()
|
|
64
|
+
if not response.ok:
|
|
65
|
+
# logging.error('Error in response. %s')
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
if return_json:
|
|
69
|
+
json_data = response.json()
|
|
70
|
+
if 'error' in json_data:
|
|
71
|
+
# raise Exception(json_data['error'])
|
|
72
|
+
logging.error(json_data['error'])
|
|
73
|
+
return None
|
|
74
|
+
return json_data
|
|
75
|
+
|
|
76
|
+
return response
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ApiClient(ApiClientBase):
|
|
80
|
+
|
|
81
|
+
def get(self, url, params=None, return_json=False, **kwargs) -> pandas.DataFrame:
|
|
82
|
+
r"""Sends a GET request.
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
:param url: URL for the new :class:`Request` object.
|
|
86
|
+
:param params: (optional) Dictionary, list of tuples or bytes to send
|
|
87
|
+
in the query string for the :class:`Request`.
|
|
88
|
+
:param return_json:
|
|
89
|
+
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
|
90
|
+
:return: :class:`Response <Response>` object
|
|
91
|
+
:rtype: requests.Response
|
|
92
|
+
"""
|
|
93
|
+
LOGGER.info(url)
|
|
94
|
+
response = self.session.get(url, params=params, timeout=_api_timeout, **kwargs)
|
|
95
|
+
json_result = self._return_response(response, return_json)
|
|
96
|
+
|
|
97
|
+
if json_result:
|
|
98
|
+
df = pandas.DataFrame(json_result['data'])
|
|
99
|
+
return df
|
|
100
|
+
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
def post(self, url, payload: PayloadModelBase = None, return_json=False, **kwargs) -> pandas.DataFrame:
|
|
104
|
+
r"""Sends a POST request.
|
|
105
|
+
|
|
106
|
+
:param url: URL for the new :class:`Request` object.
|
|
107
|
+
:param payload: (optional) Dictionary, list of tuples, bytes, or file-like
|
|
108
|
+
object to send in the body of the :class:`Request`.
|
|
109
|
+
:param return_json:
|
|
110
|
+
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
|
111
|
+
:return: :class:`Response <Response>` object
|
|
112
|
+
:rtype: requests.Response
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
LOGGER.info('Payload data: %s', payload)
|
|
116
|
+
|
|
117
|
+
data_list: List[Dict] = []
|
|
118
|
+
got_first_page = False
|
|
119
|
+
total_count = -1
|
|
120
|
+
|
|
121
|
+
with tqdm(desc=F"Querying API {url} pages", total=1, dynamic_ncols=True, miniters=0) as progress_bar:
|
|
122
|
+
while not got_first_page or total_count >= pagination['start'] + pagination['page_size']:
|
|
123
|
+
try:
|
|
124
|
+
progress_bar.refresh()
|
|
125
|
+
response = self.session.post(url, data=payload.payload(), timeout=_api_timeout, **kwargs)
|
|
126
|
+
if response.status_code == 401:
|
|
127
|
+
progress_bar.update()
|
|
128
|
+
LOGGER.error(response.text)
|
|
129
|
+
return pandas.DataFrame()
|
|
130
|
+
|
|
131
|
+
json_result = self._return_response(response, return_json)
|
|
132
|
+
pagination = json_result['pagination']
|
|
133
|
+
|
|
134
|
+
if not got_first_page:
|
|
135
|
+
total_count = pagination['total_count']
|
|
136
|
+
total_pages = pagination['total_count'] // pagination['page_size']
|
|
137
|
+
total_pages = total_pages + 1 if total_count % pagination['page_size'] > 0 else 0
|
|
138
|
+
progress_bar.reset(total=total_pages)
|
|
139
|
+
LOGGER.info('Total data size: %s, total pages to scan: %s', total_count, total_pages)
|
|
140
|
+
got_first_page = True
|
|
141
|
+
|
|
142
|
+
data_list.extend(json_result['data'])
|
|
143
|
+
payload.pagination_start = pagination['start'] + pagination['page_size']
|
|
144
|
+
progress_bar.update()
|
|
145
|
+
except:
|
|
146
|
+
pass
|
|
147
|
+
payload.pagination_start = 0
|
|
148
|
+
LOGGER.info('Total response data: %s', len(data_list))
|
|
149
|
+
df = pandas.DataFrame(data_list)
|
|
150
|
+
return df
|
|
151
|
+
|
|
152
|
+
def post_v1(self, url, payload: PayloadModelBase = None, return_json=False, **kwargs) -> pandas.DataFrame:
|
|
153
|
+
r"""Sends a POST request.
|
|
154
|
+
|
|
155
|
+
:param url: URL for the new :class:`Request` object.
|
|
156
|
+
:param payload: (optional) Dictionary, list of tuples, bytes, or file-like
|
|
157
|
+
object to send in the body of the :class:`Request`.
|
|
158
|
+
:param return_json:
|
|
159
|
+
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
|
160
|
+
:return: :class:`Response <Response>` object
|
|
161
|
+
:rtype: requests.Response
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
LOGGER.info('Payload data: %s', payload)
|
|
165
|
+
|
|
166
|
+
data_list: List[Dict] = []
|
|
167
|
+
|
|
168
|
+
response = self.session.post(url, data=payload.payload(), timeout=_api_timeout, **kwargs)
|
|
169
|
+
json_result = self._return_response(response, return_json)
|
|
170
|
+
data_list.extend(json_result['data'])
|
|
171
|
+
|
|
172
|
+
pagination = json_result['pagination']
|
|
173
|
+
total_count = pagination['total_count']
|
|
174
|
+
total_pages = pagination['total_count'] // pagination['page_size']
|
|
175
|
+
total_pages = total_pages + 1 if total_count % pagination['page_size'] > 0 else 0
|
|
176
|
+
|
|
177
|
+
# first page fetched in the above
|
|
178
|
+
total_pages -= 1
|
|
179
|
+
|
|
180
|
+
LOGGER.info('Total data size: %s, total pages to scan: %s', total_count, total_pages)
|
|
181
|
+
|
|
182
|
+
with tqdm(desc=F"Querying API {url} pages", total=total_pages, dynamic_ncols=True, miniters=0) as progress_bar:
|
|
183
|
+
while total_count >= pagination['start'] + pagination['page_size']:
|
|
184
|
+
try:
|
|
185
|
+
progress_bar.refresh()
|
|
186
|
+
payload.pagination_start = pagination['start'] + pagination['page_size']
|
|
187
|
+
response = self.session.post(url, data=payload.payload(), timeout=_api_timeout, **kwargs)
|
|
188
|
+
json_result = self._return_response(response, return_json)
|
|
189
|
+
|
|
190
|
+
pagination = json_result['pagination']
|
|
191
|
+
data_list.extend(json_result['data'])
|
|
192
|
+
progress_bar.update()
|
|
193
|
+
except:
|
|
194
|
+
pass
|
|
195
|
+
payload.pagination_start = 0
|
|
196
|
+
LOGGER.info('Total response data: %s', len(data_list))
|
|
197
|
+
df = pandas.DataFrame(data_list)
|
|
198
|
+
return df
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def retry_policy(info: RetryInfo) -> RetryPolicyStrategy:
|
|
202
|
+
"""
|
|
203
|
+
- It will always retry until succeeded
|
|
204
|
+
- If fails for the first time, it will retry immediately,
|
|
205
|
+
- If it fails again,
|
|
206
|
+
aioretry will perform a 100ms delay before the second retry,
|
|
207
|
+
200ms delay before the 3rd retry,
|
|
208
|
+
the 4th retry immediately,
|
|
209
|
+
100ms delay before the 5th retry,
|
|
210
|
+
etc...
|
|
211
|
+
"""
|
|
212
|
+
# LOGGER.info('retry_policy: since -> %s, going to sleep sec --> %s', info.since, info.fails)
|
|
213
|
+
# return False, (info.fails - 1) % 3 * 0.1
|
|
214
|
+
|
|
215
|
+
return False, info.fails
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class ApiClientAsync(ApiClientBase):
|
|
219
|
+
|
|
220
|
+
async def _post_async(self, url, payload: PayloadModelBase, data_list, progress_bar, page_size, total_pages,
|
|
221
|
+
connector: aiohttp.TCPConnector):
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
:param url:
|
|
225
|
+
:param payload:
|
|
226
|
+
:param data_list:
|
|
227
|
+
:param progress_bar:
|
|
228
|
+
:param page_size:
|
|
229
|
+
:param total_pages:
|
|
230
|
+
:param connector:
|
|
231
|
+
:return:
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
semaphore = asyncio.Semaphore(PARALLEL_REQUESTS)
|
|
235
|
+
session = aiohttp.ClientSession(connector=connector, headers=self.headers)
|
|
236
|
+
|
|
237
|
+
@retry(retry_policy)
|
|
238
|
+
async def fetch_from_api(page_number):
|
|
239
|
+
async with semaphore:
|
|
240
|
+
_data = payload.payload(pagination_start=page_number * page_size)
|
|
241
|
+
async with session.post(url, data=_data, timeout=_api_timeout, verify_ssl=False) as async_resp:
|
|
242
|
+
async_resp.raise_for_status()
|
|
243
|
+
json_data = await async_resp.json()
|
|
244
|
+
if 'error' in json_data:
|
|
245
|
+
# raise Exception(json_data['error'])
|
|
246
|
+
logging.error(json_data['error'])
|
|
247
|
+
return None
|
|
248
|
+
return json_data
|
|
249
|
+
|
|
250
|
+
tasks = [
|
|
251
|
+
fetch_from_api(_page) for _page in range(1, total_pages + 1)
|
|
252
|
+
]
|
|
253
|
+
for task in asyncio.as_completed(tasks):
|
|
254
|
+
try:
|
|
255
|
+
json_result = await task
|
|
256
|
+
if json_result:
|
|
257
|
+
data_list.extend(json_result['data'])
|
|
258
|
+
except Exception as e:
|
|
259
|
+
LOGGER.exception(e, exc_info=True)
|
|
260
|
+
progress_bar.update()
|
|
261
|
+
await session.close()
|
|
262
|
+
|
|
263
|
+
def post(self, url, payload: PayloadModelBase = None, return_json=False, **kwargs) -> pandas.DataFrame:
|
|
264
|
+
r"""
|
|
265
|
+
Sends a POST request.
|
|
266
|
+
|
|
267
|
+
:param url: URL for the new :class:`Request` object.
|
|
268
|
+
:param payload: (optional) Dictionary, list of tuples, bytes, or file-like
|
|
269
|
+
object to send in the body of the :class:`Request`.
|
|
270
|
+
:param return_json:
|
|
271
|
+
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
|
272
|
+
:return: :class:`Response <Response>` object
|
|
273
|
+
:rtype: requests.Response
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
LOGGER.info('Payload data: %s', payload)
|
|
277
|
+
|
|
278
|
+
data_list: List[Dict] = []
|
|
279
|
+
|
|
280
|
+
with tqdm(desc=F"Querying API {url} pages", total=1, dynamic_ncols=True, miniters=0) as progress_bar:
|
|
281
|
+
response = self.session.post(url, data=payload.payload(), timeout=_api_timeout, **kwargs)
|
|
282
|
+
if response.status_code == 401:
|
|
283
|
+
progress_bar.update()
|
|
284
|
+
LOGGER.error(response.text)
|
|
285
|
+
return pandas.DataFrame()
|
|
286
|
+
|
|
287
|
+
json_result = self._return_response(response, return_json)
|
|
288
|
+
pagination = json_result['pagination']
|
|
289
|
+
total_count = pagination['total_count']
|
|
290
|
+
total_pages = pagination['total_count'] // pagination['page_size']
|
|
291
|
+
total_pages = total_pages + 1 if total_count % pagination['page_size'] > 0 else 0
|
|
292
|
+
page_size = pagination['page_size']
|
|
293
|
+
progress_bar.reset(total=total_pages)
|
|
294
|
+
|
|
295
|
+
LOGGER.info('Total data size: %s, total pages to scan: %s', total_count, total_pages)
|
|
296
|
+
|
|
297
|
+
data_list.extend(json_result['data'])
|
|
298
|
+
|
|
299
|
+
if total_pages > 1:
|
|
300
|
+
connector = aiohttp.TCPConnector(limit=PARALLEL_REQUESTS, limit_per_host=PARALLEL_REQUESTS)
|
|
301
|
+
loop = asyncio.get_event_loop()
|
|
302
|
+
loop.run_until_complete(
|
|
303
|
+
self._post_async(url, payload, data_list, progress_bar, page_size, total_pages, connector))
|
|
304
|
+
connector.close()
|
|
305
|
+
|
|
306
|
+
payload.pagination_start = 0
|
|
307
|
+
LOGGER.info('Total response data: %s', len(data_list))
|
|
308
|
+
df = pandas.DataFrame(data_list)
|
|
309
|
+
return df
|