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.
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
@@ -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