speedy-utils 1.0.12__tar.gz → 1.0.14__tar.gz

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.
Files changed (32) hide show
  1. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/PKG-INFO +1 -1
  2. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/pyproject.toml +3 -2
  3. speedy_utils-1.0.14/src/speedy_utils/scripts/__init__.py +0 -0
  4. speedy_utils-1.0.14/src/speedy_utils/scripts/openapi_client_codegen.py +258 -0
  5. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/README.md +0 -0
  6. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/llm_utils/__init__.py +0 -0
  7. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/llm_utils/chat_format/__init__.py +0 -0
  8. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/llm_utils/chat_format/display.py +0 -0
  9. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/llm_utils/chat_format/transform.py +0 -0
  10. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/llm_utils/chat_format/utils.py +0 -0
  11. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/llm_utils/group_messages.py +0 -0
  12. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/llm_utils/lm/__init__.py +0 -0
  13. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/llm_utils/lm/alm.py +0 -0
  14. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/llm_utils/lm/lm.py +0 -0
  15. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/llm_utils/lm/utils.py +0 -0
  16. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/llm_utils/scripts/vllm_load_balancer.py +0 -0
  17. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/llm_utils/scripts/vllm_serve.py +0 -0
  18. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/speedy_utils/__init__.py +0 -0
  19. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/speedy_utils/all.py +0 -0
  20. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/speedy_utils/common/__init__.py +0 -0
  21. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/speedy_utils/common/clock.py +0 -0
  22. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/speedy_utils/common/function_decorator.py +0 -0
  23. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/speedy_utils/common/logger.py +0 -0
  24. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/speedy_utils/common/report_manager.py +0 -0
  25. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/speedy_utils/common/utils_cache.py +0 -0
  26. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/speedy_utils/common/utils_io.py +0 -0
  27. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/speedy_utils/common/utils_misc.py +0 -0
  28. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/speedy_utils/common/utils_print.py +0 -0
  29. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/speedy_utils/multi_worker/__init__.py +0 -0
  30. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/speedy_utils/multi_worker/process.py +0 -0
  31. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/speedy_utils/multi_worker/thread.py +0 -0
  32. {speedy_utils-1.0.12 → speedy_utils-1.0.14}/src/speedy_utils/scripts/mpython.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: speedy-utils
3
- Version: 1.0.12
3
+ Version: 1.0.14
4
4
  Summary: Fast and easy-to-use package for data science
5
5
  Author: AnhVTH
6
6
  Author-email: anhvth.226@gmail.com
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "speedy-utils"
3
- version = "1.0.12"
3
+ version = "1.0.14"
4
4
  description = "Fast and easy-to-use package for data science"
5
5
  authors = ["AnhVTH <anhvth.226@gmail.com>"]
6
6
  readme = "README.md"
@@ -11,7 +11,7 @@ packages = [
11
11
  ]
12
12
 
13
13
  [build-system]
14
- requires = ["poetry-core>=1.0.12"]
14
+ requires = ["poetry-core>=1.0.14"]
15
15
  build-backend = "poetry.core.masonry.api"
16
16
 
17
17
  [tool.black]
@@ -63,3 +63,4 @@ packaging = ">=23.2,<25"
63
63
  mpython = "speedy_utils.scripts.mpython:main"
64
64
  svllm = "llm_utils.scripts.vllm_serve:main"
65
65
  svllm-lb = "llm_utils.scripts.vllm_load_balancer:run_load_balancer"
66
+ openapi_client_codegen = "speedy_utils.scripts.openapi_client_codegen:main"
@@ -0,0 +1,258 @@
1
+ """
2
+ generate_client.py
3
+
4
+ A script to generate a synchronous GeneratedClient from an OpenAPI JSON spec.
5
+ Usage:
6
+ python generate_client.py openapi.json > generated_client.py
7
+ """
8
+
9
+ # Renamed from gen.py to openapi_client_codegen.py
10
+
11
+ import argparse
12
+ import json
13
+ import os
14
+ import signal
15
+ import subprocess
16
+ import sys
17
+ import time
18
+ from typing import Any, Dict, List, Optional
19
+
20
+
21
+ def pascal_case(s: str) -> str:
22
+ """Convert snake-case or camelCase to PascalCase"""
23
+ parts = [p for p in s.replace("-", "_").split("_") if p]
24
+ return "".join(p.title() for p in parts)
25
+
26
+
27
+ def snake_case(s: str) -> str:
28
+ """Convert camelCase or PascalCase to snake_case"""
29
+ out = []
30
+ for c in s:
31
+ if c.isupper():
32
+ if out:
33
+ out.append("_")
34
+ out.append(c.lower())
35
+ else:
36
+ out.append(c)
37
+ return "".join(out)
38
+
39
+
40
+ def map_openapi_type(prop: Dict[str, Any]) -> str:
41
+ t = prop.get("type")
42
+ if t == "string":
43
+ fmt = prop.get("format")
44
+ return "datetime" if fmt == "date-time" else "str"
45
+ if t == "integer":
46
+ return "int"
47
+ if t == "boolean":
48
+ return "bool"
49
+ if t == "array":
50
+ items = prop.get("items", {})
51
+ return f"List[{map_openapi_type(items)}]"
52
+ if t == "object":
53
+ return "Dict[str, Any]"
54
+ return "Any"
55
+
56
+
57
+ def generate_models(components: Dict[str, Any]) -> List[str]:
58
+ lines: List[str] = []
59
+ schemas = components.get("schemas", {})
60
+ for name, schema in schemas.items():
61
+ if "enum" in schema:
62
+ lines.append(f"class {name}(str, Enum):")
63
+ for val in schema["enum"]:
64
+ key = snake_case(val).upper()
65
+ lines.append(f" {key} = '{val}'")
66
+ lines.append("")
67
+ continue
68
+ lines.append(f"class {name}(BaseModel):")
69
+ props = schema.get("properties", {})
70
+ required = set(schema.get("required", []))
71
+ if not props:
72
+ lines.append(" pass")
73
+ else:
74
+ for prop_name, prop in props.items():
75
+ t = map_openapi_type(prop)
76
+ optional = prop_name not in required
77
+ type_hint = f"Optional[{t}]" if optional else t
78
+ default = " = None" if optional else ""
79
+ lines.append(f" {prop_name}: {type_hint}{default}")
80
+ lines.append("")
81
+ return lines
82
+
83
+
84
+ def generate_client(spec: Dict[str, Any]) -> List[str]:
85
+ paths = spec.get("paths", {})
86
+ models = spec.get("components", {}).get("schemas", {})
87
+ lines: List[str] = []
88
+ lines.append("class GeneratedClient:")
89
+ lines.append(' """Client generated from OpenAPI spec."""')
90
+ lines.append("")
91
+ # __init__
92
+ lines.append(
93
+ " def __init__(self, base_url: Optional[str]=None, api_key: Optional[str]=None, timeout: float=30.0, app: Any=None) -> None:"
94
+ )
95
+ lines.append(' """Initialize the generated client."""')
96
+ lines.append(" from fastapi.testclient import TestClient")
97
+ lines.append(" import httpx")
98
+ lines.append(" from pydantic import BaseModel")
99
+ lines.append(' headers = {"Content-Type": "application/json"}')
100
+ lines.append(" if api_key:")
101
+ lines.append(' headers["Authorization"] = f"Bearer {api_key}"')
102
+ lines.append("")
103
+ lines.append(" if app is not None:")
104
+ lines.append(' self.base_url = "http://testserver"')
105
+ lines.append(" self.client = TestClient(app)")
106
+ lines.append(" self.client.headers.update(headers)")
107
+ lines.append(" else:")
108
+ lines.append(
109
+ ' self.base_url = base_url.rstrip("/") if base_url else "http://localhost"'
110
+ )
111
+ lines.append(
112
+ " self.client = httpx.Client(base_url=self.base_url, headers=headers, timeout=timeout)"
113
+ )
114
+ lines.append("")
115
+
116
+ for path, ops in paths.items():
117
+ for method, op in ops.items():
118
+ op_id = op.get("operationId", snake_case(method + path))
119
+ func_name = snake_case(op_id)
120
+ summary = op.get("summary", "").strip()
121
+ # collect parameters
122
+ req_params: List[str] = ["self"]
123
+ opt_params: List[str] = []
124
+ # path params (required)
125
+ path_params = [p for p in op.get("parameters", []) if p.get("in") == "path"]
126
+ for p in path_params:
127
+ req_params.append(f"{p['name']}: str")
128
+ # requestBody props
129
+ body_props = None
130
+ if "requestBody" in op:
131
+ content = op["requestBody"].get("content", {})
132
+ media = content.get("application/json") or next(iter(content.values()))
133
+ schema_ref = media.get("schema", {})
134
+ if "$ref" in schema_ref:
135
+ ref = schema_ref["$ref"].split("/")[-1]
136
+ schema = models.get(ref, {})
137
+ body_props = schema.get("properties", {})
138
+ required = set(schema.get("required", []))
139
+ for name_, prop in body_props.items():
140
+ t = map_openapi_type(prop)
141
+ if name_ in required:
142
+ req_params.append(f"{name_}: {t}")
143
+ else:
144
+ opt_params.append(f"{name_}: Optional[{t}] = None")
145
+ # query params (always optional)
146
+ query_params = [
147
+ p for p in op.get("parameters", []) if p.get("in") == "query"
148
+ ]
149
+ for p in query_params:
150
+ t = "str"
151
+ opt_params.append(f"{p['name']}: Optional[{t}] = None")
152
+ # combine signature
153
+ sig = req_params + opt_params
154
+ param_str = ", ".join(sig)
155
+ # determine return type
156
+ ret_type = "Any"
157
+ resp200 = op.get("responses", {}).get("200", {})
158
+ content200 = resp200.get("content", {})
159
+ if content200:
160
+ schema200 = next(iter(content200.values())).get("schema", {})
161
+ if "$ref" in schema200:
162
+ ret_type = schema200["$ref"].split("/")[-1]
163
+ elif "items" in schema200 and "$ref" in schema200["items"]:
164
+ inner = schema200["items"]["$ref"].split("/")[-1]
165
+ ret_type = f"List[{inner}]"
166
+ # method definition
167
+ lines.append(f" def {func_name}({param_str}) -> {ret_type}:")
168
+ if summary:
169
+ lines.append(f' """{summary}."""')
170
+ # build URL
171
+ url = f"f'{path}'" if "{" in path else f"'{path}'"
172
+ m = method.lower()
173
+ # build call
174
+ if body_props and m in ("post", "put", "patch"):
175
+ payload_items = []
176
+ for name_ in body_props:
177
+ payload_items.append(f"'{name_}': {name_}")
178
+ payload = ", ".join(payload_items)
179
+ lines.append(
180
+ f" resp = self.client.{m}({url}, json={{ {payload} }})"
181
+ )
182
+ elif query_params and m == "get":
183
+ qpairs = ", ".join(f"'{p['name']}': {p['name']}" for p in query_params)
184
+ lines.append(
185
+ f" resp = self.client.get({url}, params={{ {qpairs} }})"
186
+ )
187
+ else:
188
+ lines.append(f" resp = self.client.{m}({url})")
189
+ lines.append(" resp.raise_for_status()")
190
+ # parse response
191
+ if ret_type.startswith("List["):
192
+ inner = ret_type[5:-1]
193
+ lines.append(
194
+ f" return [{inner}.model_validate(item) for item in resp.json()]"
195
+ )
196
+ elif ret_type in models:
197
+ lines.append(f" return {ret_type}.model_validate(resp.json())")
198
+ else:
199
+ lines.append(" return resp.json()")
200
+ lines.append("")
201
+
202
+ return lines
203
+
204
+
205
+ def main() -> None:
206
+ parser = argparse.ArgumentParser(
207
+ description="Generate a synchronous client from OpenAPI spec."
208
+ )
209
+ parser.add_argument(
210
+ "spec",
211
+ type=str,
212
+ help="OpenAPI JSON file path or URL (e.g. openapi.json or http://localhost:8084/openapi.json)",
213
+ )
214
+ parser.add_argument(
215
+ "-o",
216
+ "--output",
217
+ type=str,
218
+ default=None,
219
+ help="Output file path (default: stdout)",
220
+ )
221
+
222
+ args = parser.parse_args()
223
+
224
+ try:
225
+ spec_src = args.spec
226
+ if spec_src.startswith("http://") or spec_src.startswith("https://"):
227
+ import httpx
228
+
229
+ response = httpx.get(spec_src)
230
+ spec = response.json()
231
+ else:
232
+ with open(spec_src, "r", encoding="utf-8") as f:
233
+ spec = json.load(f)
234
+ out: List[str] = []
235
+ # imports
236
+ out.append("from typing import Any, Dict, List, Optional")
237
+ out.append("from datetime import datetime")
238
+ out.append("import httpx")
239
+ out.append("from fastapi.testclient import TestClient")
240
+ out.append("from pydantic import BaseModel")
241
+ out.append("from enum import Enum")
242
+ out.append("")
243
+ # models
244
+ out.extend(generate_models(spec.get("components", {})))
245
+ # client
246
+ out.extend(generate_client(spec))
247
+ output_str = "\n".join(out)
248
+ if args.output:
249
+ with open(args.output, "w", encoding="utf-8") as f:
250
+ f.write(output_str)
251
+ print(f"\033[0;32mClient wrapper generated at {args.output}\033[0m")
252
+ else:
253
+ print(output_str)
254
+ except Exception as e:
255
+ print(
256
+ f"\033[0;31mFailed to generate client wrapper: {e}\033[0m", file=sys.stderr
257
+ )
258
+ sys.exit(1)
File without changes