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 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=self.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
- return self._iter_chunks(
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.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.7
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=5JI8hEBExxk9VUsYRQ3upCRQ9wrWDgZ8Hv-FdX0KPKA,9820
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.7.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
18
- libentry-1.11.7.dist-info/METADATA,sha256=Md151mcRsxn-zEmyISH87ZOV3hSUp1SJeeAirmy-HW0,500
19
- libentry-1.11.7.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
20
- libentry-1.11.7.dist-info/top_level.txt,sha256=u2uF6-X5fn2Erf9PYXOg_6tntPqTpyT-yzUZrltEd6I,9
21
- libentry-1.11.7.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
22
- libentry-1.11.7.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