turboapi 0.4.15__cp314-cp314t-macosx_11_0_arm64.whl → 0.5.22__cp314-cp314t-macosx_11_0_arm64.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.
- turboapi/__init__.py +125 -8
- turboapi/background.py +51 -0
- turboapi/datastructures.py +262 -0
- turboapi/encoders.py +323 -0
- turboapi/exceptions.py +111 -0
- turboapi/main_app.py +59 -4
- turboapi/middleware.py +4 -6
- turboapi/models.py +15 -33
- turboapi/openapi.py +236 -0
- turboapi/request_handler.py +262 -54
- turboapi/responses.py +209 -0
- turboapi/routing.py +2 -1
- turboapi/rust_integration.py +137 -20
- turboapi/security.py +32 -3
- turboapi/staticfiles.py +91 -0
- turboapi/status.py +104 -0
- turboapi/templating.py +73 -0
- turboapi/testclient.py +321 -0
- turboapi/turbonet.cpython-314t-darwin.so +0 -0
- turboapi/websockets.py +130 -0
- turboapi-0.5.22.dist-info/METADATA +580 -0
- turboapi-0.5.22.dist-info/RECORD +29 -0
- {turboapi-0.4.15.dist-info → turboapi-0.5.22.dist-info}/WHEEL +1 -1
- turboapi-0.5.22.dist-info/licenses/LICENSE +21 -0
- turboapi-0.4.15.dist-info/METADATA +0 -31
- turboapi-0.4.15.dist-info/RECORD +0 -17
turboapi/__init__.py
CHANGED
|
@@ -1,24 +1,141 @@
|
|
|
1
1
|
"""
|
|
2
2
|
TurboAPI - Revolutionary Python web framework
|
|
3
|
-
|
|
3
|
+
FastAPI-compatible API with SIMD-accelerated Rust backend.
|
|
4
|
+
Requires Python 3.13+ free-threading for maximum performance.
|
|
4
5
|
"""
|
|
5
6
|
|
|
6
|
-
#
|
|
7
|
-
from .models import TurboRequest, TurboResponse
|
|
8
|
-
from .routing import APIRouter, Router
|
|
7
|
+
# Core application
|
|
9
8
|
from .rust_integration import TurboAPI
|
|
10
|
-
from .
|
|
9
|
+
from .routing import APIRouter, Router
|
|
10
|
+
from .models import TurboRequest, TurboResponse, Request
|
|
11
|
+
|
|
12
|
+
# Parameter types (FastAPI-compatible)
|
|
13
|
+
from .datastructures import (
|
|
14
|
+
Body,
|
|
15
|
+
Cookie,
|
|
16
|
+
File,
|
|
17
|
+
Form,
|
|
18
|
+
Header,
|
|
19
|
+
Path,
|
|
20
|
+
Query,
|
|
21
|
+
UploadFile,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Response types
|
|
25
|
+
from .responses import (
|
|
26
|
+
FileResponse,
|
|
27
|
+
HTMLResponse,
|
|
28
|
+
JSONResponse,
|
|
29
|
+
PlainTextResponse,
|
|
30
|
+
RedirectResponse,
|
|
31
|
+
Response,
|
|
32
|
+
StreamingResponse,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Security
|
|
36
|
+
from .security import (
|
|
37
|
+
APIKeyCookie,
|
|
38
|
+
APIKeyHeader,
|
|
39
|
+
APIKeyQuery,
|
|
40
|
+
Depends,
|
|
41
|
+
HTTPBasic,
|
|
42
|
+
HTTPBasicCredentials,
|
|
43
|
+
HTTPBearer,
|
|
44
|
+
HTTPException,
|
|
45
|
+
OAuth2AuthorizationCodeBearer,
|
|
46
|
+
OAuth2PasswordBearer,
|
|
47
|
+
Security,
|
|
48
|
+
SecurityScopes,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Exceptions
|
|
52
|
+
from .exceptions import (
|
|
53
|
+
RequestValidationError,
|
|
54
|
+
WebSocketException,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Middleware
|
|
58
|
+
from .middleware import (
|
|
59
|
+
CORSMiddleware,
|
|
60
|
+
GZipMiddleware,
|
|
61
|
+
HTTPSRedirectMiddleware,
|
|
62
|
+
Middleware,
|
|
63
|
+
TrustedHostMiddleware,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Background tasks
|
|
67
|
+
from .background import BackgroundTasks
|
|
68
|
+
|
|
69
|
+
# WebSocket
|
|
70
|
+
from .websockets import WebSocket, WebSocketDisconnect
|
|
71
|
+
|
|
72
|
+
# Encoders
|
|
73
|
+
from .encoders import jsonable_encoder
|
|
74
|
+
|
|
75
|
+
# Status codes module (import as 'status')
|
|
76
|
+
from . import status
|
|
77
|
+
|
|
78
|
+
# Version check
|
|
79
|
+
from .version_check import check_free_threading_support, get_python_threading_info
|
|
11
80
|
|
|
12
81
|
__version__ = "2.0.0"
|
|
13
82
|
__all__ = [
|
|
83
|
+
# Core
|
|
14
84
|
"TurboAPI",
|
|
15
85
|
"APIRouter",
|
|
16
86
|
"Router",
|
|
17
87
|
"TurboRequest",
|
|
18
88
|
"TurboResponse",
|
|
89
|
+
"Request",
|
|
90
|
+
# Parameters
|
|
91
|
+
"Body",
|
|
92
|
+
"Cookie",
|
|
93
|
+
"File",
|
|
94
|
+
"Form",
|
|
95
|
+
"Header",
|
|
96
|
+
"Path",
|
|
97
|
+
"Query",
|
|
98
|
+
"UploadFile",
|
|
99
|
+
# Responses
|
|
100
|
+
"FileResponse",
|
|
101
|
+
"HTMLResponse",
|
|
102
|
+
"JSONResponse",
|
|
103
|
+
"PlainTextResponse",
|
|
104
|
+
"RedirectResponse",
|
|
105
|
+
"Response",
|
|
106
|
+
"StreamingResponse",
|
|
107
|
+
# Security
|
|
108
|
+
"APIKeyCookie",
|
|
109
|
+
"APIKeyHeader",
|
|
110
|
+
"APIKeyQuery",
|
|
111
|
+
"Depends",
|
|
112
|
+
"HTTPBasic",
|
|
113
|
+
"HTTPBasicCredentials",
|
|
114
|
+
"HTTPBearer",
|
|
115
|
+
"HTTPException",
|
|
116
|
+
"OAuth2AuthorizationCodeBearer",
|
|
117
|
+
"OAuth2PasswordBearer",
|
|
118
|
+
"Security",
|
|
119
|
+
"SecurityScopes",
|
|
120
|
+
# Exceptions
|
|
121
|
+
"RequestValidationError",
|
|
122
|
+
"WebSocketException",
|
|
123
|
+
# Middleware
|
|
124
|
+
"CORSMiddleware",
|
|
125
|
+
"GZipMiddleware",
|
|
126
|
+
"HTTPSRedirectMiddleware",
|
|
127
|
+
"Middleware",
|
|
128
|
+
"TrustedHostMiddleware",
|
|
129
|
+
# Background tasks
|
|
130
|
+
"BackgroundTasks",
|
|
131
|
+
# WebSocket
|
|
132
|
+
"WebSocket",
|
|
133
|
+
"WebSocketDisconnect",
|
|
134
|
+
# Encoders
|
|
135
|
+
"jsonable_encoder",
|
|
136
|
+
# Status module
|
|
137
|
+
"status",
|
|
138
|
+
# Utils
|
|
19
139
|
"check_free_threading_support",
|
|
20
140
|
"get_python_threading_info",
|
|
21
141
|
]
|
|
22
|
-
|
|
23
|
-
# Additional exports for free-threading diagnostics
|
|
24
|
-
from .version_check import get_python_threading_info
|
turboapi/background.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Background tasks support for TurboAPI.
|
|
2
|
+
|
|
3
|
+
FastAPI-compatible BackgroundTasks class that runs functions after the response is sent.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import inspect
|
|
8
|
+
from typing import Any, Callable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BackgroundTasks:
|
|
12
|
+
"""A collection of background tasks to run after the response is sent.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
@app.post("/send-notification")
|
|
16
|
+
async def send_notification(background_tasks: BackgroundTasks):
|
|
17
|
+
background_tasks.add_task(send_email, "user@example.com", message="Hello")
|
|
18
|
+
return {"message": "Notification sent in the background"}
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self._tasks: list[tuple[Callable, tuple, dict]] = []
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def tasks(self) -> list[tuple[Callable, tuple, dict]]:
|
|
26
|
+
"""Return the list of tasks (FastAPI compatibility)."""
|
|
27
|
+
return self._tasks
|
|
28
|
+
|
|
29
|
+
def add_task(self, func: Callable, *args: Any, **kwargs: Any) -> None:
|
|
30
|
+
"""Add a task to be run in the background after the response is sent."""
|
|
31
|
+
self._tasks.append((func, args, kwargs))
|
|
32
|
+
|
|
33
|
+
async def __call__(self) -> None:
|
|
34
|
+
"""Execute all background tasks."""
|
|
35
|
+
for func, args, kwargs in self._tasks:
|
|
36
|
+
if inspect.iscoroutinefunction(func):
|
|
37
|
+
await func(*args, **kwargs)
|
|
38
|
+
else:
|
|
39
|
+
func(*args, **kwargs)
|
|
40
|
+
|
|
41
|
+
def run_tasks(self) -> None:
|
|
42
|
+
"""Run all tasks synchronously or in an event loop."""
|
|
43
|
+
for func, args, kwargs in self._tasks:
|
|
44
|
+
if inspect.iscoroutinefunction(func):
|
|
45
|
+
try:
|
|
46
|
+
loop = asyncio.get_running_loop()
|
|
47
|
+
loop.create_task(func(*args, **kwargs))
|
|
48
|
+
except RuntimeError:
|
|
49
|
+
asyncio.run(func(*args, **kwargs))
|
|
50
|
+
else:
|
|
51
|
+
func(*args, **kwargs)
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""Data structures for TurboAPI - Form, File, UploadFile.
|
|
2
|
+
|
|
3
|
+
FastAPI-compatible parameter markers and file handling classes.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import io
|
|
7
|
+
import tempfile
|
|
8
|
+
from typing import Any, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Form:
|
|
12
|
+
"""Marker class for form data parameters.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
@app.post("/login")
|
|
16
|
+
async def login(username: str = Form(), password: str = Form()):
|
|
17
|
+
return {"username": username}
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
default: Any = ...,
|
|
23
|
+
*,
|
|
24
|
+
alias: Optional[str] = None,
|
|
25
|
+
title: Optional[str] = None,
|
|
26
|
+
description: Optional[str] = None,
|
|
27
|
+
min_length: Optional[int] = None,
|
|
28
|
+
max_length: Optional[int] = None,
|
|
29
|
+
regex: Optional[str] = None,
|
|
30
|
+
media_type: str = "application/x-www-form-urlencoded",
|
|
31
|
+
):
|
|
32
|
+
self.default = default
|
|
33
|
+
self.alias = alias
|
|
34
|
+
self.title = title
|
|
35
|
+
self.description = description
|
|
36
|
+
self.min_length = min_length
|
|
37
|
+
self.max_length = max_length
|
|
38
|
+
self.regex = regex
|
|
39
|
+
self.media_type = media_type
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class File:
|
|
43
|
+
"""Marker class for file upload parameters.
|
|
44
|
+
|
|
45
|
+
Usage:
|
|
46
|
+
@app.post("/upload")
|
|
47
|
+
async def upload(file: bytes = File()):
|
|
48
|
+
return {"file_size": len(file)}
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
default: Any = ...,
|
|
54
|
+
*,
|
|
55
|
+
alias: Optional[str] = None,
|
|
56
|
+
title: Optional[str] = None,
|
|
57
|
+
description: Optional[str] = None,
|
|
58
|
+
max_length: Optional[int] = None,
|
|
59
|
+
media_type: str = "multipart/form-data",
|
|
60
|
+
):
|
|
61
|
+
self.default = default
|
|
62
|
+
self.alias = alias
|
|
63
|
+
self.title = title
|
|
64
|
+
self.description = description
|
|
65
|
+
self.max_length = max_length
|
|
66
|
+
self.media_type = media_type
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class UploadFile:
|
|
70
|
+
"""Represents an uploaded file.
|
|
71
|
+
|
|
72
|
+
Usage:
|
|
73
|
+
@app.post("/upload")
|
|
74
|
+
async def upload(file: UploadFile):
|
|
75
|
+
contents = await file.read()
|
|
76
|
+
return {"filename": file.filename, "size": len(contents)}
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
filename: Optional[str] = None,
|
|
82
|
+
file: Optional[io.IOBase] = None,
|
|
83
|
+
content_type: str = "application/octet-stream",
|
|
84
|
+
*,
|
|
85
|
+
size: Optional[int] = None,
|
|
86
|
+
headers: Optional[dict] = None,
|
|
87
|
+
):
|
|
88
|
+
self.filename = filename
|
|
89
|
+
self.content_type = content_type
|
|
90
|
+
self.size = size
|
|
91
|
+
self.headers = headers or {}
|
|
92
|
+
if file is None:
|
|
93
|
+
self.file = tempfile.SpooledTemporaryFile(max_size=1024 * 1024)
|
|
94
|
+
else:
|
|
95
|
+
self.file = file
|
|
96
|
+
|
|
97
|
+
async def read(self, size: int = -1) -> bytes:
|
|
98
|
+
"""Read file contents."""
|
|
99
|
+
if hasattr(self.file, "read"):
|
|
100
|
+
return self.file.read(size)
|
|
101
|
+
return b""
|
|
102
|
+
|
|
103
|
+
async def write(self, data: bytes) -> None:
|
|
104
|
+
"""Write data to the file."""
|
|
105
|
+
if hasattr(self.file, "write"):
|
|
106
|
+
self.file.write(data)
|
|
107
|
+
|
|
108
|
+
async def seek(self, offset: int) -> None:
|
|
109
|
+
"""Seek to a position in the file."""
|
|
110
|
+
if hasattr(self.file, "seek"):
|
|
111
|
+
self.file.seek(offset)
|
|
112
|
+
|
|
113
|
+
async def close(self) -> None:
|
|
114
|
+
"""Close the file."""
|
|
115
|
+
if hasattr(self.file, "close"):
|
|
116
|
+
self.file.close()
|
|
117
|
+
|
|
118
|
+
def __repr__(self) -> str:
|
|
119
|
+
return f"UploadFile(filename={self.filename!r}, content_type={self.content_type!r}, size={self.size})"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class Header:
|
|
123
|
+
"""Marker class for header parameters.
|
|
124
|
+
|
|
125
|
+
Usage:
|
|
126
|
+
@app.get("/items")
|
|
127
|
+
async def read_items(x_token: str = Header()):
|
|
128
|
+
return {"X-Token": x_token}
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
def __init__(
|
|
132
|
+
self,
|
|
133
|
+
default: Any = ...,
|
|
134
|
+
*,
|
|
135
|
+
alias: Optional[str] = None,
|
|
136
|
+
title: Optional[str] = None,
|
|
137
|
+
description: Optional[str] = None,
|
|
138
|
+
convert_underscores: bool = True,
|
|
139
|
+
):
|
|
140
|
+
self.default = default
|
|
141
|
+
self.alias = alias
|
|
142
|
+
self.title = title
|
|
143
|
+
self.description = description
|
|
144
|
+
self.convert_underscores = convert_underscores
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class Cookie:
|
|
148
|
+
"""Marker class for cookie parameters.
|
|
149
|
+
|
|
150
|
+
Usage:
|
|
151
|
+
@app.get("/items")
|
|
152
|
+
async def read_items(session_id: str = Cookie()):
|
|
153
|
+
return {"session_id": session_id}
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
def __init__(
|
|
157
|
+
self,
|
|
158
|
+
default: Any = ...,
|
|
159
|
+
*,
|
|
160
|
+
alias: Optional[str] = None,
|
|
161
|
+
title: Optional[str] = None,
|
|
162
|
+
description: Optional[str] = None,
|
|
163
|
+
):
|
|
164
|
+
self.default = default
|
|
165
|
+
self.alias = alias
|
|
166
|
+
self.title = title
|
|
167
|
+
self.description = description
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class Query:
|
|
171
|
+
"""Marker class for query parameters with validation.
|
|
172
|
+
|
|
173
|
+
Usage:
|
|
174
|
+
@app.get("/items")
|
|
175
|
+
async def read_items(q: str = Query(min_length=3)):
|
|
176
|
+
return {"q": q}
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
def __init__(
|
|
180
|
+
self,
|
|
181
|
+
default: Any = ...,
|
|
182
|
+
*,
|
|
183
|
+
alias: Optional[str] = None,
|
|
184
|
+
title: Optional[str] = None,
|
|
185
|
+
description: Optional[str] = None,
|
|
186
|
+
min_length: Optional[int] = None,
|
|
187
|
+
max_length: Optional[int] = None,
|
|
188
|
+
regex: Optional[str] = None,
|
|
189
|
+
gt: Optional[float] = None,
|
|
190
|
+
ge: Optional[float] = None,
|
|
191
|
+
lt: Optional[float] = None,
|
|
192
|
+
le: Optional[float] = None,
|
|
193
|
+
):
|
|
194
|
+
self.default = default
|
|
195
|
+
self.alias = alias
|
|
196
|
+
self.title = title
|
|
197
|
+
self.description = description
|
|
198
|
+
self.min_length = min_length
|
|
199
|
+
self.max_length = max_length
|
|
200
|
+
self.regex = regex
|
|
201
|
+
self.gt = gt
|
|
202
|
+
self.ge = ge
|
|
203
|
+
self.lt = lt
|
|
204
|
+
self.le = le
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class Path:
|
|
208
|
+
"""Marker class for path parameters with validation.
|
|
209
|
+
|
|
210
|
+
Usage:
|
|
211
|
+
@app.get("/items/{item_id}")
|
|
212
|
+
async def read_item(item_id: int = Path(gt=0)):
|
|
213
|
+
return {"item_id": item_id}
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
def __init__(
|
|
217
|
+
self,
|
|
218
|
+
default: Any = ...,
|
|
219
|
+
*,
|
|
220
|
+
alias: Optional[str] = None,
|
|
221
|
+
title: Optional[str] = None,
|
|
222
|
+
description: Optional[str] = None,
|
|
223
|
+
gt: Optional[float] = None,
|
|
224
|
+
ge: Optional[float] = None,
|
|
225
|
+
lt: Optional[float] = None,
|
|
226
|
+
le: Optional[float] = None,
|
|
227
|
+
):
|
|
228
|
+
self.default = default
|
|
229
|
+
self.alias = alias
|
|
230
|
+
self.title = title
|
|
231
|
+
self.description = description
|
|
232
|
+
self.gt = gt
|
|
233
|
+
self.ge = ge
|
|
234
|
+
self.lt = lt
|
|
235
|
+
self.le = le
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class Body:
|
|
239
|
+
"""Marker class for body parameters.
|
|
240
|
+
|
|
241
|
+
Usage:
|
|
242
|
+
@app.post("/items")
|
|
243
|
+
async def create_item(name: str = Body(), price: float = Body()):
|
|
244
|
+
return {"name": name, "price": price}
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
def __init__(
|
|
248
|
+
self,
|
|
249
|
+
default: Any = ...,
|
|
250
|
+
*,
|
|
251
|
+
embed: bool = False,
|
|
252
|
+
alias: Optional[str] = None,
|
|
253
|
+
title: Optional[str] = None,
|
|
254
|
+
description: Optional[str] = None,
|
|
255
|
+
media_type: str = "application/json",
|
|
256
|
+
):
|
|
257
|
+
self.default = default
|
|
258
|
+
self.embed = embed
|
|
259
|
+
self.alias = alias
|
|
260
|
+
self.title = title
|
|
261
|
+
self.description = description
|
|
262
|
+
self.media_type = media_type
|