libentry 1.11.9__py3-none-any.whl → 1.11.11__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
@@ -217,7 +217,7 @@ class APIClient:
217
217
  path: str,
218
218
  num_trials: int = 5,
219
219
  retry_factor: float = 2,
220
- timeout: float = 5
220
+ timeout: float = 15
221
221
  ):
222
222
  api_url = urljoin(self.base_url, path)
223
223
  response = self._request(
@@ -248,7 +248,7 @@ class APIClient:
248
248
  exhaust_stream: bool = False,
249
249
  num_trials: int = 5,
250
250
  retry_factor: float = 2,
251
- timeout: float = 5,
251
+ timeout: float = 15,
252
252
  chunk_delimiter: str = "\n\n",
253
253
  chunk_prefix: str = None,
254
254
  chunk_suffix: str = None,
@@ -256,10 +256,8 @@ class APIClient:
256
256
  ):
257
257
  full_url = urljoin(self.base_url, path)
258
258
 
259
- headers = self.headers
260
- if stream:
261
- headers = {**headers}
262
- headers["Accept"] = headers["Accept"] + "-stream"
259
+ headers = {**self.headers}
260
+ headers["Accept"] = headers["Accept"] + f"; stream={int(stream)}"
263
261
  data = json.dumps(json_data) if json_data is not None else None
264
262
  response = self._request(
265
263
  "post",
libentry/service/flask.py CHANGED
@@ -6,17 +6,18 @@ __all__ = [
6
6
  ]
7
7
 
8
8
  import asyncio
9
+ import re
9
10
  import traceback
10
11
  from inspect import signature
11
12
  from types import GeneratorType
12
- from typing import Callable, Iterable, Optional, Type, Union
13
+ from typing import Any, Callable, Iterable, Optional, Type, Union
13
14
 
14
15
  from flask import Flask, request
15
16
  from gunicorn.app.base import BaseApplication
16
17
  from pydantic import BaseModel, Field, create_model
17
18
  from pydantic.json_schema import GenerateJsonSchema
18
19
 
19
- from libentry import json
20
+ from libentry import api, json
20
21
  from libentry.api import APIInfo, list_api_info
21
22
  from libentry.logging import logger
22
23
 
@@ -32,8 +33,8 @@ class JSONDumper:
32
33
  if self.api_info.stream_prefix is not None:
33
34
  yield self.api_info.stream_prefix
34
35
 
35
- if self.api_info.chunk_delimiter is not None:
36
- yield self.api_info.chunk_delimiter
36
+ if self.api_info.chunk_delimiter is not None:
37
+ yield self.api_info.chunk_delimiter
37
38
 
38
39
  try:
39
40
  it = iter(response)
@@ -59,6 +60,7 @@ class JSONDumper:
59
60
  except Exception as e:
60
61
  if isinstance(e, (SystemExit, KeyboardInterrupt)):
61
62
  raise e
63
+
62
64
  if self.api_info.error_prefix is not None:
63
65
  yield self.api_info.error_prefix
64
66
 
@@ -70,12 +72,15 @@ class JSONDumper:
70
72
  if self.api_info.stream_suffix is not None:
71
73
  yield self.api_info.stream_suffix
72
74
 
73
- if self.api_info.chunk_delimiter is not None:
74
- yield self.api_info.chunk_delimiter
75
+ if self.api_info.chunk_delimiter is not None:
76
+ yield self.api_info.chunk_delimiter
75
77
 
76
78
  if return_value is not None:
77
79
  yield self.dump(return_value)
78
80
 
81
+ if self.api_info.chunk_delimiter is not None:
82
+ yield self.api_info.chunk_delimiter
83
+
79
84
  @staticmethod
80
85
  def dump(response) -> str:
81
86
  if response is None:
@@ -115,7 +120,11 @@ def create_model_from_signature(fn):
115
120
  fields[name] = (param.annotation, None)
116
121
  else:
117
122
  fields[name] = (param.annotation, Field())
118
- fields["return"] = (sig.return_annotation, None)
123
+
124
+ return_annotation = sig.return_annotation
125
+ if return_annotation is sig.empty:
126
+ return_annotation = Any
127
+ fields["return"] = (return_annotation, None)
119
128
  return create_model(f"__{fn.__name__}_signature", **fields)
120
129
 
121
130
 
@@ -167,30 +176,50 @@ class FlaskWrapper:
167
176
  raise e
168
177
  return self.app.error(self.dumper.dump_error(e))
169
178
 
170
- stream = request.headers.get("Accept", "").endswith("-stream")
171
- if stream:
172
- if not isinstance(response, (GeneratorType, range)):
173
- response = [response]
174
- return self.app.ok(
175
- self.dumper.dump_stream(response),
176
- mimetype=self.api_info.mime_type
177
- )
179
+ stream = None
180
+ accept = request.headers.get("Accept", "")
181
+ for param in accept.split(";"):
182
+ match = re.search(r"^\s*stream=(.+)$", param)
183
+ if match:
184
+ stream = match.group(1)
185
+ stream = stream in {"1", "true", "True"}
186
+ break
187
+
188
+ if stream is not None:
189
+ if stream:
190
+ if not isinstance(response, (GeneratorType, range)):
191
+ response = [response]
192
+ return self.app.ok(
193
+ self.dumper.dump_stream(response),
194
+ mimetype=self.api_info.mime_type
195
+ )
196
+ else:
197
+ if isinstance(response, (GeneratorType, range)):
198
+ output = []
199
+ it = iter(response)
200
+ while True:
201
+ try:
202
+ output.append(next(it))
203
+ except StopIteration as e:
204
+ if e.value is not None:
205
+ output.append(e.value)
206
+ break
207
+ response = output
208
+ return self.app.ok(
209
+ self.dumper.dump(response),
210
+ mimetype=self.api_info.mime_type
211
+ )
178
212
  else:
179
213
  if isinstance(response, (GeneratorType, range)):
180
- output = []
181
- it = iter(response)
182
- while True:
183
- try:
184
- output.append(next(it))
185
- except StopIteration as e:
186
- if e.value is not None:
187
- output.append(e.value)
188
- break
189
- response = output
190
- return self.app.ok(
191
- self.dumper.dump(response),
192
- mimetype=self.api_info.mime_type
193
- )
214
+ return self.app.ok(
215
+ self.dumper.dump_stream(response),
216
+ mimetype=self.api_info.mime_type
217
+ )
218
+ else:
219
+ return self.app.ok(
220
+ self.dumper.dump(response),
221
+ mimetype=self.api_info.mime_type
222
+ )
194
223
 
195
224
 
196
225
  class CustomGenerateJsonSchema(GenerateJsonSchema):
@@ -228,27 +257,45 @@ class FlaskServer(Flask):
228
257
  self.post(path)(wrapped_fn)
229
258
  else:
230
259
  raise RuntimeError(f"Unsupported method \"{method}\" for ")
231
- logger.info("Flask application initialized.")
232
260
 
233
- self.get("/")(self.index)
261
+ for fn, api_info in list_api_info(self):
262
+ method = api_info.method
263
+ path = api_info.path
264
+ if asyncio.iscoroutinefunction(fn):
265
+ logger.error(f"Async function \"{fn.__name__}\" is not supported.")
266
+ continue
267
+ logger.info(f"Serving {method}-API for {path}")
234
268
 
235
- def index(self):
236
- args = request.args
237
- if "name" not in args:
269
+ wrapped_fn = FlaskWrapper(self, fn, api_info)
270
+ if method == "GET":
271
+ self.get(path)(wrapped_fn)
272
+ elif method == "POST":
273
+ self.post(path)(wrapped_fn)
274
+ else:
275
+ raise RuntimeError(f"Unsupported method \"{method}\" for ")
276
+
277
+ logger.info("Flask application initialized.")
278
+
279
+ @api.get("/")
280
+ def index(self, name: str = None):
281
+ if name is None:
238
282
  all_api = []
239
283
  for _, api_info in self.api_info_list:
240
284
  all_api.append({"path": api_info.path})
241
- return self.ok(json.dumps(all_api, indent=4))
285
+ return all_api
242
286
 
243
- name = args["name"]
244
287
  for fn, api_info in self.api_info_list:
245
288
  if api_info.path == "/" + name:
246
289
  # noinspection PyTypeChecker
247
290
  dynamic_model = create_model_from_signature(fn)
248
291
  schema = dynamic_model.model_json_schema(schema_generator=CustomGenerateJsonSchema)
249
- return self.ok(json.dumps(schema, indent=4))
292
+ return schema
293
+
294
+ return f"No API named \"{name}\""
250
295
 
251
- return self.error(f"No API named \"{name}\"")
296
+ @api.get()
297
+ def live(self):
298
+ return "OK"
252
299
 
253
300
  def ok(self, body: Union[str, Iterable[str]], mimetype="application/json"):
254
301
  return self.response_class(body, status=200, mimetype=mimetype)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: libentry
3
- Version: 1.11.9
3
+ Version: 1.11.11
4
4
  Summary: Entries for experimental utilities.
5
5
  Home-page: https://github.com/XoriieInpottn/libentry
6
6
  Author: xi
@@ -9,13 +9,14 @@ License: Apache-2.0 license
9
9
  Platform: any
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Description-Content-Type: text/markdown
12
- License-File: LICENSE
13
- Requires-Dist: requests
14
- Requires-Dist: pydantic
15
- Requires-Dist: json5
16
- Requires-Dist: PyYAML
17
12
  Requires-Dist: Flask
13
+ Requires-Dist: PyYAML
18
14
  Requires-Dist: gunicorn
15
+ Requires-Dist: json5
16
+ Requires-Dist: pydantic
17
+ Requires-Dist: requests
19
18
 
20
19
  # libentry
21
20
 
21
+
22
+
@@ -1,5 +1,5 @@
1
1
  libentry/__init__.py,sha256=rDBip9M1Xb1N4wMKE1ni_DldrQbkRjp8DxPkTp3K2qo,170
2
- libentry/api.py,sha256=arv2CfcZA3XfwyNh_V2NZD16_GIKGSPliwPgvdPjIuk,10066
2
+ libentry/api.py,sha256=BWPeeHEgExWt0NOejH6v76baDLv4OxMoBqi1KsdaDeU,10031
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
@@ -7,17 +7,16 @@ libentry/experiment.py,sha256=ejgAHDXWIe9x4haUzIFuz1WasLY0_aD1z_vyEVGjTu8,4922
7
7
  libentry/json.py,sha256=1-Kv5ZRb5dBrOTU84n6sZtYZV3xE-O6wEt_--ynbSaU,1209
8
8
  libentry/logging.py,sha256=IiYoCUzm8XTK1fduA-NA0FI2Qz_m81NEPV3d3tEfgdI,1349
9
9
  libentry/server.py,sha256=gYPoZXd0umlDYZf-6ZV0_vJadg3YQvnLDc6JFDJh9jc,1503
10
- libentry/start_service.py,sha256=Mm0HRwikW1KcDsnkK_Jo2QlNqe5BBBgMqtGd9jZxX1o,1902
11
10
  libentry/service/__init__.py,sha256=1oLL20yLB1GL9IbFiZD8OReDqiCpFr-yetIR6x1cNkI,23
12
11
  libentry/service/common.py,sha256=OVaW2afgKA6YqstJmtnprBCqQEUZEWotZ6tHavmJJeU,42
13
- libentry/service/flask.py,sha256=0axplneHKT1LTrwQIiFV7gJaS3DzlpGXbSsPplSpYcM,10730
12
+ libentry/service/flask.py,sha256=IWA9GKyuLCcdpuwZxjKXMOy8MCnEWympMyNjHxRXu-Q,12333
14
13
  libentry/service/list.py,sha256=ElHWhTgShGOhaxMUEwVbMXos0NQKjHsODboiQ-3AMwE,1397
15
14
  libentry/service/running.py,sha256=FrPJoJX6wYxcHIysoatAxhW3LajCCm0Gx6l7__6sULQ,5105
16
15
  libentry/service/start.py,sha256=mZT7b9rVULvzy9GTZwxWnciCHgv9dbGN2JbxM60OMn4,1270
17
16
  libentry/service/stop.py,sha256=wOpwZgrEJ7QirntfvibGq-XsTC6b3ELhzRW2zezh-0s,1187
18
- libentry-1.11.9.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
19
- libentry-1.11.9.dist-info/METADATA,sha256=X4VF4NUeyi_l6fJIuqP6df0DN9R7ZxVyV7EApRBB-XU,500
20
- libentry-1.11.9.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
21
- libentry-1.11.9.dist-info/top_level.txt,sha256=u2uF6-X5fn2Erf9PYXOg_6tntPqTpyT-yzUZrltEd6I,9
22
- libentry-1.11.9.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
23
- libentry-1.11.9.dist-info/RECORD,,
17
+ libentry-1.11.11.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
18
+ libentry-1.11.11.dist-info/METADATA,sha256=3tIQqa-gC7dPzP3dzuxKuaRYK_X9J67p0BJsUKfDCoA,481
19
+ libentry-1.11.11.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
20
+ libentry-1.11.11.dist-info/top_level.txt,sha256=u2uF6-X5fn2Erf9PYXOg_6tntPqTpyT-yzUZrltEd6I,9
21
+ libentry-1.11.11.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
22
+ libentry-1.11.11.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.37.1)
2
+ Generator: bdist_wheel (0.45.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
libentry/start_service.py DELETED
@@ -1,74 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- __author__ = "xi"
4
-
5
- import os
6
- import subprocess
7
- from cgi import parse
8
- from typing import Dict, List, Optional
9
-
10
- import yaml
11
- from pydantic import BaseModel, Field
12
-
13
- from libentry import ArgumentParser
14
-
15
-
16
- class Config(BaseModel):
17
- exec: str = Field()
18
- envs: Dict[str, str] = Field(default_factory=dict)
19
- stdout: Optional[str] = Field(default="-")
20
- stderr: Optional[str] = Field(default="-")
21
-
22
-
23
- class Status(BaseModel):
24
- pid: int = Field()
25
-
26
-
27
- def main():
28
- parser = ArgumentParser()
29
- parser.add_argument("--config_dir", "-d")
30
- parser.add_argument("--config_filename", "-f", default="config.json")
31
- parser.add_argument("--status_filename", default="status.json")
32
- args = parser.parse_args()
33
-
34
- config_dir = args.config_dir
35
- if config_dir is None:
36
- config_dir = os.getcwd()
37
- config_dir = os.path.abspath(config_dir)
38
- os.chdir(config_dir)
39
-
40
- if not os.path.exists(args.config_filename):
41
- raise FileNotFoundError(f"Cannot find \"{args.config_filename}\".")
42
-
43
- with open(args.config_filename) as f:
44
- config = Config.model_validate(yaml.safe_load(f))
45
-
46
- if config.stdout == "-":
47
- stdout = None
48
- elif config.stdout is None:
49
- stdout = subprocess.DEVNULL
50
- else:
51
- stdout = open(config.stdout, "a")
52
- if config.stderr == "-":
53
- stderr = None
54
- elif config.stderr is None:
55
- stderr = subprocess.DEVNULL
56
- else:
57
- stderr = open(config.stderr, "a")
58
-
59
- process = subprocess.Popen(
60
- ["/bin/bash", "-c", config["exec"]],
61
- cwd=os.getcwd(),
62
- env={**os.environ, **config.envs} if len(config.envs) > 0 else None,
63
- preexec_fn=os.setpgrp,
64
- stdout=stdout,
65
- stderr=stderr
66
- )
67
- pgid = os.getpgid(process.pid)
68
- with open(PID_FILENAME, "w") as f:
69
- f.write(str(pgid))
70
- return 0
71
-
72
-
73
- if __name__ == "__main__":
74
- raise SystemExit(main())