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 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(retry_interval)
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=self.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
- return self._iter_chunks(
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.load(default, yaml.FullLoader)
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
- for item in response:
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
- if isinstance(response, (GeneratorType, range)):
159
- return self.app.response_class(
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
- return self.app.response_class(
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: libentry
3
- Version: 1.11.6
3
+ Version: 1.11.8
4
4
  Summary: Entries for experimental utilities.
5
5
  Home-page: https://github.com/XoriieInpottn/libentry
6
6
  Author: xi
@@ -1,22 +1,23 @@
1
1
  libentry/__init__.py,sha256=rDBip9M1Xb1N4wMKE1ni_DldrQbkRjp8DxPkTp3K2qo,170
2
- libentry/api.py,sha256=IajZ1oSSsFfB7Gc4QUCGy8uUqxrtFov1LTsMg06ck0Y,10067
3
- libentry/argparse.py,sha256=Bk11H4WRKxcjMlSd0mjWj1T4NWh0JW5eA7TX3C21IoE,10116
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=71FcjawjfoNG4p0hUWyPpiQ8X5Ao4zsTqt_UEbnSMRc,9842
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.6.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
18
- libentry-1.11.6.dist-info/METADATA,sha256=pQ_nA2SJrcmSEX-_y-ljBsTP3C7j70onmqtU8IWXFeY,500
19
- libentry-1.11.6.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
20
- libentry-1.11.6.dist-info/top_level.txt,sha256=u2uF6-X5fn2Erf9PYXOg_6tntPqTpyT-yzUZrltEd6I,9
21
- libentry-1.11.6.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
22
- libentry-1.11.6.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.36.2)
2
+ Generator: bdist_wheel (0.37.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5