tachyon-api 0.9.0__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.
- tachyon_api/__init__.py +59 -0
- tachyon_api/app.py +699 -0
- tachyon_api/background.py +72 -0
- tachyon_api/cache.py +270 -0
- tachyon_api/cli/__init__.py +9 -0
- tachyon_api/cli/__main__.py +8 -0
- tachyon_api/cli/commands/__init__.py +5 -0
- tachyon_api/cli/commands/generate.py +190 -0
- tachyon_api/cli/commands/lint.py +186 -0
- tachyon_api/cli/commands/new.py +82 -0
- tachyon_api/cli/commands/openapi.py +128 -0
- tachyon_api/cli/main.py +69 -0
- tachyon_api/cli/templates/__init__.py +8 -0
- tachyon_api/cli/templates/project.py +194 -0
- tachyon_api/cli/templates/service.py +330 -0
- tachyon_api/core/__init__.py +12 -0
- tachyon_api/core/lifecycle.py +106 -0
- tachyon_api/core/websocket.py +92 -0
- tachyon_api/di.py +86 -0
- tachyon_api/exceptions.py +39 -0
- tachyon_api/files.py +14 -0
- tachyon_api/middlewares/__init__.py +4 -0
- tachyon_api/middlewares/core.py +40 -0
- tachyon_api/middlewares/cors.py +159 -0
- tachyon_api/middlewares/logger.py +123 -0
- tachyon_api/models.py +73 -0
- tachyon_api/openapi.py +419 -0
- tachyon_api/params.py +268 -0
- tachyon_api/processing/__init__.py +14 -0
- tachyon_api/processing/dependencies.py +172 -0
- tachyon_api/processing/parameters.py +484 -0
- tachyon_api/processing/response_processor.py +93 -0
- tachyon_api/responses.py +92 -0
- tachyon_api/router.py +161 -0
- tachyon_api/security.py +295 -0
- tachyon_api/testing.py +110 -0
- tachyon_api/utils/__init__.py +15 -0
- tachyon_api/utils/type_converter.py +113 -0
- tachyon_api/utils/type_utils.py +162 -0
- tachyon_api-0.9.0.dist-info/METADATA +291 -0
- tachyon_api-0.9.0.dist-info/RECORD +44 -0
- tachyon_api-0.9.0.dist-info/WHEEL +4 -0
- tachyon_api-0.9.0.dist-info/entry_points.txt +3 -0
- tachyon_api-0.9.0.dist-info/licenses/LICENSE +17 -0
tachyon_api/openapi.py
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional, List, Type
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
import datetime
|
|
4
|
+
import uuid
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from .models import Struct
|
|
8
|
+
from .utils import TypeUtils, OPENAPI_TYPE_MAP
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _schema_for_python_type(
|
|
12
|
+
py_type: Type,
|
|
13
|
+
components: Dict[str, Dict[str, Any]],
|
|
14
|
+
visited: set[Type],
|
|
15
|
+
) -> Dict[str, Any]:
|
|
16
|
+
"""Return OpenAPI schema for a Python type, adding components for Structs if needed."""
|
|
17
|
+
# Check if Optional[T] using centralized utility
|
|
18
|
+
inner_type, is_optional = TypeUtils.unwrap_optional(py_type)
|
|
19
|
+
if is_optional:
|
|
20
|
+
schema = _schema_for_python_type(inner_type, components, visited)
|
|
21
|
+
schema["nullable"] = True
|
|
22
|
+
return schema
|
|
23
|
+
|
|
24
|
+
# Check if List[T] using centralized utility
|
|
25
|
+
is_list, item_type = TypeUtils.is_list_type(py_type)
|
|
26
|
+
if is_list:
|
|
27
|
+
item_schema = _schema_for_python_type(item_type, components, visited)
|
|
28
|
+
return {"type": "array", "items": item_schema}
|
|
29
|
+
|
|
30
|
+
# Struct subclass
|
|
31
|
+
if isinstance(py_type, type) and issubclass(py_type, Struct):
|
|
32
|
+
name = py_type.__name__
|
|
33
|
+
if py_type not in visited:
|
|
34
|
+
visited.add(py_type)
|
|
35
|
+
components[name] = _generate_struct_schema(py_type, components, visited)
|
|
36
|
+
return {"$ref": f"#/components/schemas/{name}"}
|
|
37
|
+
|
|
38
|
+
# Special formats
|
|
39
|
+
if py_type is uuid.UUID:
|
|
40
|
+
return {"type": "string", "format": "uuid"}
|
|
41
|
+
if py_type is datetime.datetime:
|
|
42
|
+
return {"type": "string", "format": "date-time"}
|
|
43
|
+
if py_type is datetime.date:
|
|
44
|
+
return {"type": "string", "format": "date"}
|
|
45
|
+
|
|
46
|
+
# Scalars - use centralized type mapping
|
|
47
|
+
return {"type": OPENAPI_TYPE_MAP.get(py_type, "string")}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _generate_struct_schema(
|
|
51
|
+
struct_class: Type[Struct],
|
|
52
|
+
components: Dict[str, Dict[str, Any]],
|
|
53
|
+
visited: set[Type],
|
|
54
|
+
) -> Dict[str, Any]:
|
|
55
|
+
"""
|
|
56
|
+
Generate a JSON Schema dictionary for a msgspec Struct, populating components for nested Structs.
|
|
57
|
+
"""
|
|
58
|
+
properties: Dict[str, Any] = {}
|
|
59
|
+
required: List[str] = []
|
|
60
|
+
|
|
61
|
+
annotations = getattr(struct_class, "__annotations__", {})
|
|
62
|
+
for field_name in getattr(struct_class, "__struct_fields__", annotations.keys()):
|
|
63
|
+
field_type = annotations.get(field_name, str)
|
|
64
|
+
# Use centralized TypeUtils instead of local _unwrap_optional
|
|
65
|
+
base_type, is_opt = TypeUtils.unwrap_optional(field_type)
|
|
66
|
+
|
|
67
|
+
# Build property schema
|
|
68
|
+
prop_schema = _schema_for_python_type(base_type, components, visited)
|
|
69
|
+
if is_opt:
|
|
70
|
+
prop_schema["nullable"] = True
|
|
71
|
+
|
|
72
|
+
properties[field_name] = prop_schema
|
|
73
|
+
|
|
74
|
+
# Determine required: mark non-Optional fields as required
|
|
75
|
+
if not is_opt:
|
|
76
|
+
required.append(field_name)
|
|
77
|
+
|
|
78
|
+
schema: Dict[str, Any] = {"type": "object", "properties": properties}
|
|
79
|
+
if required:
|
|
80
|
+
schema["required"] = required
|
|
81
|
+
return schema
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def build_components_for_struct(
|
|
85
|
+
struct_class: Type[Struct],
|
|
86
|
+
) -> Dict[str, Dict[str, Any]]:
|
|
87
|
+
"""
|
|
88
|
+
Build components schemas for the given Struct and all nested Structs.
|
|
89
|
+
|
|
90
|
+
Returns a dict mapping component name to schema, including the top-level struct.
|
|
91
|
+
"""
|
|
92
|
+
components: Dict[str, Dict[str, Any]] = {}
|
|
93
|
+
visited: set[Type] = set()
|
|
94
|
+
name = struct_class.__name__
|
|
95
|
+
components[name] = _generate_struct_schema(struct_class, components, visited)
|
|
96
|
+
return components
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class Contact:
|
|
101
|
+
"""Contact information for the API"""
|
|
102
|
+
|
|
103
|
+
name: Optional[str] = None
|
|
104
|
+
url: Optional[str] = None
|
|
105
|
+
email: Optional[str] = None
|
|
106
|
+
|
|
107
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
108
|
+
"""Convert to OpenAPI contact object"""
|
|
109
|
+
result = {}
|
|
110
|
+
if self.name:
|
|
111
|
+
result["name"] = self.name
|
|
112
|
+
if self.url:
|
|
113
|
+
result["url"] = self.url
|
|
114
|
+
if self.email:
|
|
115
|
+
result["email"] = self.email
|
|
116
|
+
return result
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass
|
|
120
|
+
class License:
|
|
121
|
+
"""License information for the API"""
|
|
122
|
+
|
|
123
|
+
name: str
|
|
124
|
+
url: Optional[str] = None
|
|
125
|
+
|
|
126
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
127
|
+
"""Convert to OpenAPI license object"""
|
|
128
|
+
result = {"name": self.name}
|
|
129
|
+
if self.url:
|
|
130
|
+
result["url"] = self.url
|
|
131
|
+
return result
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@dataclass
|
|
135
|
+
class Info:
|
|
136
|
+
"""General information about the API"""
|
|
137
|
+
|
|
138
|
+
title: str = "Tachyon API"
|
|
139
|
+
description: Optional[str] = "A fast API built with Tachyon"
|
|
140
|
+
version: str = "0.1.0"
|
|
141
|
+
terms_of_service: Optional[str] = None
|
|
142
|
+
contact: Optional[Contact] = None
|
|
143
|
+
license: Optional[License] = None
|
|
144
|
+
|
|
145
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
146
|
+
"""Convert to OpenAPI info object"""
|
|
147
|
+
result: Dict[str, Any] = {"title": self.title, "version": self.version}
|
|
148
|
+
if self.description:
|
|
149
|
+
result["description"] = self.description
|
|
150
|
+
if self.terms_of_service:
|
|
151
|
+
result["termsOfService"] = self.terms_of_service
|
|
152
|
+
if self.contact:
|
|
153
|
+
result["contact"] = self.contact.to_dict()
|
|
154
|
+
if self.license:
|
|
155
|
+
result["license"] = self.license.to_dict()
|
|
156
|
+
return result
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@dataclass
|
|
160
|
+
class Server:
|
|
161
|
+
"""Server information"""
|
|
162
|
+
|
|
163
|
+
url: str
|
|
164
|
+
description: Optional[str] = None
|
|
165
|
+
|
|
166
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
167
|
+
"""Convert to OpenAPI server object"""
|
|
168
|
+
result = {"url": self.url}
|
|
169
|
+
if self.description:
|
|
170
|
+
result["description"] = self.description
|
|
171
|
+
return result
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@dataclass
|
|
175
|
+
class OpenAPIConfig:
|
|
176
|
+
"""Configuration for OpenAPI/Swagger documentation"""
|
|
177
|
+
|
|
178
|
+
info: Info = field(default_factory=Info)
|
|
179
|
+
servers: List[Server] = field(default_factory=list)
|
|
180
|
+
openapi_version: str = "3.0.0"
|
|
181
|
+
docs_url: str = "/docs"
|
|
182
|
+
redoc_url: str = "/redoc"
|
|
183
|
+
openapi_url: str = "/openapi.json"
|
|
184
|
+
include_in_schema: bool = True
|
|
185
|
+
# Scalar configuration
|
|
186
|
+
scalar_js_url: str = "https://cdn.jsdelivr.net/npm/@scalar/api-reference"
|
|
187
|
+
scalar_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png"
|
|
188
|
+
# Swagger UI configuration (legacy support)
|
|
189
|
+
swagger_ui_oauth2_redirect_url: Optional[str] = None
|
|
190
|
+
swagger_ui_init_oauth: Optional[Dict[str, Any]] = None
|
|
191
|
+
swagger_ui_parameters: Optional[Dict[str, Any]] = None
|
|
192
|
+
swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png"
|
|
193
|
+
swagger_js_url: str = (
|
|
194
|
+
"https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js"
|
|
195
|
+
)
|
|
196
|
+
swagger_css_url: str = (
|
|
197
|
+
"https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css"
|
|
198
|
+
)
|
|
199
|
+
redoc_js_url: str = (
|
|
200
|
+
"https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def to_openapi_dict(self) -> Dict[str, Any]:
|
|
204
|
+
"""Generate the complete OpenAPI dictionary"""
|
|
205
|
+
openapi_dict = {
|
|
206
|
+
"openapi": self.openapi_version,
|
|
207
|
+
"info": self.info.to_dict(),
|
|
208
|
+
"paths": {},
|
|
209
|
+
"components": {"schemas": {}},
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if self.servers:
|
|
213
|
+
openapi_dict["servers"] = [server.to_dict() for server in self.servers]
|
|
214
|
+
|
|
215
|
+
return openapi_dict
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class OpenAPIGenerator:
|
|
219
|
+
"""Generator for OpenAPI documentation"""
|
|
220
|
+
|
|
221
|
+
def __init__(self, config: Optional[OpenAPIConfig] = None):
|
|
222
|
+
"""
|
|
223
|
+
Initialize the OpenAPI generator.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
config: Optional OpenAPI configuration. Uses defaults if not provided.
|
|
227
|
+
"""
|
|
228
|
+
self.config = config or OpenAPIConfig()
|
|
229
|
+
self._openapi_schema: Optional[Dict[str, Any]] = None
|
|
230
|
+
|
|
231
|
+
def get_openapi_schema(self) -> Dict[str, Any]:
|
|
232
|
+
"""Get the complete OpenAPI schema"""
|
|
233
|
+
if self._openapi_schema is None:
|
|
234
|
+
self._openapi_schema = self.config.to_openapi_dict()
|
|
235
|
+
return self._openapi_schema
|
|
236
|
+
|
|
237
|
+
def get_swagger_ui_html(self, openapi_url: str, title: str) -> str:
|
|
238
|
+
"""Generate HTML for Swagger UI"""
|
|
239
|
+
swagger_ui_parameters = self.config.swagger_ui_parameters or {}
|
|
240
|
+
|
|
241
|
+
# Serialize parameters to JSON safely
|
|
242
|
+
params_json = json.dumps(swagger_ui_parameters)
|
|
243
|
+
|
|
244
|
+
html = f"""<!DOCTYPE html>
|
|
245
|
+
<html>
|
|
246
|
+
<head>
|
|
247
|
+
<link type="text/css" rel="stylesheet" href="{self.config.swagger_css_url}">
|
|
248
|
+
<link rel="shortcut icon" href="{self.config.swagger_favicon_url}">
|
|
249
|
+
<title>{title}</title>
|
|
250
|
+
</head>
|
|
251
|
+
<body>
|
|
252
|
+
<div id="swagger-ui"></div>
|
|
253
|
+
<script src="{self.config.swagger_js_url}"></script>
|
|
254
|
+
<script>
|
|
255
|
+
const ui = SwaggerUIBundle({{
|
|
256
|
+
url: '{openapi_url}',
|
|
257
|
+
dom_id: '#swagger-ui',
|
|
258
|
+
presets: [
|
|
259
|
+
SwaggerUIBundle.presets.apis,
|
|
260
|
+
SwaggerUIBundle.presets.standalone
|
|
261
|
+
],
|
|
262
|
+
layout: "BaseLayout",
|
|
263
|
+
...{params_json}
|
|
264
|
+
}})
|
|
265
|
+
</script>
|
|
266
|
+
</body>
|
|
267
|
+
</html>"""
|
|
268
|
+
return html
|
|
269
|
+
|
|
270
|
+
def get_redoc_html(self, openapi_url: str, title: str) -> str:
|
|
271
|
+
"""Generate HTML for ReDoc"""
|
|
272
|
+
html = f"""<!DOCTYPE html>
|
|
273
|
+
<html>
|
|
274
|
+
<head>
|
|
275
|
+
<title>{title}</title>
|
|
276
|
+
<meta charset="utf-8"/>
|
|
277
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
278
|
+
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
|
279
|
+
<style>
|
|
280
|
+
body {{
|
|
281
|
+
margin: 0;
|
|
282
|
+
padding: 0;
|
|
283
|
+
}}
|
|
284
|
+
</style>
|
|
285
|
+
</head>
|
|
286
|
+
<body>
|
|
287
|
+
<redoc spec-url='{openapi_url}'></redoc>
|
|
288
|
+
<script src="{self.config.redoc_js_url}"></script>
|
|
289
|
+
</body>
|
|
290
|
+
</html>"""
|
|
291
|
+
return html
|
|
292
|
+
|
|
293
|
+
def get_scalar_html(self, openapi_url: str, title: str) -> str:
|
|
294
|
+
"""Generate HTML for Scalar API Reference"""
|
|
295
|
+
html = f"""<!DOCTYPE html>
|
|
296
|
+
<html>
|
|
297
|
+
<head>
|
|
298
|
+
<title>{title}</title>
|
|
299
|
+
<meta charset="utf-8" />
|
|
300
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
301
|
+
<link rel="shortcut icon" href="{self.config.scalar_favicon_url}">
|
|
302
|
+
<style>
|
|
303
|
+
body {{
|
|
304
|
+
margin: 0;
|
|
305
|
+
padding: 0;
|
|
306
|
+
}}
|
|
307
|
+
</style>
|
|
308
|
+
</head>
|
|
309
|
+
<body>
|
|
310
|
+
<script
|
|
311
|
+
id="api-reference"
|
|
312
|
+
data-url="{openapi_url}"
|
|
313
|
+
src="{self.config.scalar_js_url}"></script>
|
|
314
|
+
</body>
|
|
315
|
+
</html>"""
|
|
316
|
+
return html
|
|
317
|
+
|
|
318
|
+
def add_path(self, path: str, method: str, operation_data: Dict[str, Any]) -> None:
|
|
319
|
+
"""
|
|
320
|
+
Add a path operation to the OpenAPI schema.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
path: The URL path (e.g., "/items/{item_id}")
|
|
324
|
+
method: HTTP method (e.g., "get", "post")
|
|
325
|
+
operation_data: OpenAPI operation object
|
|
326
|
+
"""
|
|
327
|
+
if self._openapi_schema is None:
|
|
328
|
+
self._openapi_schema = self.config.to_openapi_dict()
|
|
329
|
+
if path not in self._openapi_schema["paths"]:
|
|
330
|
+
self._openapi_schema["paths"][path] = {}
|
|
331
|
+
|
|
332
|
+
self._openapi_schema["paths"][path][method.lower()] = operation_data
|
|
333
|
+
|
|
334
|
+
def add_schema(self, name: str, schema_data: Dict[str, Any]) -> None:
|
|
335
|
+
"""
|
|
336
|
+
Add a component schema to the OpenAPI specification.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
name: Schema name (e.g., "Item", "User")
|
|
340
|
+
schema_data: OpenAPI schema object
|
|
341
|
+
"""
|
|
342
|
+
if self._openapi_schema is None:
|
|
343
|
+
self._openapi_schema = self.config.to_openapi_dict()
|
|
344
|
+
|
|
345
|
+
self._openapi_schema["components"]["schemas"][name] = schema_data
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def create_openapi_config(
|
|
349
|
+
title: str = "Tachyon API",
|
|
350
|
+
description: Optional[str] = "A fast API built with Tachyon",
|
|
351
|
+
version: str = "0.1.0",
|
|
352
|
+
openapi_version: str = "3.0.0",
|
|
353
|
+
docs_url: str = "/docs",
|
|
354
|
+
redoc_url: str = "/redoc",
|
|
355
|
+
openapi_url: str = "/openapi.json",
|
|
356
|
+
contact: Optional[Contact] = None,
|
|
357
|
+
license: Optional[License] = None,
|
|
358
|
+
servers: Optional[List[Server]] = None,
|
|
359
|
+
terms_of_service: Optional[str] = None,
|
|
360
|
+
# Scalar configuration
|
|
361
|
+
scalar_js_url: str = "https://cdn.jsdelivr.net/npm/@scalar/api-reference",
|
|
362
|
+
scalar_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
|
|
363
|
+
# Swagger UI configuration (legacy support)
|
|
364
|
+
swagger_ui_parameters: Optional[Dict[str, Any]] = None,
|
|
365
|
+
swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
|
|
366
|
+
swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js",
|
|
367
|
+
swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css",
|
|
368
|
+
redoc_js_url: str = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js",
|
|
369
|
+
) -> OpenAPIConfig:
|
|
370
|
+
"""
|
|
371
|
+
Create a customizable OpenAPI configuration similar to FastAPI.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
title: API title
|
|
375
|
+
description: API description
|
|
376
|
+
version: API version
|
|
377
|
+
openapi_version: OpenAPI specification version
|
|
378
|
+
docs_url: URL for Scalar API Reference documentation (default)
|
|
379
|
+
redoc_url: URL for ReDoc documentation
|
|
380
|
+
openapi_url: URL for OpenAPI JSON schema
|
|
381
|
+
contact: Contact information
|
|
382
|
+
license: License information
|
|
383
|
+
servers: List of servers
|
|
384
|
+
terms_of_service: Terms of service URL
|
|
385
|
+
scalar_js_url: Scalar API Reference JavaScript URL
|
|
386
|
+
scalar_favicon_url: Favicon URL for Scalar
|
|
387
|
+
swagger_ui_parameters: Additional Swagger UI parameters
|
|
388
|
+
swagger_favicon_url: Favicon URL for Swagger UI
|
|
389
|
+
swagger_js_url: Swagger UI JavaScript URL
|
|
390
|
+
swagger_css_url: Swagger UI CSS URL
|
|
391
|
+
redoc_js_url: ReDoc JavaScript URL
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
Configured OpenAPIConfig instance
|
|
395
|
+
"""
|
|
396
|
+
info = Info(
|
|
397
|
+
title=title,
|
|
398
|
+
description=description,
|
|
399
|
+
version=version,
|
|
400
|
+
terms_of_service=terms_of_service,
|
|
401
|
+
contact=contact,
|
|
402
|
+
license=license,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
return OpenAPIConfig(
|
|
406
|
+
info=info,
|
|
407
|
+
servers=servers or [],
|
|
408
|
+
openapi_version=openapi_version,
|
|
409
|
+
docs_url=docs_url,
|
|
410
|
+
redoc_url=redoc_url,
|
|
411
|
+
openapi_url=openapi_url,
|
|
412
|
+
scalar_js_url=scalar_js_url,
|
|
413
|
+
scalar_favicon_url=scalar_favicon_url,
|
|
414
|
+
swagger_ui_parameters=swagger_ui_parameters,
|
|
415
|
+
swagger_favicon_url=swagger_favicon_url,
|
|
416
|
+
swagger_js_url=swagger_js_url,
|
|
417
|
+
swagger_css_url=swagger_css_url,
|
|
418
|
+
redoc_js_url=redoc_js_url,
|
|
419
|
+
)
|
tachyon_api/params.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tachyon Web Framework - Parameter Definition Module
|
|
3
|
+
|
|
4
|
+
This module provides parameter marker classes for defining how endpoint function
|
|
5
|
+
parameters should be resolved from HTTP requests (query strings, path variables,
|
|
6
|
+
and request bodies).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Query:
|
|
13
|
+
"""
|
|
14
|
+
Marker class for query string parameters.
|
|
15
|
+
|
|
16
|
+
Use this to define parameters that should be extracted from the URL query string
|
|
17
|
+
with optional default values and automatic type conversion.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
default: Default value if parameter is not provided. Use ... for required parameters.
|
|
21
|
+
description: Optional description for OpenAPI documentation.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
@app.get("/search")
|
|
25
|
+
def search(
|
|
26
|
+
q: str = Query(...), # Required query parameter
|
|
27
|
+
limit: int = Query(10), # Optional with default value
|
|
28
|
+
active: bool = Query(False) # Optional boolean parameter
|
|
29
|
+
):
|
|
30
|
+
return {"query": q, "limit": limit, "active": active}
|
|
31
|
+
|
|
32
|
+
Note:
|
|
33
|
+
- Boolean parameters accept: "true", "1", "t", "yes" (case-insensitive) as True
|
|
34
|
+
- Type conversion is automatic based on parameter annotation
|
|
35
|
+
- Missing required parameters return 422 Unprocessable Entity
|
|
36
|
+
- Invalid type conversions return 422 Unprocessable Entity
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, default: Any = ..., description: Optional[str] = None):
|
|
40
|
+
"""
|
|
41
|
+
Initialize a Query parameter marker.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
default: Default value for the parameter. Use ... (Ellipsis) for required parameters.
|
|
45
|
+
description: Optional description for API documentation.
|
|
46
|
+
"""
|
|
47
|
+
self.default = default
|
|
48
|
+
self.description = description
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Path:
|
|
52
|
+
"""
|
|
53
|
+
Marker class for path parameters.
|
|
54
|
+
|
|
55
|
+
Use this to define parameters that should be extracted from the URL path.
|
|
56
|
+
Path parameters are always required.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
description: Optional description for OpenAPI documentation.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, description: Optional[str] = None):
|
|
63
|
+
"""
|
|
64
|
+
Initialize a Path parameter marker.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
description: Optional description for API documentation.
|
|
68
|
+
"""
|
|
69
|
+
self.description = description
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Body:
|
|
73
|
+
"""
|
|
74
|
+
Marker class for request body parameters.
|
|
75
|
+
|
|
76
|
+
Use this to define parameters that should be extracted and validated from
|
|
77
|
+
the JSON request body. The parameter type should be a Struct subclass.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
description: Optional description for OpenAPI documentation.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def __init__(self, description: Optional[str] = None):
|
|
84
|
+
"""
|
|
85
|
+
Initialize a Body parameter marker.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
description: Optional description for API documentation.
|
|
89
|
+
"""
|
|
90
|
+
self.description = description
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class Header:
|
|
94
|
+
"""
|
|
95
|
+
Marker class for HTTP header parameters.
|
|
96
|
+
|
|
97
|
+
Use this to define parameters that should be extracted from HTTP request headers.
|
|
98
|
+
Header names are case-insensitive per HTTP specification.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
default: Default value if header is not provided. Use ... for required headers.
|
|
102
|
+
alias: Optional custom header name. If not provided, the parameter name is used
|
|
103
|
+
with underscores converted to hyphens.
|
|
104
|
+
description: Optional description for OpenAPI documentation.
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
@app.get("/protected")
|
|
108
|
+
def protected(
|
|
109
|
+
authorization: str = Header(...), # Required header
|
|
110
|
+
x_request_id: str = Header("default-id"), # Optional with default
|
|
111
|
+
token: str = Header(..., alias="X-Auth-Token") # Custom header name
|
|
112
|
+
):
|
|
113
|
+
return {"auth": authorization, "id": x_request_id}
|
|
114
|
+
|
|
115
|
+
Note:
|
|
116
|
+
- Header names are matched case-insensitively
|
|
117
|
+
- Parameter names with underscores match headers with hyphens
|
|
118
|
+
(e.g., x_request_id matches X-Request-Id)
|
|
119
|
+
- Missing required headers return 422 Unprocessable Entity
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def __init__(
|
|
123
|
+
self,
|
|
124
|
+
default: Any = ...,
|
|
125
|
+
alias: Optional[str] = None,
|
|
126
|
+
description: Optional[str] = None,
|
|
127
|
+
):
|
|
128
|
+
"""
|
|
129
|
+
Initialize a Header parameter marker.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
default: Default value for the header. Use ... (Ellipsis) for required.
|
|
133
|
+
alias: Optional custom header name to use instead of parameter name.
|
|
134
|
+
description: Optional description for API documentation.
|
|
135
|
+
"""
|
|
136
|
+
self.default = default
|
|
137
|
+
self.alias = alias
|
|
138
|
+
self.description = description
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class Cookie:
|
|
142
|
+
"""
|
|
143
|
+
Marker class for HTTP cookie parameters.
|
|
144
|
+
|
|
145
|
+
Use this to define parameters that should be extracted from HTTP cookies.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
default: Default value if cookie is not provided. Use ... for required cookies.
|
|
149
|
+
alias: Optional custom cookie name.
|
|
150
|
+
description: Optional description for OpenAPI documentation.
|
|
151
|
+
|
|
152
|
+
Example:
|
|
153
|
+
@app.get("/profile")
|
|
154
|
+
def profile(session_id: str = Cookie(...)):
|
|
155
|
+
return {"session": session_id}
|
|
156
|
+
|
|
157
|
+
Note:
|
|
158
|
+
- Missing required cookies return 422 Unprocessable Entity
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
def __init__(
|
|
162
|
+
self,
|
|
163
|
+
default: Any = ...,
|
|
164
|
+
alias: Optional[str] = None,
|
|
165
|
+
description: Optional[str] = None,
|
|
166
|
+
):
|
|
167
|
+
"""
|
|
168
|
+
Initialize a Cookie parameter marker.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
default: Default value for the cookie. Use ... (Ellipsis) for required.
|
|
172
|
+
alias: Optional custom cookie name to use instead of parameter name.
|
|
173
|
+
description: Optional description for API documentation.
|
|
174
|
+
"""
|
|
175
|
+
self.default = default
|
|
176
|
+
self.alias = alias
|
|
177
|
+
self.description = description
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class Form:
|
|
181
|
+
"""
|
|
182
|
+
Marker class for form data parameters.
|
|
183
|
+
|
|
184
|
+
Use this to define parameters that should be extracted from
|
|
185
|
+
application/x-www-form-urlencoded or multipart/form-data request bodies.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
default: Default value if field is not provided. Use ... for required fields.
|
|
189
|
+
alias: Optional custom field name.
|
|
190
|
+
description: Optional description for OpenAPI documentation.
|
|
191
|
+
|
|
192
|
+
Example:
|
|
193
|
+
@app.post("/login")
|
|
194
|
+
async def login(
|
|
195
|
+
username: str = Form(...),
|
|
196
|
+
password: str = Form(...),
|
|
197
|
+
):
|
|
198
|
+
return {"username": username}
|
|
199
|
+
|
|
200
|
+
Note:
|
|
201
|
+
- Missing required form fields return 422 Unprocessable Entity
|
|
202
|
+
- Works with multipart/form-data for file uploads
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
def __init__(
|
|
206
|
+
self,
|
|
207
|
+
default: Any = ...,
|
|
208
|
+
alias: Optional[str] = None,
|
|
209
|
+
description: Optional[str] = None,
|
|
210
|
+
):
|
|
211
|
+
"""
|
|
212
|
+
Initialize a Form parameter marker.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
default: Default value for the field. Use ... (Ellipsis) for required.
|
|
216
|
+
alias: Optional custom field name to use instead of parameter name.
|
|
217
|
+
description: Optional description for API documentation.
|
|
218
|
+
"""
|
|
219
|
+
self.default = default
|
|
220
|
+
self.alias = alias
|
|
221
|
+
self.description = description
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class File:
|
|
225
|
+
"""
|
|
226
|
+
Marker class for file upload parameters.
|
|
227
|
+
|
|
228
|
+
Use this to define parameters that should be extracted from
|
|
229
|
+
multipart/form-data file uploads. The parameter type should be UploadFile.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
default: Default value if file is not provided. Use ... for required files,
|
|
233
|
+
None for optional files.
|
|
234
|
+
description: Optional description for OpenAPI documentation.
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
from tachyon_api.files import UploadFile
|
|
238
|
+
|
|
239
|
+
@app.post("/upload")
|
|
240
|
+
async def upload(file: UploadFile = File(...)):
|
|
241
|
+
content = await file.read()
|
|
242
|
+
return {"filename": file.filename, "size": len(content)}
|
|
243
|
+
|
|
244
|
+
@app.post("/optional-upload")
|
|
245
|
+
async def optional(file: UploadFile = File(None)):
|
|
246
|
+
if file is None:
|
|
247
|
+
return {"uploaded": False}
|
|
248
|
+
return {"uploaded": True, "filename": file.filename}
|
|
249
|
+
|
|
250
|
+
Note:
|
|
251
|
+
- Missing required files return 422 Unprocessable Entity
|
|
252
|
+
- Use UploadFile type annotation for file parameters
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
def __init__(
|
|
256
|
+
self,
|
|
257
|
+
default: Any = ...,
|
|
258
|
+
description: Optional[str] = None,
|
|
259
|
+
):
|
|
260
|
+
"""
|
|
261
|
+
Initialize a File parameter marker.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
default: Default value. Use ... for required, None for optional.
|
|
265
|
+
description: Optional description for API documentation.
|
|
266
|
+
"""
|
|
267
|
+
self.default = default
|
|
268
|
+
self.description = description
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Request/response processing for Tachyon applications.
|
|
3
|
+
|
|
4
|
+
This module contains components for:
|
|
5
|
+
- parameters: Extraction and validation of request parameters
|
|
6
|
+
- dependencies: Dependency injection resolution
|
|
7
|
+
- response_processor: Response validation and serialization
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .parameters import ParameterProcessor
|
|
11
|
+
from .dependencies import DependencyResolver
|
|
12
|
+
from .response_processor import ResponseProcessor
|
|
13
|
+
|
|
14
|
+
__all__ = ["ParameterProcessor", "DependencyResolver", "ResponseProcessor"]
|