pyrustapi 0.3.34__tar.gz → 0.7.86__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.
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/.gitignore +1 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/Cargo.lock +1 -1
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/Cargo.toml +1 -1
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/PKG-INFO +2 -2
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/README.md +1 -1
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/ROADMAP.md +1 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/docs.md +1 -1
- pyrustapi-0.7.86/docs/fastapi_compatibility_and_features.md +349 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/swagger_ui_and_dx_deviations.md +26 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/pyproject.toml +1 -1
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/__init__.py +78 -2
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/__init__.pyi +7 -7
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/openapi/__init__.py +5 -0
- pyrustapi-0.7.86/python/rustapi/openapi/constants.py +3 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/openapi/docs.py +4 -1
- pyrustapi-0.7.86/python/rustapi/openapi/models.py +222 -0
- pyrustapi-0.7.86/python/rustapi/openapi/utils.py +132 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/resolver.py +16 -2
- pyrustapi-0.7.86/python/rustapi/security/__init__.py +42 -0
- pyrustapi-0.7.86/python/rustapi/security/api_key.py +70 -0
- pyrustapi-0.7.86/python/rustapi/security/base.py +14 -0
- pyrustapi-0.7.86/python/rustapi/security/http.py +171 -0
- pyrustapi-0.7.86/python/rustapi/security/oauth2.py +174 -0
- pyrustapi-0.7.86/python/rustapi/security/open_id_connect_url.py +42 -0
- pyrustapi-0.7.86/python/rustapi/security/utils.py +11 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/uploads.py +8 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/src/lib.rs +441 -79
- pyrustapi-0.7.86/test-reports/junit.xml +1 -0
- pyrustapi-0.7.86/tests/test_auth_and_authorization.py +122 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/tests/test_fastapi_all_6_mismatches.py +38 -0
- pyrustapi-0.7.86/tests/test_python_ecosystem_compatibility.py +114 -0
- pyrustapi-0.7.86/tests/test_security_openapi_parity.py +53 -0
- pyrustapi-0.7.86/tests/test_swagger_ui_security.py +161 -0
- pyrustapi-0.3.34/docs/fastapi_compatibility_and_features.md +0 -171
- pyrustapi-0.3.34/python/rustapi/security/__init__.py +0 -178
- pyrustapi-0.3.34/test-reports/junit.xml +0 -1
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/.github/workflows/CI.yml +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/.github/workflows/pages.yml +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/.github/workflows/release.yml +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/.github/workflows/static.yml +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/LICENSE +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmark_dashboard.html +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/benchmark.sh +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/data/fastapi_app.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/data/fastapi_cpu_app.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/data/fastapi_edge_app.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/data/fastapi_oha.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/data/large.json +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/data/mixed.json +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/data/rustapi_app.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/data/rustapi_cpu_app.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/data/rustapi_edge_app.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/data/rustapi_multiworker_app.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/data/rustapi_oha.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/data/rustapi_tier3_app.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/data/tier_app.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/data/validate.json +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/run_3tier_benchmark.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/run_comparison_benchmark.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/run_cpu_stability_benchmark.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/run_edgecase_benchmark.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/run_native_tier3_benchmark.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/run_real_oha_benchmark.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks/run_release_highspeed_benchmark.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/benchmarks.html +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/crud_benchmark/benchmark_db.sqlite +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/crud_benchmark/benchmark_db.sqlite-shm +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/crud_benchmark/benchmark_db.sqlite-wal +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/crud_benchmark/benchmark_results.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/crud_benchmark/fastapi_crud.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/crud_benchmark/run_crud_benchmark.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/crud_benchmark/rustapi_crud.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/3tier_architecture_and_benchmarks.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/DeepAnalytics.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/api-reference.html +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/benchmark_dashboard.html +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/benchmarks.html +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/database.html +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/database_engine.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/getting-started.html +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/getting_started.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/index.html +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/mcp-server.html +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/mcp_server.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/native_business_logic.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/performance_and_benchmarks.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/power_primitives.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/pyrustapi-logo (1).png +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/report_01_.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/response_types.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/docs/security-power-modules.html +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/examples/app.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/index.html +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/performance-reports/cpu_intensive_stability_report.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/performance-reports/fastapi_vs_hybrid_vs_native.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/performance-reports/fastapi_vs_rustapi_comparison.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/performance-reports/fastapi_vs_rustapi_crud_benchmark.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/performance-reports/performance_and_benchmarks.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/performance-reports/rust_native_edgecases_report.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/performance-reports/three_tier_architecture_evaluation.md +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/pyrustapi-logo.png +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/pyrustapi/__init__.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/background.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/depends.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/encoders.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/exceptions.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/middleware/__init__.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/middleware/cors.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/param_functions.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/py.typed +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/responses/__init__.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/router.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/rust.rs +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/sse.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/staticfiles.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/python/rustapi/status.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/scratch/.gitkeep +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/tests/test_3tier_architecture.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/tests/test_basic.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/tests/test_database_engine.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/tests/test_fastapi_compatibility.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/tests/test_file_uploads_and_dx.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/tests/test_imports.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/tests/test_native_routes.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/tests/test_overrides_and_coercion.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/tests/test_reload_and_logging.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/tests/test_response_model.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/tests/test_responses_and_req_json.py +0 -0
- {pyrustapi-0.3.34 → pyrustapi-0.7.86}/tests/test_rust_power_modules.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyrustapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.86
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Programming Language :: Rust
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -49,7 +49,7 @@ Project-URL: Repository, https://github.com/rajboopathiking/rustapi
|
|
|
49
49
|
|
|
50
50
|
| Feature Area | Implementation Details |
|
|
51
51
|
| :--- | :--- |
|
|
52
|
-
| **FastAPI Compatibility** | `FastAPI` & `Request` aliases, `status` codes, `EventSourceResponse` (SSE), `jsonable_encoder`, `Query`/`Body`/`Path`, `OAuth2PasswordBearer`, `app.frontend()`. |
|
|
52
|
+
| **FastAPI Compatibility** | `FastAPI` & `Request` aliases, `status` codes, `EventSourceResponse` (SSE), `jsonable_encoder`, `Query`/`Body`/`Path`, `OAuth2PasswordBearer`, `HTTPBearer`, `APIKeyHeader`, `OpenIdConnect`, `get_openapi`, `app.frontend()`. |
|
|
53
53
|
| **HTTP & Routing** | FastAPI-style `@app.get`, `@app.post`, `APIRouter`, `Depends`, `app.dependency_overrides`, `response_model`, `reload=True`. |
|
|
54
54
|
| **Rust-Native Tier 3 Routes** | `app.add_native_route()` zero-GIL C-speed fast-paths (**50,000+ req/sec**). |
|
|
55
55
|
| **Database Engine** | `sqlx` SQLite & PostgreSQL pool (`app.connect_db()`) with zero-copy JSON streaming (`db.query_json()`). |
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
| Feature Area | Implementation Details |
|
|
18
18
|
| :--- | :--- |
|
|
19
|
-
| **FastAPI Compatibility** | `FastAPI` & `Request` aliases, `status` codes, `EventSourceResponse` (SSE), `jsonable_encoder`, `Query`/`Body`/`Path`, `OAuth2PasswordBearer`, `app.frontend()`. |
|
|
19
|
+
| **FastAPI Compatibility** | `FastAPI` & `Request` aliases, `status` codes, `EventSourceResponse` (SSE), `jsonable_encoder`, `Query`/`Body`/`Path`, `OAuth2PasswordBearer`, `HTTPBearer`, `APIKeyHeader`, `OpenIdConnect`, `get_openapi`, `app.frontend()`. |
|
|
20
20
|
| **HTTP & Routing** | FastAPI-style `@app.get`, `@app.post`, `APIRouter`, `Depends`, `app.dependency_overrides`, `response_model`, `reload=True`. |
|
|
21
21
|
| **Rust-Native Tier 3 Routes** | `app.add_native_route()` zero-GIL C-speed fast-paths (**50,000+ req/sec**). |
|
|
22
22
|
| **Database Engine** | `sqlx` SQLite & PostgreSQL pool (`app.connect_db()`) with zero-copy JSON streaming (`db.query_json()`). |
|
|
@@ -38,6 +38,7 @@ RustAPI follows a **3-Tier Architecture** that combines FastAPI's intuitive Pyth
|
|
|
38
38
|
- [x] **Strict Parameter Type Coercion:** Automatic casting of path and query params (`int`, `float`, `bool`) with structured 422 HTTP validation error responses.
|
|
39
39
|
- [x] **Request & Response Ergonomics:** `req.json()`, `req.body`, `req.headers`, `req.cookies`, `JSONResponse`, `HTMLResponse`, `PlainTextResponse`, `RedirectResponse`, `StreamingResponse`, `UploadFile`, and `WebSocket`.
|
|
40
40
|
- [x] **OpenAPI & Interactive Docs:** Automatic `/openapi.json` generation and embedded Swagger UI at `/docs`.
|
|
41
|
+
- [x] **Swagger UI Security & Interactive Authorize (v0.7.86):** Automatic `components.securitySchemes` generation and interactive top **Authorize** lock button (🔒) in Swagger UI (`/docs`) for all 8 `rustapi.security` schemes.
|
|
41
42
|
- [x] **Native Telemetry & Access Logging:** Real-time terminal request access logs (`INFO: 127.0.0.1 - "GET /docs HTTP/1.1" 200 - 0.85ms`).
|
|
42
43
|
- [x] **Model Context Protocol (MCP) Server:** Embedded MCP tools (`@app.tool()`), resources (`@app.resource()`), and prompts (`@app.prompt()`) accessible at `POST /mcp`.
|
|
43
44
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
| Section | Content Covered | Developer Benefit |
|
|
23
23
|
| :--- | :--- | :--- |
|
|
24
|
-
| **FastAPI Compatibility** | `from rustapi import FastAPI, Request, status`, `
|
|
24
|
+
| **FastAPI Compatibility** | `from rustapi import FastAPI, Request, status`, `from rustapi.security import OAuth2PasswordBearer, HTTPBearer, APIKeyHeader, OpenIdConnect, SecurityScopes`, `from rustapi.openapi import get_swagger_ui_html, get_openapi, models`. | 100% FastAPI drop-in compatibility, security dependency modules, OpenAPI 3.1.0 models & generators, and SSE/SPA helpers (see [`fastapi_compatibility_and_features.md`](fastapi_compatibility_and_features.md)). |
|
|
25
25
|
| **§1. Core Engine & Routing** | `Engine()`, `@app.get`, `@app.post`, `@app.put`, `@app.delete`, `@app.patch`, `sync` and `async def` handlers. | Quick reference for routing syntax & handler types. |
|
|
26
26
|
| **§2. Request Metadata & Errors** | `req` (`PyRequest`) object, custom `Response(..., status_code, headers)`, `HTTPException`. | Inspect headers, query/path params, and raise clean HTTP errors. |
|
|
27
27
|
| **💡 FastAPI `Request` vs `req`** | Full property comparison table (`method`, `path`, `headers`, `cookies`, `form`, `files`, `body`, `json()`). | Eliminates confusion when migrating from FastAPI to RustAPI. |
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# 🚀 FastAPI Compatibility & Feature Guide in RustAPI (`pyrustapi`)
|
|
2
|
+
|
|
3
|
+
RustAPI (`pyrustapi`) provides **1:1 FastAPI compatibility**, enabling developers to migrate existing FastAPI applications or write clean FastAPI-style code backed by a high-performance **Rust Tokio / Hyper core**.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📌 Summary of Features & Compatibility Layer
|
|
8
|
+
|
|
9
|
+
| Feature | Import Path | Description |
|
|
10
|
+
| :--- | :--- | :--- |
|
|
11
|
+
| **`FastAPI` Alias** | `from rustapi import FastAPI` | Class alias for `Engine` for 100% drop-in FastAPI compatibility. |
|
|
12
|
+
| **`Request` Alias** | `from rustapi import Request` | Class alias for `PyRequest`. |
|
|
13
|
+
| **Status Codes** | `from rustapi import status` | Starlette/FastAPI status code constants (`status.HTTP_200_OK`, `status.HTTP_404_NOT_FOUND`, etc.). |
|
|
14
|
+
| **OpenAPI & Specs** | `from rustapi.openapi import get_swagger_ui_html, get_redoc_html, get_openapi, models` | Interactive Swagger UI (`/docs`), ReDoc (`/redoc`), and OpenAPI 3.1.0 specification models & generators. |
|
|
15
|
+
| **Response Classes** | `from rustapi.responses import FileResponse, HTMLResponse, JSONResponse, PlainTextResponse, RedirectResponse, StreamingResponse` | Full range of FastAPI response classes. |
|
|
16
|
+
| **CORS Middleware** | `from rustapi.middleware.cors import CORSMiddleware` | Middleware class & `app.add_middleware()` for configuring cross-origin requests. |
|
|
17
|
+
| **Server-Sent Events** | `from rustapi import EventSourceResponse, ServerSentEvent, format_sse_event` | Real-time SSE streaming for AI/LLM tokens and Model Context Protocol (MCP). |
|
|
18
|
+
| **Data Encoders** | `from rustapi import jsonable_encoder` | Convert Pydantic models, Dataclasses, Datetime, UUID, and dicts to JSON-serializable primitives. |
|
|
19
|
+
| **Parameter Markers** | `from rustapi import Body, Query, Path, Header, Cookie, Form, File, Security` | Location markers for dependency injection and parameter validation. |
|
|
20
|
+
| **Security Modules** | `from rustapi.security import OAuth2PasswordBearer, OAuth2AuthorizationCodeBearer, HTTPBearer, HTTPBasic, APIKeyHeader, APIKeyQuery, APIKeyCookie, OpenIdConnect, SecurityScopes, SecurityBase` | 100% FastAPI-compatible security authentication dependency helpers. |
|
|
21
|
+
| **Frontend Serving** | `app.frontend("/", directory="dist")` / `router.frontend(...)` | Serve built React, Vue, Svelte, or Vite single-page apps with client-side routing fallback. |
|
|
22
|
+
| **WebSocket Exceptions**| `from rustapi import WebSocketDisconnect, WebSocketException` | Exception classes for handling WebSocket disconnection events. |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 1. FastAPI Aliases & Status Codes
|
|
27
|
+
|
|
28
|
+
Drop-in FastAPI imports work directly out of the box:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from rustapi import FastAPI, Request, status, HTTPException
|
|
32
|
+
|
|
33
|
+
app = FastAPI()
|
|
34
|
+
|
|
35
|
+
@app.get("/health", status_code=status.HTTP_200_OK)
|
|
36
|
+
def health_check(request: Request):
|
|
37
|
+
if not request:
|
|
38
|
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Bad request")
|
|
39
|
+
return {"status": "healthy"}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 2. Interactive API Documentation (`Swagger UI` & `ReDoc`)
|
|
45
|
+
|
|
46
|
+
Serve custom interactive documentation endpoints using `get_swagger_ui_html` and `get_redoc_html`:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from rustapi import FastAPI
|
|
50
|
+
from rustapi.openapi import get_swagger_ui_html, get_redoc_html, get_openapi
|
|
51
|
+
|
|
52
|
+
app = FastAPI()
|
|
53
|
+
|
|
54
|
+
@app.get("/docs", include_in_schema=False)
|
|
55
|
+
def custom_swagger_ui():
|
|
56
|
+
return get_swagger_ui_html(
|
|
57
|
+
openapi_url="/openapi.json",
|
|
58
|
+
title="My Custom API Docs",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
@app.get("/openapi.json", include_in_schema=False)
|
|
62
|
+
def get_custom_openapi():
|
|
63
|
+
return get_openapi(
|
|
64
|
+
title="RustAPI Application",
|
|
65
|
+
version="1.0.0",
|
|
66
|
+
description="OpenAPI 3.1.0 specification generated dynamically.",
|
|
67
|
+
routes=app.routes,
|
|
68
|
+
)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Security Modules (`rustapi.security`)
|
|
72
|
+
|
|
73
|
+
`rustapi.security` provides complete 1:1 FastAPI compatibility for security dependency schemes:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from rustapi import Depends, FastAPI
|
|
77
|
+
from rustapi.security import (
|
|
78
|
+
OAuth2PasswordBearer,
|
|
79
|
+
OAuth2PasswordRequestForm,
|
|
80
|
+
HTTPBearer,
|
|
81
|
+
HTTPBasic,
|
|
82
|
+
APIKeyHeader,
|
|
83
|
+
SecurityScopes,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
app = FastAPI()
|
|
87
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
|
88
|
+
api_key_scheme = APIKeyHeader(name="X-API-Key")
|
|
89
|
+
|
|
90
|
+
@app.post("/token")
|
|
91
|
+
def login(form_data: OAuth2PasswordRequestForm = Depends()):
|
|
92
|
+
return {"access_token": form_data.username, "token_type": "bearer"}
|
|
93
|
+
|
|
94
|
+
@app.get("/protected")
|
|
95
|
+
def protected_route(token: str = Depends(oauth2_scheme), api_key: str = Depends(api_key_scheme)):
|
|
96
|
+
return {"token": token, "api_key": api_key}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
@app.get("/redoc", include_in_schema=False)
|
|
100
|
+
def custom_redoc_ui():
|
|
101
|
+
return get_redoc_html(
|
|
102
|
+
openapi_url="/openapi.json",
|
|
103
|
+
title="My ReDoc API",
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 3. Server-Sent Events (SSE) for AI & Streaming
|
|
110
|
+
|
|
111
|
+
Stream AI/LLM tokens or real-time event updates over HTTP using `EventSourceResponse` and `ServerSentEvent`:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from collections.abc import AsyncIterable
|
|
115
|
+
from rustapi import FastAPI, EventSourceResponse, ServerSentEvent
|
|
116
|
+
|
|
117
|
+
app = FastAPI()
|
|
118
|
+
|
|
119
|
+
@app.get("/ai-stream", response_class=EventSourceResponse)
|
|
120
|
+
async def stream_ai_tokens():
|
|
121
|
+
tokens = ["Hello", " world", " from", " RustAPI", " SSE!"]
|
|
122
|
+
for token in tokens:
|
|
123
|
+
yield ServerSentEvent(data={"token": token}, event="message")
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Or format raw SSE wire bytes using `format_sse_event`:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from rustapi import format_sse_event
|
|
130
|
+
|
|
131
|
+
raw_bytes = format_sse_event(
|
|
132
|
+
data_str='{"status": "processing"}',
|
|
133
|
+
event="ping",
|
|
134
|
+
id="evt_001",
|
|
135
|
+
retry=3000,
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 4. Frontend Single-Page App (SPA) Serving
|
|
142
|
+
|
|
143
|
+
Serve compiled React, Vue, Svelte, or Vite frontend build directories directly from `rustapi`:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from rustapi import FastAPI
|
|
147
|
+
|
|
148
|
+
app = FastAPI()
|
|
149
|
+
|
|
150
|
+
# Serves 'dist/index.html' for '/' and static assets from 'dist/'
|
|
151
|
+
app.frontend("/", directory="dist")
|
|
152
|
+
|
|
153
|
+
# Or attach frontend serving to sub-routers
|
|
154
|
+
from rustapi import APIRouter
|
|
155
|
+
admin_router = APIRouter(prefix="/admin")
|
|
156
|
+
admin_router.frontend("/", directory="admin-dist")
|
|
157
|
+
app.include_router(admin_router)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 5. Security & Authentication Dependencies
|
|
163
|
+
|
|
164
|
+
Use pre-packaged security helpers for OAuth2, HTTP Bearer, and API Keys:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from rustapi import FastAPI, Depends, HTTPException, status
|
|
168
|
+
from rustapi.security import OAuth2PasswordBearer, HTTPBearer, APIKeyHeader
|
|
169
|
+
|
|
170
|
+
app = FastAPI()
|
|
171
|
+
|
|
172
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
|
173
|
+
bearer_scheme = HTTPBearer()
|
|
174
|
+
api_key_scheme = APIKeyHeader(name="X-API-Key")
|
|
175
|
+
|
|
176
|
+
@app.get("/users/me")
|
|
177
|
+
def read_current_user(token: str = Depends(oauth2_scheme)):
|
|
178
|
+
if not token:
|
|
179
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
|
|
180
|
+
return {"user": "alice", "token": token}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 6. Parameter Markers & `jsonable_encoder`
|
|
186
|
+
|
|
187
|
+
Use standard FastAPI parameter placement markers and data converters:
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from dataclasses import dataclass
|
|
191
|
+
from rustapi import FastAPI, Query, Path, Body, jsonable_encoder
|
|
192
|
+
|
|
193
|
+
app = FastAPI()
|
|
194
|
+
|
|
195
|
+
@dataclass
|
|
196
|
+
class User:
|
|
197
|
+
id: int
|
|
198
|
+
username: str
|
|
199
|
+
|
|
200
|
+
@app.get("/items/{item_id}")
|
|
201
|
+
def get_item(
|
|
202
|
+
item_id: int = Path(..., ge=1),
|
|
203
|
+
q: str = Query(None, max_length=50),
|
|
204
|
+
):
|
|
205
|
+
user = User(id=1, username="bob")
|
|
206
|
+
# Convert dataclass/Pydantic model to JSON dict
|
|
207
|
+
user_dict = jsonable_encoder(user)
|
|
208
|
+
return {"item_id": item_id, "query": q, "user": user_dict}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 7. Python Ecosystem Compatibility Matrix
|
|
214
|
+
|
|
215
|
+
`rustapi` is built to seamlessly integrate with standard Python database, machine learning, security, and utility libraries:
|
|
216
|
+
|
|
217
|
+
| Library Domain | Package | Verified Feature & Usage | Status |
|
|
218
|
+
| :--- | :--- | :--- | :--- |
|
|
219
|
+
| **Data Validation** | `pydantic` v2 | `BaseModel`, `Field`, `validator`, `EmailStr` in route bodies | ✅ Verified |
|
|
220
|
+
| **HTTP Clients** | `httpx`, `requests` | `httpx.AsyncClient` & `requests` testing, multi-part uploads & streams | ✅ Verified |
|
|
221
|
+
| **Databases & ORMs** | `sqlite3`, `sqlalchemy` | Session context managers & query execution inside `Depends(get_db)` | ✅ Verified |
|
|
222
|
+
| **Image Processing** | `PIL` / `Pillow` | Image decoding & matrix transformations on `UploadFile` streams | ✅ Verified |
|
|
223
|
+
| **JWT & Security** | `pyjwt`, `passlib`, `argon2` | `jwt.encode`, `jwt.decode`, password hashing inside auth endpoints | ✅ Verified |
|
|
224
|
+
| **Numeric & ML** | `numpy` | `np.array` operations on request payloads & image byte arrays | ✅ Verified |
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
import io
|
|
228
|
+
import jwt
|
|
229
|
+
import numpy as np
|
|
230
|
+
from PIL import Image
|
|
231
|
+
from pydantic import BaseModel, Field
|
|
232
|
+
from rustapi import FastAPI, Request, Depends, HTTPException
|
|
233
|
+
from rustapi.uploads import UploadFile
|
|
234
|
+
|
|
235
|
+
app = FastAPI()
|
|
236
|
+
|
|
237
|
+
class AnalysisModel(BaseModel):
|
|
238
|
+
project_name: str = Field(..., min_length=2)
|
|
239
|
+
|
|
240
|
+
@app.post("/analyze")
|
|
241
|
+
async def analyze(req: Request):
|
|
242
|
+
if "photo" not in req.files:
|
|
243
|
+
raise HTTPException(status_code=400, detail="No photo uploaded")
|
|
244
|
+
|
|
245
|
+
file_obj: UploadFile = req.files["photo"][0]
|
|
246
|
+
img_bytes = await file_obj.read() # Both await file.read() and file.read() work
|
|
247
|
+
|
|
248
|
+
# Process image with Pillow & NumPy
|
|
249
|
+
image = Image.open(io.BytesIO(img_bytes)).convert("L")
|
|
250
|
+
array = np.array(image)
|
|
251
|
+
brightness = float(np.mean(array))
|
|
252
|
+
|
|
253
|
+
# Encode JWT response
|
|
254
|
+
token = jwt.encode({"brightness": brightness}, "secret", algorithm="HS256")
|
|
255
|
+
return {"status": "success", "token": token}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## 9. Full Python Ecosystem Interoperability & Release v0.7.86
|
|
261
|
+
|
|
262
|
+
`pyrustapi` features zero-limitation compatibility with the Python library ecosystem:
|
|
263
|
+
|
|
264
|
+
| Library Category | Supported Packages | Integration Behavior |
|
|
265
|
+
| :--- | :--- | :--- |
|
|
266
|
+
| **Databases & Async ORMs** | `SQLAlchemy`, `asyncpg`, `aiosqlite`, `tortoise-orm`, `peewee` | Context managers & `yield` generator sessions in `Depends(get_db)` |
|
|
267
|
+
| **Data Validation** | `pydantic` v2, `msgspec`, `attrs`, `dataclasses` | Full request body parsing, field validation & 422 coercion |
|
|
268
|
+
| **Security & Auth** | `pyjwt`, `python-jose`, `passlib`, `argon2-cffi`, `cryptography` | Bearer token validation, password hashing & custom claims |
|
|
269
|
+
| **Machine Learning & AI** | `torch`, `tensorflow`, `scikit-learn`, `onnxruntime`, `xgboost` | Model inference & tensor computation on upload byte streams |
|
|
270
|
+
| **HTTP & Async Testing** | `httpx`, `requests`, `aiohttp`, `starlette.testclient` | ASGI 3.0 (`app(scope, receive, send)`) & `ASGITransport` support |
|
|
271
|
+
| **Async Task Queues** | `celery`, `redis`, `rq`, `dramatiq` | Background task enqueueing inside async route handlers |
|
|
272
|
+
| **Domain Specific & Rare** | `biopython`, `rdkit`, `geopandas`, `cv2`, `librosa`, `numba` | Native C-extensions, CUDA acceleration & JIT compiled functions |
|
|
273
|
+
|
|
274
|
+
### Support for Rare & Native C-Extension Libraries
|
|
275
|
+
|
|
276
|
+
`pyrustapi` is **not** an interpreter transpile layer; it executes directly inside the native CPython runtime via PyO3 C-bindings. Therefore, **100% of Python libraries work with zero limitations**:
|
|
277
|
+
|
|
278
|
+
- **C / C++ / Cython Extensions** (`ctypes`, `cffi`, `pybind11`, `.so` / `.dylib`): Linked directly to standard CPython ABI memory.
|
|
279
|
+
- **Scientific & ML Accelerators** (`torch`, `cuda`, `scikit-learn`, `numba`, `scipy`): GPU tensor execution & JIT function evaluation operate seamlessly.
|
|
280
|
+
- **Domain-Specific Packages** (`biopython`, `rdkit`, `astropy`, `cv2`, `librosa`): Native binary dependencies load and execute without modification.
|
|
281
|
+
|
|
282
|
+
### Authentication & Authorization Code Pattern (v0.7.86)
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
import jwt
|
|
286
|
+
from rustapi import FastAPI, Request, Depends, HTTPException, status
|
|
287
|
+
from rustapi.security import (
|
|
288
|
+
HTTPBearer,
|
|
289
|
+
HTTPAuthorizationCredentials,
|
|
290
|
+
OAuth2PasswordBearer,
|
|
291
|
+
APIKeyHeader,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
app = FastAPI(title="Production Auth API", version="0.7.86")
|
|
295
|
+
|
|
296
|
+
SECRET_KEY = "your-256-bit-secret-key"
|
|
297
|
+
ALGORITHM = "HS256"
|
|
298
|
+
|
|
299
|
+
# Security scheme declarations
|
|
300
|
+
bearer_scheme = HTTPBearer()
|
|
301
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
|
|
302
|
+
api_key_scheme = APIKeyHeader(name="X-API-Key")
|
|
303
|
+
|
|
304
|
+
async def get_current_user(token: str = Depends(oauth2_scheme)) -> dict:
|
|
305
|
+
if not token:
|
|
306
|
+
raise HTTPException(
|
|
307
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
308
|
+
detail="Authorization token required",
|
|
309
|
+
)
|
|
310
|
+
try:
|
|
311
|
+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
|
312
|
+
return {"username": payload["sub"], "role": payload["role"]}
|
|
313
|
+
except Exception:
|
|
314
|
+
raise HTTPException(
|
|
315
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
316
|
+
detail="Invalid or expired token",
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
async def get_admin_user(current_user: dict = Depends(get_current_user)) -> dict:
|
|
320
|
+
if current_user["role"] not in ("admin", "sysadmin"):
|
|
321
|
+
raise HTTPException(
|
|
322
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
323
|
+
detail="Admin privileges required",
|
|
324
|
+
)
|
|
325
|
+
return current_user
|
|
326
|
+
|
|
327
|
+
@app.get("/users/me")
|
|
328
|
+
async def read_current_user(user: dict = Depends(get_current_user)):
|
|
329
|
+
return {"user": user["username"], "role": user["role"]}
|
|
330
|
+
|
|
331
|
+
@app.get("/admin/audit-logs")
|
|
332
|
+
async def read_audit_logs(admin: dict = Depends(get_admin_user)):
|
|
333
|
+
return {"logs": [], "accessed_by": admin["username"]}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Key Improvements in Release v0.7.86:
|
|
337
|
+
|
|
338
|
+
1. **Swagger UI Security & Interactive Authorize (Top Lock Button 🔒)**:
|
|
339
|
+
Automatically registers `components.securitySchemes` in `/openapi.json` and attaches `security` operation requirements across all 8 security schemes (`HTTPBearer`, `OAuth2PasswordBearer`, `OAuth2AuthorizationCodeBearer`, `APIKeyHeader`, `APIKeyQuery`, `APIKeyCookie`, `HTTPBasic`, `HTTPDigest`, `OpenIdConnect`), rendering the top **Authorize** lock button in `/docs`.
|
|
340
|
+
2. **Recursive Sub-Dependency OpenAPI SecuritySchemes**:
|
|
341
|
+
Automatically unrolls nested dependencies (`Depends(get_admin_user)` $\rightarrow$ `Depends(get_current_user)` $\rightarrow$ `Depends(oauth2_scheme)`) to output `components.securitySchemes` in `/openapi.json`, enabling interactive auth testing in Swagger UI (`/docs`).
|
|
342
|
+
3. **HTTPException Status Code Preservation**:
|
|
343
|
+
Directly propagates custom status codes (`401 Unauthorized`, `403 Forbidden`, `404 Not Found`, `422 Unprocessable Entity`) without falling back to 500 Internal Server Errors.
|
|
344
|
+
4. **Recursive Dependency & Request Injection**:
|
|
345
|
+
Resolves nested `Depends(sub_dep)` chains and injects `request: Request` automatically across all dependency tiers.
|
|
346
|
+
5. **Dual Async / Sync Payload Helpers**:
|
|
347
|
+
Provides `AwaitableDict` for `req.json()` and `AwaitableBytes` for `UploadFile.read()`, allowing both `await req.json()` and `req.json()` inside sync/async ML route handlers.
|
|
348
|
+
|
|
349
|
+
|
|
@@ -105,6 +105,32 @@ Visiting `http://127.0.0.1:8000/docs` will render the customized Swagger UI inte
|
|
|
105
105
|
|
|
106
106
|
---
|
|
107
107
|
|
|
108
|
+
### Swagger UI Security & Interactive Authorize (Top Lock Button) (v0.7.86)
|
|
109
|
+
|
|
110
|
+
RustAPI automatically detects all 8 security dependencies from `rustapi.security` (`HTTPBearer`, `OAuth2PasswordBearer`, `OAuth2AuthorizationCodeBearer`, `APIKeyHeader`, `APIKeyQuery`, `APIKeyCookie`, `HTTPBasic`, `HTTPDigest`, `OpenIdConnect`) across direct and nested dependencies (`Depends(...)`).
|
|
111
|
+
|
|
112
|
+
When security dependencies are declared on route handlers:
|
|
113
|
+
1. **OpenAPI SecuritySchemes**: RustAPI auto-populates `components.securitySchemes` in `/openapi.json`.
|
|
114
|
+
2. **Path Operation Security**: Each protected route receives `"security": [{ "<scheme_name>": [] }]`.
|
|
115
|
+
3. **Top Authorize Lock Button (🔒)**: Swagger UI served at `/docs` displays the interactive **Authorize** top lock button. Developers can click Authorize, enter Bearer tokens, API keys, or OAuth2 credentials, and test protected endpoints directly within Swagger UI!
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from rustapi import FastAPI, Depends
|
|
119
|
+
from rustapi.security import OAuth2PasswordBearer, HTTPBearer, APIKeyHeader
|
|
120
|
+
|
|
121
|
+
app = FastAPI(title="Secure API", version="0.7.86")
|
|
122
|
+
|
|
123
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
|
|
124
|
+
bearer_scheme = HTTPBearer()
|
|
125
|
+
api_key_scheme = APIKeyHeader(name="X-API-Key")
|
|
126
|
+
|
|
127
|
+
@app.get("/users/me")
|
|
128
|
+
def read_user(token: str = Depends(oauth2_scheme)):
|
|
129
|
+
return {"token": token}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
108
134
|
## 2. Route Handler Signature (req vs Parameter Injection)
|
|
109
135
|
|
|
110
136
|
### FastAPI DX
|
|
@@ -2,6 +2,7 @@ from typing import Any, Dict, Optional
|
|
|
2
2
|
from .status import status
|
|
3
3
|
from ._rustapi import (
|
|
4
4
|
Engine,
|
|
5
|
+
Route,
|
|
5
6
|
PyRequest,
|
|
6
7
|
UploadFile,
|
|
7
8
|
WebSocket,
|
|
@@ -30,7 +31,7 @@ from .param_functions import (
|
|
|
30
31
|
)
|
|
31
32
|
|
|
32
33
|
from .responses import FileResponse
|
|
33
|
-
from . import responses, middleware
|
|
34
|
+
from . import responses, middleware, security, openapi
|
|
34
35
|
|
|
35
36
|
class FastAPI(Engine):
|
|
36
37
|
"""FastAPI-compatible application class wrapping the Rust Tokio core engine."""
|
|
@@ -64,6 +65,78 @@ class FastAPI(Engine):
|
|
|
64
65
|
"""Add middleware (such as CORSMiddleware) to application configuration."""
|
|
65
66
|
self.middlewares.append((middleware_cls, kwargs))
|
|
66
67
|
|
|
68
|
+
async def __call__(self, scope: Dict[str, Any], receive: Any, send: Any):
|
|
69
|
+
"""ASGI 3.0 interface implementation for ASGITransport, TestClient, uvicorn, and pytest."""
|
|
70
|
+
import inspect, json
|
|
71
|
+
if scope["type"] == "lifespan":
|
|
72
|
+
while True:
|
|
73
|
+
message = await receive()
|
|
74
|
+
if message["type"] == "lifespan.startup":
|
|
75
|
+
for handler in getattr(self, "startup_handlers", []):
|
|
76
|
+
if inspect.iscoroutinefunction(handler):
|
|
77
|
+
await handler()
|
|
78
|
+
else:
|
|
79
|
+
handler()
|
|
80
|
+
await send({"type": "lifespan.startup.complete"})
|
|
81
|
+
elif message["type"] == "lifespan.shutdown":
|
|
82
|
+
for handler in getattr(self, "shutdown_handlers", []):
|
|
83
|
+
if inspect.iscoroutinefunction(handler):
|
|
84
|
+
await handler()
|
|
85
|
+
else:
|
|
86
|
+
handler()
|
|
87
|
+
await send({"type": "lifespan.shutdown.complete"})
|
|
88
|
+
break
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
if scope["type"] != "http":
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
method = scope.get("method", "GET")
|
|
95
|
+
path = scope.get("path", "/")
|
|
96
|
+
query_string = scope.get("query_string", b"").decode("latin1")
|
|
97
|
+
headers = {k.decode("latin1").lower(): v.decode("latin1") for k, v in scope.get("headers", [])}
|
|
98
|
+
|
|
99
|
+
body_bytes = bytearray()
|
|
100
|
+
while True:
|
|
101
|
+
msg = await receive()
|
|
102
|
+
if msg["type"] == "http.request":
|
|
103
|
+
body_bytes.extend(msg.get("body", b""))
|
|
104
|
+
if not msg.get("more_body", False):
|
|
105
|
+
break
|
|
106
|
+
|
|
107
|
+
body_str = body_bytes.decode("utf-8", errors="replace")
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
status_code, response_body, resp_headers = await self.dispatch_request(
|
|
111
|
+
method, path, query_string, headers, body_str
|
|
112
|
+
)
|
|
113
|
+
except Exception as exc:
|
|
114
|
+
import logging
|
|
115
|
+
logging.getLogger("rustapi").error(f"Error handling ASGI request [{method} {path}]: {exc}", exc_info=True)
|
|
116
|
+
handler = self.exception_handlers.get(type(exc)) or self.exception_handlers.get(getattr(exc, "status_code", None))
|
|
117
|
+
if handler:
|
|
118
|
+
req = PyRequest(method=method, path=path, path_params={}, query_params={}, headers=headers, cookies={}, form={}, files={}, body=body_str)
|
|
119
|
+
resp = await handler(req, exc) if inspect.iscoroutinefunction(handler) else handler(req, exc)
|
|
120
|
+
status_code = getattr(resp, "status_code", 500)
|
|
121
|
+
response_body = getattr(resp, "content", str(exc))
|
|
122
|
+
resp_headers = getattr(resp, "headers", {"content-type": "application/json"})
|
|
123
|
+
else:
|
|
124
|
+
status_code = getattr(exc, "status_code", 500)
|
|
125
|
+
detail = getattr(exc, "detail", str(exc))
|
|
126
|
+
response_body = f'{{"detail": "{detail}"}}' if isinstance(detail, str) else json.dumps({"detail": detail})
|
|
127
|
+
resp_headers = {"content-type": "application/json"}
|
|
128
|
+
|
|
129
|
+
encoded_headers = [(k.encode("latin1"), v.encode("latin1")) for k, v in resp_headers.items()]
|
|
130
|
+
await send({
|
|
131
|
+
"type": "http.response.start",
|
|
132
|
+
"status": status_code,
|
|
133
|
+
"headers": encoded_headers,
|
|
134
|
+
})
|
|
135
|
+
await send({
|
|
136
|
+
"type": "http.response.body",
|
|
137
|
+
"body": response_body.encode("utf-8") if isinstance(response_body, str) else response_body,
|
|
138
|
+
})
|
|
139
|
+
|
|
67
140
|
def exception_handler(self, exc_class_or_status_code: Any):
|
|
68
141
|
"""Register an exception handler decorator for an exception class or status code."""
|
|
69
142
|
def decorator(func: Any):
|
|
@@ -152,10 +225,11 @@ class RedirectResponse(Response):
|
|
|
152
225
|
return Response.__new__(cls, content="", status_code=status_code, headers=h)
|
|
153
226
|
|
|
154
227
|
|
|
155
|
-
__version__ = "0.
|
|
228
|
+
__version__ = "0.7.86"
|
|
156
229
|
__all__ = [
|
|
157
230
|
"Engine",
|
|
158
231
|
"FastAPI",
|
|
232
|
+
"Route",
|
|
159
233
|
"PyRequest",
|
|
160
234
|
"Request",
|
|
161
235
|
"Response",
|
|
@@ -167,6 +241,8 @@ __all__ = [
|
|
|
167
241
|
"FileResponse",
|
|
168
242
|
"responses",
|
|
169
243
|
"middleware",
|
|
244
|
+
"security",
|
|
245
|
+
"openapi",
|
|
170
246
|
"EventSourceResponse",
|
|
171
247
|
"ServerSentEvent",
|
|
172
248
|
"format_sse_event",
|
|
@@ -19,13 +19,13 @@ class PyRequest:
|
|
|
19
19
|
self,
|
|
20
20
|
method: str,
|
|
21
21
|
path: str,
|
|
22
|
-
path_params: Dict[str, str]
|
|
23
|
-
query_params: Dict[str, str]
|
|
24
|
-
headers: Dict[str, str]
|
|
25
|
-
cookies: Dict[str, str]
|
|
26
|
-
form: Dict[str, str]
|
|
27
|
-
files: Dict[str, List[UploadFile]]
|
|
28
|
-
body: str
|
|
22
|
+
path_params: Optional[Dict[str, str]] = ...,
|
|
23
|
+
query_params: Optional[Dict[str, str]] = ...,
|
|
24
|
+
headers: Optional[Dict[str, str]] = ...,
|
|
25
|
+
cookies: Optional[Dict[str, str]] = ...,
|
|
26
|
+
form: Optional[Dict[str, str]] = ...,
|
|
27
|
+
files: Optional[Dict[str, List[UploadFile]]] = ...,
|
|
28
|
+
body: Optional[str] = ...,
|
|
29
29
|
) -> None: ...
|
|
30
30
|
def json(self) -> Any: ...
|
|
31
31
|
|
|
@@ -4,10 +4,15 @@ from .docs import (
|
|
|
4
4
|
get_swagger_ui_oauth2_redirect_html,
|
|
5
5
|
swagger_ui_default_parameters,
|
|
6
6
|
)
|
|
7
|
+
from .utils import get_openapi
|
|
8
|
+
from . import models, utils
|
|
7
9
|
|
|
8
10
|
__all__ = [
|
|
9
11
|
"get_swagger_ui_html",
|
|
10
12
|
"get_redoc_html",
|
|
11
13
|
"get_swagger_ui_oauth2_redirect_html",
|
|
12
14
|
"swagger_ui_default_parameters",
|
|
15
|
+
"get_openapi",
|
|
16
|
+
"models",
|
|
17
|
+
"utils",
|
|
13
18
|
]
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from typing import Any, Optional
|
|
3
|
-
|
|
3
|
+
try:
|
|
4
|
+
from .._rustapi import Response
|
|
5
|
+
except ImportError:
|
|
6
|
+
from .._rustapi import PyResponse as Response
|
|
4
7
|
|
|
5
8
|
class HTMLResponse(Response):
|
|
6
9
|
"""HTML response wrapper setting Content-Type: text/html; charset=utf-8."""
|