libentry 1.11.6__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 +10 -10
- libentry/argparse.py +7 -2
- libentry/service/flask.py +35 -6
- libentry/start_service.py +74 -0
- {libentry-1.11.6.dist-info → libentry-1.11.8.dist-info}/METADATA +1 -1
- {libentry-1.11.6.dist-info → libentry-1.11.8.dist-info}/RECORD +10 -9
- {libentry-1.11.6.dist-info → libentry-1.11.8.dist-info}/WHEEL +1 -1
- {libentry-1.11.6.dist-info → libentry-1.11.8.dist-info}/LICENSE +0 -0
- {libentry-1.11.6.dist-info → libentry-1.11.8.dist-info}/top_level.txt +0 -0
- {libentry-1.11.6.dist-info → libentry-1.11.8.dist-info}/zip-safe +0 -0
libentry/api.py
CHANGED
@@ -196,7 +196,6 @@ class APIClient:
|
|
196
196
|
method: str,
|
197
197
|
url: str,
|
198
198
|
num_trials: int = 5,
|
199
|
-
retry_interval: int = 5,
|
200
199
|
retry_factor: float = 2,
|
201
200
|
timeout: float = 5,
|
202
201
|
**kwargs
|
@@ -209,16 +208,14 @@ class APIClient:
|
|
209
208
|
err = e
|
210
209
|
except requests.ConnectionError as e:
|
211
210
|
err = e
|
212
|
-
sleep(
|
211
|
+
sleep(timeout)
|
213
212
|
timeout *= retry_factor
|
214
|
-
retry_interval *= retry_factor
|
215
213
|
raise err
|
216
214
|
|
217
215
|
def get(
|
218
216
|
self,
|
219
217
|
path: str,
|
220
218
|
num_trials: int = 5,
|
221
|
-
retry_interval: int = 5,
|
222
219
|
retry_factor: float = 2,
|
223
220
|
timeout: float = 5
|
224
221
|
):
|
@@ -229,7 +226,6 @@ class APIClient:
|
|
229
226
|
headers=self.headers,
|
230
227
|
verify=self.verify,
|
231
228
|
num_trials=num_trials,
|
232
|
-
retry_interval=retry_interval,
|
233
229
|
retry_factor=retry_factor,
|
234
230
|
timeout=timeout
|
235
231
|
)
|
@@ -247,10 +243,10 @@ class APIClient:
|
|
247
243
|
def post(
|
248
244
|
self,
|
249
245
|
path: str,
|
250
|
-
json_data: Optional[Mapping] = None,
|
246
|
+
json_data: Optional[Mapping] = None, *,
|
251
247
|
stream: bool = False,
|
248
|
+
exhaust_stream: bool = False,
|
252
249
|
num_trials: int = 5,
|
253
|
-
retry_interval: int = 5,
|
254
250
|
retry_factor: float = 2,
|
255
251
|
timeout: float = 5,
|
256
252
|
chunk_delimiter: str = "\n\n",
|
@@ -260,16 +256,19 @@ class APIClient:
|
|
260
256
|
):
|
261
257
|
full_url = urljoin(self.base_url, path)
|
262
258
|
|
259
|
+
headers = self.headers
|
260
|
+
if stream:
|
261
|
+
headers = {**headers}
|
262
|
+
headers["Accept"] = headers["Accept"] + "-stream"
|
263
263
|
data = json.dumps(json_data) if json_data is not None else None
|
264
264
|
response = self._request(
|
265
265
|
"post",
|
266
266
|
url=full_url,
|
267
|
-
headers=
|
267
|
+
headers=headers,
|
268
268
|
data=data,
|
269
269
|
verify=self.verify,
|
270
270
|
stream=stream,
|
271
271
|
num_trials=num_trials,
|
272
|
-
retry_interval=retry_interval,
|
273
272
|
retry_factor=retry_factor,
|
274
273
|
timeout=timeout
|
275
274
|
)
|
@@ -283,13 +282,14 @@ class APIClient:
|
|
283
282
|
# TODO: this branch is not tested yet!
|
284
283
|
return response.iter_content(decode_unicode=True)
|
285
284
|
else:
|
286
|
-
|
285
|
+
gen = self._iter_chunks(
|
287
286
|
response=response,
|
288
287
|
chunk_delimiter=chunk_delimiter.encode() if chunk_delimiter else None,
|
289
288
|
chunk_prefix=chunk_prefix.encode() if chunk_prefix else None,
|
290
289
|
chunk_suffix=chunk_suffix.encode() if chunk_suffix else None,
|
291
290
|
error_prefix=error_prefix.encode() if error_prefix else None,
|
292
291
|
)
|
292
|
+
return gen if not exhaust_stream else [*gen]
|
293
293
|
else:
|
294
294
|
try:
|
295
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
|