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 +4 -6
- libentry/service/flask.py +85 -38
- {libentry-1.11.9.dist-info → libentry-1.11.11.dist-info}/METADATA +7 -6
- {libentry-1.11.9.dist-info → libentry-1.11.11.dist-info}/RECORD +8 -9
- {libentry-1.11.9.dist-info → libentry-1.11.11.dist-info}/WHEEL +1 -1
- libentry/start_service.py +0 -74
- {libentry-1.11.9.dist-info → libentry-1.11.11.dist-info}/LICENSE +0 -0
- {libentry-1.11.9.dist-info → libentry-1.11.11.dist-info}/top_level.txt +0 -0
- {libentry-1.11.9.dist-info → libentry-1.11.11.dist-info}/zip-safe +0 -0
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 =
|
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 =
|
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
|
-
|
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
|
-
|
36
|
-
|
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
|
-
|
74
|
-
|
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
|
-
|
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 =
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
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
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
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
|
-
|
236
|
-
|
237
|
-
|
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
|
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
|
292
|
+
return schema
|
293
|
+
|
294
|
+
return f"No API named \"{name}\""
|
250
295
|
|
251
|
-
|
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.
|
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=
|
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=
|
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.
|
19
|
-
libentry-1.11.
|
20
|
-
libentry-1.11.
|
21
|
-
libentry-1.11.
|
22
|
-
libentry-1.11.
|
23
|
-
libentry-1.11.
|
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,,
|
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())
|
File without changes
|
File without changes
|
File without changes
|