libentry 1.19__py3-none-any.whl → 1.20__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.
libentry/api.py CHANGED
@@ -15,7 +15,8 @@ from time import sleep
15
15
  from typing import Any, Callable, Iterable, List, Literal, Mapping, Optional, Tuple
16
16
  from urllib.parse import urljoin
17
17
 
18
- import requests
18
+ from urllib3 import PoolManager
19
+ from urllib3.exceptions import HTTPError, TimeoutError
19
20
 
20
21
  from libentry import json
21
22
 
@@ -193,32 +194,49 @@ class APIClient:
193
194
  if api_key is not None:
194
195
  self.headers["Authorization"] = f"Bearer {api_key}"
195
196
  self.verify = verify
197
+ self.charset = "UTF-8"
198
+
199
+ DEFAULT_CONN_POOL_SIZE = 10
200
+ CONN_POOL = (
201
+ PoolManager(DEFAULT_CONN_POOL_SIZE),
202
+ PoolManager(DEFAULT_CONN_POOL_SIZE, cert_reqs='CERT_NONE')
203
+ )
196
204
 
197
205
  def _request(
198
206
  self,
199
- method: str,
207
+ method: Literal["GET", "POST"],
200
208
  url: str,
201
- num_trials: int,
202
- timeout: float,
203
- interval: float,
204
- retry_factor: float,
209
+ body: Optional[str] = None,
210
+ headers: Optional[Mapping[str, str]] = None,
211
+ stream: bool = False,
212
+ num_trials: int = 5,
213
+ timeout: float = 15,
214
+ interval: float = 1,
215
+ retry_factor: float = 0.5,
205
216
  on_error: Optional[ErrorCallback] = None,
206
- **kwargs
217
+ verify: Optional[bool] = None,
207
218
  ):
219
+ headers = self.headers if headers is None else headers
220
+ verify = self.verify if verify is None else verify
221
+ preload_content = not stream
222
+
223
+ pool = self.CONN_POOL[int(not verify)]
208
224
  err = None
209
225
  for i in range(num_trials):
210
226
  try:
211
- return requests.request(
227
+ return pool.request(
212
228
  method=method,
213
229
  url=url,
230
+ body=body,
231
+ headers=headers,
214
232
  timeout=timeout * (1 + i * retry_factor),
215
- **kwargs
233
+ preload_content=preload_content
216
234
  )
217
- except requests.Timeout as e:
235
+ except TimeoutError as e:
218
236
  err = e
219
237
  if callable(on_error):
220
238
  on_error(e)
221
- except requests.ConnectionError as e:
239
+ except HTTPError as e:
222
240
  err = e
223
241
  if callable(on_error):
224
242
  on_error(e)
@@ -236,10 +254,8 @@ class APIClient:
236
254
  ):
237
255
  full_url = urljoin(self.base_url, path)
238
256
  response = self._request(
239
- "get",
257
+ method="GET",
240
258
  url=full_url,
241
- headers=self.headers,
242
- verify=self.verify,
243
259
  num_trials=num_trials,
244
260
  timeout=timeout,
245
261
  interval=interval,
@@ -247,15 +263,15 @@ class APIClient:
247
263
  on_error=on_error
248
264
  )
249
265
 
250
- if response.status_code != 200:
251
- text = response.text
252
- response.close()
266
+ if response.status != 200:
267
+ text = response.data.decode(self.charset)
268
+ response.release_conn()
253
269
  raise ServiceError(text)
254
270
 
255
271
  try:
256
- return _load_json_or_str(response.text)
272
+ return _load_json_or_str(response.data.decode(self.charset))
257
273
  finally:
258
- response.close()
274
+ response.release_conn()
259
275
 
260
276
  def post(
261
277
  self,
@@ -271,19 +287,19 @@ class APIClient:
271
287
  chunk_delimiter: str = "\n\n",
272
288
  chunk_prefix: str = None,
273
289
  chunk_suffix: str = None,
274
- error_prefix: str = "ERROR: "
290
+ error_prefix: str = "ERROR: ",
291
+ stream_read_size: int = 512
275
292
  ):
276
293
  full_url = urljoin(self.base_url, path)
277
294
 
278
295
  headers = {**self.headers}
279
296
  headers["Accept"] = headers["Accept"] + f"; stream={int(stream)}"
280
- data = json.dumps(json_data) if json_data is not None else None
297
+ body = json.dumps(json_data) if json_data is not None else None
281
298
  response = self._request(
282
- "post",
299
+ "POST",
283
300
  url=full_url,
301
+ body=body,
284
302
  headers=headers,
285
- data=data,
286
- verify=self.verify,
287
303
  stream=stream,
288
304
  num_trials=num_trials,
289
305
  timeout=timeout,
@@ -291,70 +307,81 @@ class APIClient:
291
307
  retry_factor=retry_factor,
292
308
  on_error=on_error
293
309
  )
294
- if response.status_code != 200:
295
- text = response.text
296
- response.close()
310
+ if response.status != 200:
311
+ text = response.data.decode(self.charset)
312
+ response.release_conn()
297
313
  raise ServiceError(text)
298
314
 
299
- if stream:
300
- if chunk_delimiter is None:
301
- # TODO: this branch is not tested yet!
302
- return response.iter_content(decode_unicode=True)
303
- else:
304
- gen = self._iter_chunks(
305
- response=response,
306
- chunk_delimiter=chunk_delimiter.encode() if chunk_delimiter else None,
307
- chunk_prefix=chunk_prefix.encode() if chunk_prefix else None,
308
- chunk_suffix=chunk_suffix.encode() if chunk_suffix else None,
309
- error_prefix=error_prefix.encode() if error_prefix else None,
310
- )
311
- return gen if not exhaust_stream else [*gen]
312
- else:
315
+ if not stream:
313
316
  try:
314
- return _load_json_or_str(response.text)
317
+ return _load_json_or_str(response.data.decode(self.charset))
315
318
  finally:
316
- response.close()
319
+ response.release_conn()
320
+ else:
321
+ def iter_content():
322
+ try:
323
+ if hasattr(response, "stream"):
324
+ yield from response.stream(stream_read_size, decode_content=True)
325
+ else:
326
+ while True:
327
+ data = response.read(stream_read_size)
328
+ if not data:
329
+ break
330
+ yield data
331
+ finally:
332
+ response.release_conn()
333
+
334
+ def iter_lines(contents: Iterable[bytes]) -> Iterable[str]:
335
+ delimiter = chunk_delimiter.encode(self.charset) if chunk_delimiter is not None else None
336
+ pending = None
337
+ for data in contents:
338
+ if pending is not None:
339
+ data = pending + data
340
+
341
+ lines = data.split(delimiter) if delimiter else data.splitlines()
342
+ pending = lines.pop() if lines and lines[-1] and lines[-1][-1] == data[-1] else None
343
+
344
+ for line in lines:
345
+ yield line.decode(self.charset)
346
+
347
+ if pending is not None:
348
+ yield pending.decode(self.charset)
349
+
350
+ def iter_chunks(lines: Iterable[str]):
351
+ error = None
352
+ for chunk in lines:
353
+ if error is not None:
354
+ # error is not None means there is a fatal exception raised from the server side.
355
+ # The client should just complete the stream and then raise the error to the upper.
356
+ continue
317
357
 
318
- def _iter_chunks(
319
- self,
320
- response: requests.Response,
321
- chunk_delimiter: bytes,
322
- chunk_prefix: bytes,
323
- chunk_suffix: bytes,
324
- error_prefix: bytes
325
- ) -> Iterable:
326
- try:
327
- error = None
328
- for chunk in response.iter_lines(decode_unicode=False, delimiter=chunk_delimiter):
329
- if error is not None:
330
- # error is not None means there is a fatal exception raised from the server side.
331
- # The client should just complete the stream and then raise the error to the upper.
332
- continue
358
+ if not chunk:
359
+ continue
333
360
 
334
- if not chunk:
335
- continue
361
+ if error_prefix is not None:
362
+ if chunk.startswith(error_prefix):
363
+ chunk = chunk[len(error_prefix):]
364
+ error = ServiceError(chunk)
365
+ continue
336
366
 
337
- if error_prefix is not None:
338
- if chunk.startswith(error_prefix):
339
- chunk = chunk[len(error_prefix):]
340
- error = ServiceError(chunk)
341
- continue
367
+ if chunk_prefix is not None:
368
+ if chunk.startswith(chunk_prefix):
369
+ chunk = chunk[len(chunk_prefix):]
370
+ else:
371
+ continue
342
372
 
343
- if chunk_prefix is not None:
344
- if chunk.startswith(chunk_prefix):
345
- chunk = chunk[len(chunk_prefix):]
346
- else:
347
- continue
373
+ if chunk_suffix is not None:
374
+ if chunk.endswith(chunk_suffix):
375
+ chunk = chunk[:-len(chunk_suffix)]
376
+ else:
377
+ continue
348
378
 
349
- if chunk_suffix is not None:
350
- if chunk.endswith(chunk_suffix):
351
- chunk = chunk[:-len(chunk_suffix)]
352
- else:
353
- continue
379
+ yield _load_json_or_str(chunk)
354
380
 
355
- yield _load_json_or_str(chunk)
381
+ if error is not None:
382
+ raise error
356
383
 
357
- if error is not None:
358
- raise error
359
- finally:
360
- response.close()
384
+ gen = iter_content()
385
+ gen = iter_lines(gen)
386
+ gen = iter_chunks(gen)
387
+ return gen if not exhaust_stream else [*gen]
libentry/test_api.py CHANGED
@@ -21,6 +21,7 @@ class TestRequest(BaseModel):
21
21
  timeout: float = 15
22
22
  num_threads: int = 1
23
23
  num_calls: int = 1
24
+ stream: bool = False
24
25
  quiet: bool = False
25
26
  max_resp_len: int = 100
26
27
 
@@ -42,7 +43,8 @@ def test(request: TestRequest):
42
43
  try:
43
44
  kwargs = dict(
44
45
  on_error=lambda err: print(f"[{tid}:{cid}:RETRY] {err}"),
45
- timeout=request.timeout
46
+ timeout=request.timeout,
47
+ stream=request.stream
46
48
  )
47
49
  t = time()
48
50
  if request.method == "GET":
@@ -56,12 +58,26 @@ def test(request: TestRequest):
56
58
  request.data,
57
59
  **kwargs
58
60
  )
59
- t = time() - t
61
+ if not request.stream:
62
+ t = time() - t
63
+ if not request.quiet:
64
+ content = str(response).replace("\n", "\\n")
65
+ print(f"[{tid}:{cid}:SUCCESS] Response={content:.{request.max_resp_len}} Time={t:.04f}")
66
+ else:
67
+ if not request.quiet and request.num_threads == 1:
68
+ print(f"[{tid}:{cid}:SUCCESS] Response=[", end="", flush=True)
69
+ for chunk in response:
70
+ print(f"'{chunk}', ", end="", flush=True)
71
+ t = time() - t
72
+ print(f"] Time={t:.04f}", flush=True)
73
+ else:
74
+ content = [*response]
75
+ t = time() - t
76
+ if not request.quiet:
77
+ content = str(content).replace("\n", "\\n")
78
+ print(f"[{tid}:{cid}:SUCCESS] Response={content:.{request.max_resp_len}} Time={t:.04f}")
60
79
  with lock:
61
80
  time_list.append(t)
62
- if not request.quiet:
63
- content = str(response).replace("\n", " ")
64
- print(f"[{tid}:{cid}:SUCCESS] Time={t:.04f} Response={content:.{request.max_resp_len}}")
65
81
  except Exception as e:
66
82
  print(f"[{tid}:{cid}:FAILED] {e}")
67
83
 
@@ -93,6 +109,7 @@ def main():
93
109
  parser.add_argument("--timeout", type=float, default=15)
94
110
  parser.add_argument("--num_threads", "-t", type=int, default=1)
95
111
  parser.add_argument("--num_calls", "-c", type=int, default=1)
112
+ parser.add_argument("--stream", "-s", action="store_true")
96
113
  parser.add_argument("--quiet", "-q", action="store_true")
97
114
  args = parser.parse_args()
98
115
 
@@ -110,6 +127,7 @@ def main():
110
127
  timeout=args.timeout,
111
128
  num_threads=args.num_threads,
112
129
  num_calls=args.num_calls,
130
+ stream=args.stream,
113
131
  quiet=args.quiet
114
132
  ))
115
133
  print(response.model_dump_json(indent=4))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: libentry
3
- Version: 1.19
3
+ Version: 1.20
4
4
  Summary: Entries for experimental utilities.
5
5
  Home-page: https://github.com/XoriieInpottn/libentry
6
6
  Author: xi
@@ -12,10 +12,10 @@ Description-Content-Type: text/markdown
12
12
  Requires-Dist: Flask
13
13
  Requires-Dist: PyYAML
14
14
  Requires-Dist: gunicorn
15
- Requires-Dist: json5
16
15
  Requires-Dist: numpy
16
+ Requires-Dist: psutil
17
17
  Requires-Dist: pydantic
18
- Requires-Dist: requests
18
+ Requires-Dist: urllib3
19
19
 
20
20
  # libentry
21
21
 
@@ -1,5 +1,5 @@
1
1
  libentry/__init__.py,sha256=rDBip9M1Xb1N4wMKE1ni_DldrQbkRjp8DxPkTp3K2qo,170
2
- libentry/api.py,sha256=LqTa8T8Oh8z5BybzrgtdC0HhMXWL2-DZPdOdBHb6Vl0,10753
2
+ libentry/api.py,sha256=bs4mpOLRA6i9gAWPGzeNarpKssZs2UlaP2xuQbAi9NQ,12063
3
3
  libentry/argparse.py,sha256=NxzXV-jBN51ReZsNs5aeyOfzwYQ5A5nJ95rWoa-FYCs,10415
4
4
  libentry/dataclasses.py,sha256=AQV2PuxplJCwGZ5HKX72U-z-POUhTdy3XtpEK9KNIGQ,4541
5
5
  libentry/executor.py,sha256=cTV0WxJi0nU1TP-cOwmeodN8DD6L1691M2HIQsJtGrU,6582
@@ -8,7 +8,7 @@ libentry/json.py,sha256=1-Kv5ZRb5dBrOTU84n6sZtYZV3xE-O6wEt_--ynbSaU,1209
8
8
  libentry/logging.py,sha256=IiYoCUzm8XTK1fduA-NA0FI2Qz_m81NEPV3d3tEfgdI,1349
9
9
  libentry/schema.py,sha256=BO7EE7i43Cb4xn_OLsBM-HDwWOT6V5fPs91Am3QqnNQ,8178
10
10
  libentry/server.py,sha256=gYPoZXd0umlDYZf-6ZV0_vJadg3YQvnLDc6JFDJh9jc,1503
11
- libentry/test_api.py,sha256=uiHo9rVli9ENkzIyyFDDK6PdteBdetFTA8ikCDNykRQ,3488
11
+ libentry/test_api.py,sha256=GFmirvxfCAQ-nhh6QXyB5EpF4WaENCc3Eoymuk5JxBQ,4448
12
12
  libentry/service/__init__.py,sha256=1oLL20yLB1GL9IbFiZD8OReDqiCpFr-yetIR6x1cNkI,23
13
13
  libentry/service/common.py,sha256=OVaW2afgKA6YqstJmtnprBCqQEUZEWotZ6tHavmJJeU,42
14
14
  libentry/service/flask.py,sha256=Alpsix01ROqI28k-dabD8wJlWAWs-ObNc1nJ-LxOXn4,13349
@@ -16,10 +16,10 @@ libentry/service/list.py,sha256=ElHWhTgShGOhaxMUEwVbMXos0NQKjHsODboiQ-3AMwE,1397
16
16
  libentry/service/running.py,sha256=FrPJoJX6wYxcHIysoatAxhW3LajCCm0Gx6l7__6sULQ,5105
17
17
  libentry/service/start.py,sha256=mZT7b9rVULvzy9GTZwxWnciCHgv9dbGN2JbxM60OMn4,1270
18
18
  libentry/service/stop.py,sha256=wOpwZgrEJ7QirntfvibGq-XsTC6b3ELhzRW2zezh-0s,1187
19
- libentry-1.19.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
20
- libentry-1.19.dist-info/METADATA,sha256=L3cYIpAJrvAMvlHXoqrDqlKe3HeBf2_6BI8EfvqCGuo,812
21
- libentry-1.19.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
22
- libentry-1.19.dist-info/entry_points.txt,sha256=vgHmJZhM-kqM7U9S179UwDD3pM232tpzJ5NntncXi_8,62
23
- libentry-1.19.dist-info/top_level.txt,sha256=u2uF6-X5fn2Erf9PYXOg_6tntPqTpyT-yzUZrltEd6I,9
24
- libentry-1.19.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
25
- libentry-1.19.dist-info/RECORD,,
19
+ libentry-1.20.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
20
+ libentry-1.20.dist-info/METADATA,sha256=lW-aRHt0LPfumZXzccvJazTcOXkTCFhNMUrzGiU90-w,812
21
+ libentry-1.20.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
22
+ libentry-1.20.dist-info/entry_points.txt,sha256=vgHmJZhM-kqM7U9S179UwDD3pM232tpzJ5NntncXi_8,62
23
+ libentry-1.20.dist-info/top_level.txt,sha256=u2uF6-X5fn2Erf9PYXOg_6tntPqTpyT-yzUZrltEd6I,9
24
+ libentry-1.20.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
25
+ libentry-1.20.dist-info/RECORD,,