bustapi 0.1.0__cp311-cp311-win_amd64.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.
Potentially problematic release.
This version of bustapi might be problematic. Click here for more details.
- bustapi/__init__.py +96 -0
- bustapi/app.py +552 -0
- bustapi/blueprints.py +500 -0
- bustapi/bustapi_core.cp311-win_amd64.pyd +0 -0
- bustapi/exceptions.py +438 -0
- bustapi/flask_compat.py +62 -0
- bustapi/helpers.py +370 -0
- bustapi/py.typed +0 -0
- bustapi/request.py +378 -0
- bustapi/response.py +406 -0
- bustapi/testing.py +375 -0
- bustapi-0.1.0.dist-info/METADATA +233 -0
- bustapi-0.1.0.dist-info/RECORD +16 -0
- bustapi-0.1.0.dist-info/WHEEL +4 -0
- bustapi-0.1.0.dist-info/entry_points.txt +2 -0
- bustapi-0.1.0.dist-info/licenses/LICENSE +21 -0
bustapi/testing.py
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Testing utilities for BustAPI - Flask-compatible test client
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any, Dict, Optional, Union
|
|
7
|
+
from urllib.parse import urlencode
|
|
8
|
+
|
|
9
|
+
from werkzeug.datastructures import Headers
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestResponse:
|
|
13
|
+
"""
|
|
14
|
+
Test response object for BustAPI test client.
|
|
15
|
+
|
|
16
|
+
Provides access to response data, status, and headers for testing.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, response_data: bytes, status_code: int, headers: Dict[str, str]):
|
|
20
|
+
"""
|
|
21
|
+
Initialize test response.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
response_data: Response body data
|
|
25
|
+
status_code: HTTP status code
|
|
26
|
+
headers: Response headers
|
|
27
|
+
"""
|
|
28
|
+
self.data = response_data
|
|
29
|
+
self.status_code = status_code
|
|
30
|
+
self.headers = Headers(headers)
|
|
31
|
+
self._json_cache = None
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def status(self) -> str:
|
|
35
|
+
"""Status code and reason phrase."""
|
|
36
|
+
return f"{self.status_code}"
|
|
37
|
+
|
|
38
|
+
def get_data(self, as_text: bool = False) -> Union[bytes, str]:
|
|
39
|
+
"""
|
|
40
|
+
Get response data.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
as_text: Return as text instead of bytes
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Response data as bytes or text
|
|
47
|
+
"""
|
|
48
|
+
if as_text:
|
|
49
|
+
return self.data.decode("utf-8", errors="replace")
|
|
50
|
+
return self.data
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def text(self) -> str:
|
|
54
|
+
"""Response data as text."""
|
|
55
|
+
return self.get_data(as_text=True)
|
|
56
|
+
|
|
57
|
+
def get_json(self, force: bool = False, silent: bool = False) -> Optional[Any]:
|
|
58
|
+
"""
|
|
59
|
+
Parse response data as JSON.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
force: Force parsing even without JSON content type
|
|
63
|
+
silent: Don't raise exception on parse error
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Parsed JSON data or None
|
|
67
|
+
"""
|
|
68
|
+
if self._json_cache is not None:
|
|
69
|
+
return self._json_cache
|
|
70
|
+
|
|
71
|
+
content_type = self.headers.get("Content-Type", "")
|
|
72
|
+
if not force and "application/json" not in content_type.lower():
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
self._json_cache = json.loads(self.get_data(as_text=True))
|
|
77
|
+
return self._json_cache
|
|
78
|
+
except (ValueError, TypeError):
|
|
79
|
+
if not silent:
|
|
80
|
+
raise
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def json(self) -> Optional[Any]:
|
|
85
|
+
"""Response data as JSON (cached)."""
|
|
86
|
+
return self.get_json()
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def is_json(self) -> bool:
|
|
90
|
+
"""Check if response has JSON content type."""
|
|
91
|
+
content_type = self.headers.get("Content-Type", "")
|
|
92
|
+
return "application/json" in content_type.lower()
|
|
93
|
+
|
|
94
|
+
def __repr__(self) -> str:
|
|
95
|
+
return f'<TestResponse {self.status_code} [{self.headers.get("Content-Type", "")}]>'
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class TestClient:
|
|
99
|
+
"""
|
|
100
|
+
Test client for BustAPI applications (Flask-compatible).
|
|
101
|
+
|
|
102
|
+
Provides methods to make test requests to the application without
|
|
103
|
+
starting a real HTTP server.
|
|
104
|
+
|
|
105
|
+
Example:
|
|
106
|
+
app = BustAPI()
|
|
107
|
+
|
|
108
|
+
@app.route('/test')
|
|
109
|
+
def test():
|
|
110
|
+
return {'message': 'Hello, Test!'}
|
|
111
|
+
|
|
112
|
+
with app.test_client() as client:
|
|
113
|
+
resp = client.get('/test')
|
|
114
|
+
assert resp.status_code == 200
|
|
115
|
+
assert resp.json['message'] == 'Hello, Test!'
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def __init__(
|
|
119
|
+
self,
|
|
120
|
+
application,
|
|
121
|
+
response_wrapper=None,
|
|
122
|
+
use_cookies=True,
|
|
123
|
+
allow_subdomain_redirects=False,
|
|
124
|
+
):
|
|
125
|
+
"""
|
|
126
|
+
Initialize test client.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
application: BustAPI application instance
|
|
130
|
+
response_wrapper: Custom response wrapper class
|
|
131
|
+
use_cookies: Enable cookie support
|
|
132
|
+
allow_subdomain_redirects: Allow subdomain redirects
|
|
133
|
+
"""
|
|
134
|
+
self.application = application
|
|
135
|
+
self.response_wrapper = response_wrapper or TestResponse
|
|
136
|
+
self.use_cookies = use_cookies
|
|
137
|
+
self.allow_subdomain_redirects = allow_subdomain_redirects
|
|
138
|
+
|
|
139
|
+
# Cookie jar for session management
|
|
140
|
+
self.cookie_jar: Dict[str, str] = {}
|
|
141
|
+
|
|
142
|
+
def open(
|
|
143
|
+
self,
|
|
144
|
+
path: str,
|
|
145
|
+
method: str = "GET",
|
|
146
|
+
data: Optional[Union[str, bytes, dict]] = None,
|
|
147
|
+
query_string: Optional[Union[str, dict]] = None,
|
|
148
|
+
headers: Optional[Dict[str, str]] = None,
|
|
149
|
+
content_type: Optional[str] = None,
|
|
150
|
+
**kwargs,
|
|
151
|
+
) -> TestResponse:
|
|
152
|
+
"""
|
|
153
|
+
Make a test request to the application.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
path: Request path
|
|
157
|
+
method: HTTP method
|
|
158
|
+
data: Request data
|
|
159
|
+
query_string: Query parameters
|
|
160
|
+
headers: Request headers
|
|
161
|
+
content_type: Content type header
|
|
162
|
+
**kwargs: Additional arguments
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
TestResponse object
|
|
166
|
+
"""
|
|
167
|
+
headers = headers or {}
|
|
168
|
+
|
|
169
|
+
# Set content type if provided
|
|
170
|
+
if content_type:
|
|
171
|
+
headers["Content-Type"] = content_type
|
|
172
|
+
|
|
173
|
+
# Handle query string
|
|
174
|
+
if query_string:
|
|
175
|
+
if isinstance(query_string, dict):
|
|
176
|
+
query_string = urlencode(query_string)
|
|
177
|
+
if "?" in path:
|
|
178
|
+
path += "&" + query_string
|
|
179
|
+
else:
|
|
180
|
+
path += "?" + query_string
|
|
181
|
+
|
|
182
|
+
# Handle request data
|
|
183
|
+
if data is not None:
|
|
184
|
+
if isinstance(data, dict):
|
|
185
|
+
# If data is dict and no content type specified, assume form data
|
|
186
|
+
if "Content-Type" not in headers:
|
|
187
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
|
188
|
+
data = urlencode(data)
|
|
189
|
+
elif headers.get("Content-Type") == "application/json":
|
|
190
|
+
data = json.dumps(data)
|
|
191
|
+
|
|
192
|
+
if isinstance(data, str):
|
|
193
|
+
data = data.encode("utf-8")
|
|
194
|
+
|
|
195
|
+
# Add cookies to headers
|
|
196
|
+
if self.use_cookies and self.cookie_jar:
|
|
197
|
+
cookie_header = "; ".join([f"{k}={v}" for k, v in self.cookie_jar.items()])
|
|
198
|
+
headers["Cookie"] = cookie_header
|
|
199
|
+
|
|
200
|
+
# Create mock request object for the Rust backend
|
|
201
|
+
# TODO: This is a simplified mock - in a full implementation,
|
|
202
|
+
# we would need to properly interface with the Rust backend
|
|
203
|
+
# or create a test mode that bypasses the network layer
|
|
204
|
+
|
|
205
|
+
mock_request = MockRequest(method, path, headers, data or b"")
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
# Call application handler directly
|
|
209
|
+
# This is a simplified approach - real implementation would
|
|
210
|
+
# need proper integration with the Rust backend
|
|
211
|
+
response_data = self._call_application(mock_request)
|
|
212
|
+
|
|
213
|
+
# Parse response
|
|
214
|
+
status_code = getattr(response_data, "status_code", 200)
|
|
215
|
+
response_headers = getattr(response_data, "headers", {})
|
|
216
|
+
body_data = getattr(response_data, "data", b"")
|
|
217
|
+
|
|
218
|
+
# Update cookies from response
|
|
219
|
+
if self.use_cookies and "Set-Cookie" in response_headers:
|
|
220
|
+
self._update_cookies(response_headers["Set-Cookie"])
|
|
221
|
+
|
|
222
|
+
return self.response_wrapper(body_data, status_code, response_headers)
|
|
223
|
+
|
|
224
|
+
except Exception as e:
|
|
225
|
+
# Return error response
|
|
226
|
+
error_msg = f"Test client error: {str(e)}"
|
|
227
|
+
return self.response_wrapper(error_msg.encode(), 500, {})
|
|
228
|
+
|
|
229
|
+
def _call_application(self, request):
|
|
230
|
+
"""
|
|
231
|
+
Call application with mock request (simplified implementation).
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
request: Mock request object
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Response object
|
|
238
|
+
"""
|
|
239
|
+
# TODO: Implement proper test request handling
|
|
240
|
+
# This would need to interface with the application's route handlers
|
|
241
|
+
# without going through the Rust HTTP server
|
|
242
|
+
|
|
243
|
+
# For now, return a mock response
|
|
244
|
+
from .response import Response
|
|
245
|
+
|
|
246
|
+
return Response("Test response", status=200)
|
|
247
|
+
|
|
248
|
+
def _update_cookies(self, set_cookie_header: str):
|
|
249
|
+
"""
|
|
250
|
+
Update cookie jar from Set-Cookie header.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
set_cookie_header: Set-Cookie header value
|
|
254
|
+
"""
|
|
255
|
+
# Simple cookie parsing - real implementation would be more robust
|
|
256
|
+
if "=" in set_cookie_header:
|
|
257
|
+
cookie_parts = set_cookie_header.split(";")[0] # Get main cookie part
|
|
258
|
+
if "=" in cookie_parts:
|
|
259
|
+
name, value = cookie_parts.split("=", 1)
|
|
260
|
+
self.cookie_jar[name.strip()] = value.strip()
|
|
261
|
+
|
|
262
|
+
def get(self, path: str, **kwargs) -> TestResponse:
|
|
263
|
+
"""Make GET request."""
|
|
264
|
+
return self.open(path, method="GET", **kwargs)
|
|
265
|
+
|
|
266
|
+
def post(self, path: str, **kwargs) -> TestResponse:
|
|
267
|
+
"""Make POST request."""
|
|
268
|
+
return self.open(path, method="POST", **kwargs)
|
|
269
|
+
|
|
270
|
+
def put(self, path: str, **kwargs) -> TestResponse:
|
|
271
|
+
"""Make PUT request."""
|
|
272
|
+
return self.open(path, method="PUT", **kwargs)
|
|
273
|
+
|
|
274
|
+
def delete(self, path: str, **kwargs) -> TestResponse:
|
|
275
|
+
"""Make DELETE request."""
|
|
276
|
+
return self.open(path, method="DELETE", **kwargs)
|
|
277
|
+
|
|
278
|
+
def patch(self, path: str, **kwargs) -> TestResponse:
|
|
279
|
+
"""Make PATCH request."""
|
|
280
|
+
return self.open(path, method="PATCH", **kwargs)
|
|
281
|
+
|
|
282
|
+
def head(self, path: str, **kwargs) -> TestResponse:
|
|
283
|
+
"""Make HEAD request."""
|
|
284
|
+
return self.open(path, method="HEAD", **kwargs)
|
|
285
|
+
|
|
286
|
+
def options(self, path: str, **kwargs) -> TestResponse:
|
|
287
|
+
"""Make OPTIONS request."""
|
|
288
|
+
return self.open(path, method="OPTIONS", **kwargs)
|
|
289
|
+
|
|
290
|
+
def trace(self, path: str, **kwargs) -> TestResponse:
|
|
291
|
+
"""Make TRACE request."""
|
|
292
|
+
return self.open(path, method="TRACE", **kwargs)
|
|
293
|
+
|
|
294
|
+
# Context manager support
|
|
295
|
+
def __enter__(self):
|
|
296
|
+
return self
|
|
297
|
+
|
|
298
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
299
|
+
# Cleanup if needed
|
|
300
|
+
self.cookie_jar.clear()
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class MockRequest:
|
|
304
|
+
"""
|
|
305
|
+
Mock request object for testing.
|
|
306
|
+
"""
|
|
307
|
+
|
|
308
|
+
def __init__(self, method: str, path: str, headers: Dict[str, str], body: bytes):
|
|
309
|
+
self.method = method
|
|
310
|
+
self.path = path
|
|
311
|
+
self.headers = headers
|
|
312
|
+
self.body = body
|
|
313
|
+
|
|
314
|
+
# Parse query string from path
|
|
315
|
+
if "?" in path:
|
|
316
|
+
self.path, query_string = path.split("?", 1)
|
|
317
|
+
self.query_params = dict(
|
|
318
|
+
item.split("=", 1) if "=" in item else (item, "")
|
|
319
|
+
for item in query_string.split("&")
|
|
320
|
+
if item
|
|
321
|
+
)
|
|
322
|
+
else:
|
|
323
|
+
self.query_params = {}
|
|
324
|
+
|
|
325
|
+
def get_header(self, name: str) -> Optional[str]:
|
|
326
|
+
"""Get header value (case-insensitive)."""
|
|
327
|
+
name_lower = name.lower()
|
|
328
|
+
for key, value in self.headers.items():
|
|
329
|
+
if key.lower() == name_lower:
|
|
330
|
+
return value
|
|
331
|
+
return None
|
|
332
|
+
|
|
333
|
+
def is_json(self) -> bool:
|
|
334
|
+
"""Check if request has JSON content type."""
|
|
335
|
+
content_type = self.get_header("content-type") or ""
|
|
336
|
+
return "application/json" in content_type.lower()
|
|
337
|
+
|
|
338
|
+
def get_json(self):
|
|
339
|
+
"""Get request body as JSON."""
|
|
340
|
+
if not self.is_json():
|
|
341
|
+
return None
|
|
342
|
+
try:
|
|
343
|
+
return json.loads(self.body.decode("utf-8"))
|
|
344
|
+
except (ValueError, UnicodeDecodeError):
|
|
345
|
+
return None
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# Flask-compatible test utilities
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def make_test_environ_builder(*args, **kwargs):
|
|
352
|
+
"""
|
|
353
|
+
Create test environ builder (Flask compatibility placeholder).
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
Mock environ builder
|
|
357
|
+
"""
|
|
358
|
+
# TODO: Implement proper environ builder for WSGI compatibility
|
|
359
|
+
return {}
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class FlaskClient(TestClient):
|
|
363
|
+
"""Alias for Flask compatibility."""
|
|
364
|
+
|
|
365
|
+
pass
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
# Re-export for convenience
|
|
369
|
+
__all__ = [
|
|
370
|
+
"TestClient",
|
|
371
|
+
"TestResponse",
|
|
372
|
+
"FlaskClient",
|
|
373
|
+
"MockRequest",
|
|
374
|
+
"make_test_environ_builder",
|
|
375
|
+
]
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bustapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Rust
|
|
14
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
16
|
+
Requires-Dist: black>=24.8.0
|
|
17
|
+
Requires-Dist: isort>=5.13.2
|
|
18
|
+
Requires-Dist: maturin>=1.9.3
|
|
19
|
+
Requires-Dist: pytest>=8.3.5
|
|
20
|
+
Requires-Dist: pytest-asyncio>=0.24.0
|
|
21
|
+
Requires-Dist: pytest-cov>=5.0.0
|
|
22
|
+
Requires-Dist: ruff>=0.12.10
|
|
23
|
+
Requires-Dist: typing-extensions>=4.0.0
|
|
24
|
+
Requires-Dist: werkzeug>=2.0.0
|
|
25
|
+
Requires-Dist: pytest>=7.0 ; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.21.0 ; extra == 'dev'
|
|
27
|
+
Requires-Dist: black>=22.0 ; extra == 'dev'
|
|
28
|
+
Requires-Dist: mypy>=1.0 ; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.1.0 ; extra == 'dev'
|
|
30
|
+
Requires-Dist: pre-commit>=3.0 ; extra == 'dev'
|
|
31
|
+
Requires-Dist: maturin>=1.0 ; extra == 'dev'
|
|
32
|
+
Requires-Dist: mkdocs>=1.5 ; extra == 'docs'
|
|
33
|
+
Requires-Dist: mkdocs-material>=9.0 ; extra == 'docs'
|
|
34
|
+
Requires-Dist: mkdocstrings[python]>=0.20 ; extra == 'docs'
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Provides-Extra: docs
|
|
37
|
+
License-File: LICENSE
|
|
38
|
+
Summary: High-performance Flask-compatible web framework with async support
|
|
39
|
+
Keywords: web,framework,async,performance,flask
|
|
40
|
+
Author-email: BustAPI Team <hello@bustapi.dev>
|
|
41
|
+
License: MIT
|
|
42
|
+
Requires-Python: >=3.8
|
|
43
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
44
|
+
Project-URL: Homepage, https://github.com/bustapi/bustapi
|
|
45
|
+
Project-URL: Documentation, https://bustapi.dev
|
|
46
|
+
Project-URL: Repository, https://github.com/bustapi/bustapi.git
|
|
47
|
+
Project-URL: Issues, https://github.com/bustapi/bustapi/issues
|
|
48
|
+
|
|
49
|
+
# BustAPI 🚀
|
|
50
|
+
|
|
51
|
+
[](https://badge.fury.io/py/bustapi)
|
|
52
|
+
[](https://www.python.org/downloads/)
|
|
53
|
+
[](https://opensource.org/licenses/MIT)
|
|
54
|
+
|
|
55
|
+
**BustAPI** is a high-performance, Flask-compatible Python web framework built with a Rust backend using PyO3. It combines Flask's simplicity with modern async capabilities and superior performance.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
<h1>NOTE</h1>
|
|
59
|
+
- Made by AI , so Docs are not valid 100%
|
|
60
|
+
|
|
61
|
+
## ✨ Features
|
|
62
|
+
|
|
63
|
+
- 🏃♂️ **High Performance**: Built with Rust for maximum speed (50,000+ RPS)
|
|
64
|
+
- 🔄 **Hybrid Async/Sync**: Seamlessly support both programming models
|
|
65
|
+
- 🔌 **Flask Compatible**: Drop-in replacement with same API
|
|
66
|
+
- 🛠️ **Extension Support**: Works with Flask-CORS, SQLAlchemy, Login, JWT-Extended
|
|
67
|
+
- 🔥 **Hot Reload**: File watching with automatic restart
|
|
68
|
+
- 🛡️ **Production Ready**: Built-in security, monitoring, and cross-platform support
|
|
69
|
+
- 📦 **Modern Tooling**: UV packaging, fast builds, automated releases
|
|
70
|
+
|
|
71
|
+
## 🚀 Quick Start
|
|
72
|
+
|
|
73
|
+
### Installation
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install bustapi
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Hello World
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from bustapi import BustAPI
|
|
83
|
+
|
|
84
|
+
app = BustAPI()
|
|
85
|
+
|
|
86
|
+
@app.route('/')
|
|
87
|
+
def hello():
|
|
88
|
+
return {'message': 'Hello, World!'}
|
|
89
|
+
|
|
90
|
+
@app.route('/async')
|
|
91
|
+
async def async_hello():
|
|
92
|
+
return {'message': 'Async Hello!'}
|
|
93
|
+
|
|
94
|
+
if __name__ == '__main__':
|
|
95
|
+
app.run(debug=True)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Flask Migration
|
|
99
|
+
|
|
100
|
+
BustAPI is designed as a drop-in replacement for Flask:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
# Just change the import!
|
|
104
|
+
# from flask import Flask
|
|
105
|
+
from bustapi import BustAPI as Flask
|
|
106
|
+
|
|
107
|
+
app = Flask(__name__)
|
|
108
|
+
# Rest of your Flask code works unchanged!
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 🔧 Development
|
|
112
|
+
|
|
113
|
+
### Prerequisites
|
|
114
|
+
|
|
115
|
+
- Python 3.8+
|
|
116
|
+
- Rust 1.70+
|
|
117
|
+
- UV (for package management)
|
|
118
|
+
|
|
119
|
+
### Setup
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
git clone https://github.com/grandpaej/bustapi.git
|
|
123
|
+
cd bustapi
|
|
124
|
+
uv sync --extra dev
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Build
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
maturin develop
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Run Tests
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
pytest
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Run Benchmarks
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
python benchmarks/benchmark_core.py
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## 📊 Performance
|
|
146
|
+
|
|
147
|
+
BustAPI targets exceptional performance:
|
|
148
|
+
|
|
149
|
+
| Framework | Requests/sec | Memory Usage | Latency P99 |
|
|
150
|
+
|-----------|--------------|--------------|-------------|
|
|
151
|
+
| BustAPI | 50,000+ | <50MB | <10ms |
|
|
152
|
+
| Flask | 5,000-10,000 | 80-120MB | 50-100ms |
|
|
153
|
+
| FastAPI | 20,000-30,000| 60-90MB | 20-30ms |
|
|
154
|
+
|
|
155
|
+
## 🔌 Flask Extension Compatibility
|
|
156
|
+
|
|
157
|
+
BustAPI works with popular Flask extensions:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from flask_cors import CORS
|
|
161
|
+
from flask_sqlalchemy import SQLAlchemy
|
|
162
|
+
from bustapi import BustAPI
|
|
163
|
+
|
|
164
|
+
app = BustAPI()
|
|
165
|
+
|
|
166
|
+
# Flask extensions work without changes
|
|
167
|
+
CORS(app)
|
|
168
|
+
db = SQLAlchemy(app)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Supported Extensions:**
|
|
172
|
+
- ✅ Flask-CORS
|
|
173
|
+
- ✅ Flask-SQLAlchemy
|
|
174
|
+
- ✅ Flask-Login
|
|
175
|
+
- ✅ Flask-JWT-Extended
|
|
176
|
+
- 🔄 More extensions being added
|
|
177
|
+
|
|
178
|
+
## 🛠️ CLI Usage
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Run application
|
|
182
|
+
bustapi run app:app
|
|
183
|
+
|
|
184
|
+
# Run with hot reload
|
|
185
|
+
bustapi run --reload --debug app:app
|
|
186
|
+
|
|
187
|
+
# Initialize new project
|
|
188
|
+
bustapi init myproject
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## 📚 Documentation
|
|
192
|
+
|
|
193
|
+
- [Quick Start Guide](https://bustapi.dev/quickstart)
|
|
194
|
+
- [API Reference](https://bustapi.dev/api)
|
|
195
|
+
- [Migration from Flask](https://bustapi.dev/migration)
|
|
196
|
+
- [Performance Guide](https://bustapi.dev/performance)
|
|
197
|
+
- [Extension Development](https://bustapi.dev/extensions)
|
|
198
|
+
|
|
199
|
+
## 🤝 Contributing
|
|
200
|
+
|
|
201
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
202
|
+
|
|
203
|
+
### Development Setup
|
|
204
|
+
|
|
205
|
+
1. Fork the repository
|
|
206
|
+
2. Create a virtual environment: `uv venv`
|
|
207
|
+
3. Install dependencies: `uv sync --extra dev`
|
|
208
|
+
4. Install pre-commit hooks: `pre-commit install`
|
|
209
|
+
5. Make your changes and run tests
|
|
210
|
+
6. Submit a pull request
|
|
211
|
+
|
|
212
|
+
## 📄 License
|
|
213
|
+
|
|
214
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
215
|
+
|
|
216
|
+
## 🙏 Acknowledgments
|
|
217
|
+
|
|
218
|
+
- Built with [PyO3](https://github.com/PyO3/pyo3) for Python-Rust integration
|
|
219
|
+
- Inspired by [Flask](https://flask.palletsprojects.com/) for the API design
|
|
220
|
+
- Uses [Tokio](https://tokio.rs/) and [Hyper](https://hyper.rs/) for async HTTP handling
|
|
221
|
+
|
|
222
|
+
## 🔗 Links
|
|
223
|
+
|
|
224
|
+
- [Homepage](https://bustapi.dev)
|
|
225
|
+
- [Documentation](https://bustapi.dev/docs)
|
|
226
|
+
- [PyPI Package](https://pypi.org/project/bustapi/)
|
|
227
|
+
- [GitHub Repository](https://github.com/bustapi/bustapi)
|
|
228
|
+
- [Issue Tracker](https://github.com/bustapi/bustapi/issues)
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
**BustAPI** - *High-performance Flask-compatible web framework* 🚀
|
|
233
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
bustapi-0.1.0.dist-info/METADATA,sha256=OR9KqpKo1UeLXwHgYd7GwmibDP8oRqz6fUyhIQLhgTg,6602
|
|
2
|
+
bustapi-0.1.0.dist-info/WHEEL,sha256=YCZ9Vxhf2aXNyfoR2QH-PPqnUr48Igr9zjgnGhp3xTc,96
|
|
3
|
+
bustapi-0.1.0.dist-info/entry_points.txt,sha256=rsTfPFL20JIrjJAAXxTWETsd6aqFdbsCJUd478dkI30,43
|
|
4
|
+
bustapi-0.1.0.dist-info/licenses/LICENSE,sha256=V2GOYCgpPjxQF18t7-EfVS2CXdSGNe4czXv1NUs4o18,1088
|
|
5
|
+
bustapi/__init__.py,sha256=2jV0H9sBzpV4MuRShLXSR3trD9SyfGmjiwYi7zdcL7w,2224
|
|
6
|
+
bustapi/app.py,sha256=ckI9M3CXVGaLEVyfIaYl-iO5lluvWzm4Powmkpvi7-Q,18811
|
|
7
|
+
bustapi/blueprints.py,sha256=286VL4Kim8LstmCTzV4YaScE4yMuwWvP7Dvw_vMs15Y,15007
|
|
8
|
+
bustapi/bustapi_core.cp311-win_amd64.pyd,sha256=BNTuDvQPcVRDFgUupdE4hqqjGEGbW6TE1exfPZUo3CY,744448
|
|
9
|
+
bustapi/exceptions.py,sha256=tcQW9pDz20vy3R5RK5FP7wHjG3DMSYvUfn2FWi_MnbE,12823
|
|
10
|
+
bustapi/flask_compat.py,sha256=sgvVGvonTEAcgykGsS-TfnVfA7fAPHHRWUYqTZHU10w,1339
|
|
11
|
+
bustapi/helpers.py,sha256=BtFdsDfk5q0R4EsjxqIxjfhTaVqIWhA1_6qqa74y3v4,9343
|
|
12
|
+
bustapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
bustapi/request.py,sha256=IHuASZqKsnxaYPIfuYqlVW1PGIjvYN04s7y_Q1c-yfE,11752
|
|
14
|
+
bustapi/response.py,sha256=nKAYveh4dF0TcHb9wqXLMW8zS_NGqpjzYYIJh6Bc3ZU,11840
|
|
15
|
+
bustapi/testing.py,sha256=nQWnSjQ3-7AhtcqEyiRPEnkXUBnIKNncfgYHYstIzms,11954
|
|
16
|
+
bustapi-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 BustAPI Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|