libentry 1.11.7__py3-none-any.whl → 1.11.8__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 +9 -3
- libentry/argparse.py +7 -2
- libentry/service/flask.py +35 -6
- libentry/start_service.py +74 -0
- {libentry-1.11.7.dist-info → libentry-1.11.8.dist-info}/METADATA +1 -1
- {libentry-1.11.7.dist-info → libentry-1.11.8.dist-info}/RECORD +10 -9
- {libentry-1.11.7.dist-info → libentry-1.11.8.dist-info}/WHEEL +1 -1
- {libentry-1.11.7.dist-info → libentry-1.11.8.dist-info}/LICENSE +0 -0
- {libentry-1.11.7.dist-info → libentry-1.11.8.dist-info}/top_level.txt +0 -0
- {libentry-1.11.7.dist-info → libentry-1.11.8.dist-info}/zip-safe +0 -0
libentry/api.py
CHANGED
@@ -243,8 +243,9 @@ class APIClient:
|
|
243
243
|
def post(
|
244
244
|
self,
|
245
245
|
path: str,
|
246
|
-
json_data: Optional[Mapping] = None,
|
246
|
+
json_data: Optional[Mapping] = None, *,
|
247
247
|
stream: bool = False,
|
248
|
+
exhaust_stream: bool = False,
|
248
249
|
num_trials: int = 5,
|
249
250
|
retry_factor: float = 2,
|
250
251
|
timeout: float = 5,
|
@@ -255,11 +256,15 @@ class APIClient:
|
|
255
256
|
):
|
256
257
|
full_url = urljoin(self.base_url, path)
|
257
258
|
|
259
|
+
headers = self.headers
|
260
|
+
if stream:
|
261
|
+
headers = {**headers}
|
262
|
+
headers["Accept"] = headers["Accept"] + "-stream"
|
258
263
|
data = json.dumps(json_data) if json_data is not None else None
|
259
264
|
response = self._request(
|
260
265
|
"post",
|
261
266
|
url=full_url,
|
262
|
-
headers=
|
267
|
+
headers=headers,
|
263
268
|
data=data,
|
264
269
|
verify=self.verify,
|
265
270
|
stream=stream,
|
@@ -277,13 +282,14 @@ class APIClient:
|
|
277
282
|
# TODO: this branch is not tested yet!
|
278
283
|
return response.iter_content(decode_unicode=True)
|
279
284
|
else:
|
280
|
-
|
285
|
+
gen = self._iter_chunks(
|
281
286
|
response=response,
|
282
287
|
chunk_delimiter=chunk_delimiter.encode() if chunk_delimiter else None,
|
283
288
|
chunk_prefix=chunk_prefix.encode() if chunk_prefix else None,
|
284
289
|
chunk_suffix=chunk_suffix.encode() if chunk_suffix else None,
|
285
290
|
error_prefix=error_prefix.encode() if error_prefix else None,
|
286
291
|
)
|
292
|
+
return gen if not exhaust_stream else [*gen]
|
287
293
|
else:
|
288
294
|
try:
|
289
295
|
return _load_json_or_str(response.text)
|
libentry/argparse.py
CHANGED
@@ -106,11 +106,16 @@ class ArgumentParser(argparse.ArgumentParser):
|
|
106
106
|
def add_schema(self, name: str, schema: Type[BaseModel], default: Union[str, BaseModel] = None):
|
107
107
|
if default is not None:
|
108
108
|
if isinstance(default, str):
|
109
|
-
default_json = yaml.
|
109
|
+
default_json = yaml.safe_load(default)
|
110
|
+
if isinstance(default_json, str):
|
111
|
+
with open(default_json, "r") as f:
|
112
|
+
default_json = yaml.safe_load(f)
|
113
|
+
if not isinstance(default_json, dict):
|
114
|
+
raise ValueError(f"Invalid default value for \"{name}\".")
|
110
115
|
elif isinstance(default, BaseModel):
|
111
116
|
default_json = default.model_dump()
|
112
117
|
else:
|
113
|
-
raise TypeError(f"Invalid default type {type(default)}.")
|
118
|
+
raise TypeError(f"Invalid default type {type(default)} for \"{name}\".")
|
114
119
|
default_flat_dict = {}
|
115
120
|
self._json_flatten(name, default_json, default_flat_dict)
|
116
121
|
default = default_flat_dict
|
libentry/service/flask.py
CHANGED
@@ -26,7 +26,9 @@ class JSONDumper:
|
|
26
26
|
def __init__(self, api_info: APIInfo):
|
27
27
|
self.api_info = api_info
|
28
28
|
|
29
|
-
def dump_stream(self, response: Iterable):
|
29
|
+
def dump_stream(self, response: Iterable) -> Iterable[str]:
|
30
|
+
return_value = None
|
31
|
+
|
30
32
|
if self.api_info.stream_prefix is not None:
|
31
33
|
yield self.api_info.stream_prefix
|
32
34
|
|
@@ -34,7 +36,14 @@ class JSONDumper:
|
|
34
36
|
yield self.api_info.chunk_delimiter
|
35
37
|
|
36
38
|
try:
|
37
|
-
|
39
|
+
it = iter(response)
|
40
|
+
while True:
|
41
|
+
try:
|
42
|
+
item = next(it)
|
43
|
+
except StopIteration as e:
|
44
|
+
return_value = e.value
|
45
|
+
break
|
46
|
+
|
38
47
|
text = self.dump(item)
|
39
48
|
|
40
49
|
if self.api_info.chunk_prefix is not None:
|
@@ -64,6 +73,9 @@ class JSONDumper:
|
|
64
73
|
if self.api_info.chunk_delimiter is not None:
|
65
74
|
yield self.api_info.chunk_delimiter
|
66
75
|
|
76
|
+
if return_value is not None:
|
77
|
+
yield self.dump(return_value)
|
78
|
+
|
67
79
|
@staticmethod
|
68
80
|
def dump(response) -> str:
|
69
81
|
if response is None:
|
@@ -132,6 +144,9 @@ class FlaskWrapper:
|
|
132
144
|
self.input_schema = value.annotation
|
133
145
|
|
134
146
|
def __call__(self):
|
147
|
+
print("*" * 10)
|
148
|
+
print(request.headers)
|
149
|
+
print("*" * 10)
|
135
150
|
if request.method == "POST":
|
136
151
|
input_json = json.loads(request.data) if request.data else {}
|
137
152
|
elif request.method == "GET":
|
@@ -155,13 +170,27 @@ class FlaskWrapper:
|
|
155
170
|
raise e
|
156
171
|
return self.app.error(self.dumper.dump_error(e))
|
157
172
|
|
158
|
-
|
159
|
-
|
173
|
+
stream = request.headers.get("Accept", "").endswith("-stream")
|
174
|
+
if stream:
|
175
|
+
if not isinstance(response, (GeneratorType, range)):
|
176
|
+
response = [response]
|
177
|
+
return self.app.ok(
|
160
178
|
self.dumper.dump_stream(response),
|
161
179
|
mimetype=self.api_info.mime_type
|
162
180
|
)
|
163
181
|
else:
|
164
|
-
|
182
|
+
if isinstance(response, (GeneratorType, range)):
|
183
|
+
output = []
|
184
|
+
it = iter(response)
|
185
|
+
while True:
|
186
|
+
try:
|
187
|
+
output.append(next(it))
|
188
|
+
except StopIteration as e:
|
189
|
+
if e.value is not None:
|
190
|
+
output.append(e.value)
|
191
|
+
break
|
192
|
+
response = output
|
193
|
+
return self.app.ok(
|
165
194
|
self.dumper.dump(response),
|
166
195
|
mimetype=self.api_info.mime_type
|
167
196
|
)
|
@@ -224,7 +253,7 @@ class FlaskServer(Flask):
|
|
224
253
|
|
225
254
|
return self.error(f"No API named \"{name}\"")
|
226
255
|
|
227
|
-
def ok(self, body: str, mimetype="application/json"):
|
256
|
+
def ok(self, body: Union[str, Iterable[str]], mimetype="application/json"):
|
228
257
|
return self.response_class(body, status=200, mimetype=mimetype)
|
229
258
|
|
230
259
|
def error(self, body: str, mimetype="text"):
|
@@ -0,0 +1,74 @@
|
|
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())
|
@@ -1,22 +1,23 @@
|
|
1
1
|
libentry/__init__.py,sha256=rDBip9M1Xb1N4wMKE1ni_DldrQbkRjp8DxPkTp3K2qo,170
|
2
|
-
libentry/api.py,sha256=
|
3
|
-
libentry/argparse.py,sha256=
|
2
|
+
libentry/api.py,sha256=arv2CfcZA3XfwyNh_V2NZD16_GIKGSPliwPgvdPjIuk,10066
|
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
|
6
6
|
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
|
10
11
|
libentry/service/__init__.py,sha256=1oLL20yLB1GL9IbFiZD8OReDqiCpFr-yetIR6x1cNkI,23
|
11
12
|
libentry/service/common.py,sha256=OVaW2afgKA6YqstJmtnprBCqQEUZEWotZ6tHavmJJeU,42
|
12
|
-
libentry/service/flask.py,sha256=
|
13
|
+
libentry/service/flask.py,sha256=A8BJ4EMgRLwOVknnhuQYaceCEONcIl5RQnTQ2Q1qyW8,10809
|
13
14
|
libentry/service/list.py,sha256=ElHWhTgShGOhaxMUEwVbMXos0NQKjHsODboiQ-3AMwE,1397
|
14
15
|
libentry/service/running.py,sha256=FrPJoJX6wYxcHIysoatAxhW3LajCCm0Gx6l7__6sULQ,5105
|
15
16
|
libentry/service/start.py,sha256=mZT7b9rVULvzy9GTZwxWnciCHgv9dbGN2JbxM60OMn4,1270
|
16
17
|
libentry/service/stop.py,sha256=wOpwZgrEJ7QirntfvibGq-XsTC6b3ELhzRW2zezh-0s,1187
|
17
|
-
libentry-1.11.
|
18
|
-
libentry-1.11.
|
19
|
-
libentry-1.11.
|
20
|
-
libentry-1.11.
|
21
|
-
libentry-1.11.
|
22
|
-
libentry-1.11.
|
18
|
+
libentry-1.11.8.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
19
|
+
libentry-1.11.8.dist-info/METADATA,sha256=6FeQU10fPl7lFCmX6l1P5754bnaiFBUbeqPw1d7bXwU,500
|
20
|
+
libentry-1.11.8.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
21
|
+
libentry-1.11.8.dist-info/top_level.txt,sha256=u2uF6-X5fn2Erf9PYXOg_6tntPqTpyT-yzUZrltEd6I,9
|
22
|
+
libentry-1.11.8.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
23
|
+
libentry-1.11.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|