pyrustapi 0.3.86__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.86 → pyrustapi-0.7.86}/.gitignore +1 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/Cargo.lock +1 -1
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/Cargo.toml +1 -1
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/PKG-INFO +2 -2
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/README.md +1 -1
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/ROADMAP.md +1 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/docs.md +1 -1
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/fastapi_compatibility_and_features.md +127 -17
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/swagger_ui_and_dx_deviations.md +26 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/pyproject.toml +1 -1
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/rustapi/__init__.py +78 -2
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/rustapi/__init__.pyi +7 -7
- {pyrustapi-0.3.86 → 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.86 → 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.86 → 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.86 → pyrustapi-0.7.86}/python/rustapi/uploads.py +8 -0
- {pyrustapi-0.3.86 → 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.86 → pyrustapi-0.7.86}/tests/test_fastapi_all_6_mismatches.py +38 -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.86/python/rustapi/security/__init__.py +0 -178
- pyrustapi-0.3.86/test-reports/junit.xml +0 -1
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/.github/workflows/CI.yml +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/.github/workflows/pages.yml +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/.github/workflows/release.yml +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/.github/workflows/static.yml +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/LICENSE +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmark_dashboard.html +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/benchmark.sh +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/data/fastapi_app.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/data/fastapi_cpu_app.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/data/fastapi_edge_app.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/data/fastapi_oha.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/data/large.json +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/data/mixed.json +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/data/rustapi_app.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/data/rustapi_cpu_app.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/data/rustapi_edge_app.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/data/rustapi_multiworker_app.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/data/rustapi_oha.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/data/rustapi_tier3_app.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/data/tier_app.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/data/validate.json +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/run_3tier_benchmark.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/run_comparison_benchmark.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/run_cpu_stability_benchmark.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/run_edgecase_benchmark.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/run_native_tier3_benchmark.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/run_real_oha_benchmark.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks/run_release_highspeed_benchmark.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/benchmarks.html +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/crud_benchmark/benchmark_db.sqlite +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/crud_benchmark/benchmark_db.sqlite-shm +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/crud_benchmark/benchmark_db.sqlite-wal +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/crud_benchmark/benchmark_results.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/crud_benchmark/fastapi_crud.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/crud_benchmark/run_crud_benchmark.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/crud_benchmark/rustapi_crud.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/3tier_architecture_and_benchmarks.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/DeepAnalytics.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/api-reference.html +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/benchmark_dashboard.html +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/benchmarks.html +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/database.html +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/database_engine.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/getting-started.html +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/getting_started.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/index.html +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/mcp-server.html +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/mcp_server.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/native_business_logic.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/performance_and_benchmarks.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/power_primitives.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/pyrustapi-logo (1).png +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/report_01_.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/response_types.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/docs/security-power-modules.html +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/examples/app.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/index.html +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/performance-reports/cpu_intensive_stability_report.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/performance-reports/fastapi_vs_hybrid_vs_native.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/performance-reports/fastapi_vs_rustapi_comparison.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/performance-reports/fastapi_vs_rustapi_crud_benchmark.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/performance-reports/performance_and_benchmarks.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/performance-reports/rust_native_edgecases_report.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/performance-reports/three_tier_architecture_evaluation.md +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/pyrustapi-logo.png +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/pyrustapi/__init__.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/rustapi/background.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/rustapi/depends.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/rustapi/encoders.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/rustapi/exceptions.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/rustapi/middleware/__init__.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/rustapi/middleware/cors.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/rustapi/param_functions.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/rustapi/py.typed +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/rustapi/responses/__init__.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/rustapi/router.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/rustapi/rust.rs +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/rustapi/sse.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/rustapi/staticfiles.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/python/rustapi/status.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/scratch/.gitkeep +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/tests/test_3tier_architecture.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/tests/test_basic.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/tests/test_database_engine.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/tests/test_fastapi_compatibility.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/tests/test_file_uploads_and_dx.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/tests/test_imports.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/tests/test_native_routes.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/tests/test_overrides_and_coercion.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/tests/test_python_ecosystem_compatibility.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/tests/test_reload_and_logging.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/tests/test_response_model.py +0 -0
- {pyrustapi-0.3.86 → pyrustapi-0.7.86}/tests/test_responses_and_req_json.py +0 -0
- {pyrustapi-0.3.86 → 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. |
|
|
@@ -11,13 +11,13 @@ RustAPI (`pyrustapi`) provides **1:1 FastAPI compatibility**, enabling developer
|
|
|
11
11
|
| **`FastAPI` Alias** | `from rustapi import FastAPI` | Class alias for `Engine` for 100% drop-in FastAPI compatibility. |
|
|
12
12
|
| **`Request` Alias** | `from rustapi import Request` | Class alias for `PyRequest`. |
|
|
13
13
|
| **Status Codes** | `from rustapi import status` | Starlette/FastAPI status code constants (`status.HTTP_200_OK`, `status.HTTP_404_NOT_FOUND`, etc.). |
|
|
14
|
-
| **OpenAPI &
|
|
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
15
|
| **Response Classes** | `from rustapi.responses import FileResponse, HTMLResponse, JSONResponse, PlainTextResponse, RedirectResponse, StreamingResponse` | Full range of FastAPI response classes. |
|
|
16
16
|
| **CORS Middleware** | `from rustapi.middleware.cors import CORSMiddleware` | Middleware class & `app.add_middleware()` for configuring cross-origin requests. |
|
|
17
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
18
|
| **Data Encoders** | `from rustapi import jsonable_encoder` | Convert Pydantic models, Dataclasses, Datetime, UUID, and dicts to JSON-serializable primitives. |
|
|
19
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, HTTPBearer, APIKeyHeader` |
|
|
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
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
22
|
| **WebSocket Exceptions**| `from rustapi import WebSocketDisconnect, WebSocketException` | Exception classes for handling WebSocket disconnection events. |
|
|
23
23
|
|
|
@@ -47,7 +47,7 @@ Serve custom interactive documentation endpoints using `get_swagger_ui_html` and
|
|
|
47
47
|
|
|
48
48
|
```python
|
|
49
49
|
from rustapi import FastAPI
|
|
50
|
-
from rustapi.openapi import get_swagger_ui_html, get_redoc_html
|
|
50
|
+
from rustapi.openapi import get_swagger_ui_html, get_redoc_html, get_openapi
|
|
51
51
|
|
|
52
52
|
app = FastAPI()
|
|
53
53
|
|
|
@@ -58,6 +58,44 @@ def custom_swagger_ui():
|
|
|
58
58
|
title="My Custom API Docs",
|
|
59
59
|
)
|
|
60
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
|
+
|
|
61
99
|
@app.get("/redoc", include_in_schema=False)
|
|
62
100
|
def custom_redoc_ui():
|
|
63
101
|
return get_redoc_html(
|
|
@@ -219,21 +257,93 @@ async def analyze(req: Request):
|
|
|
219
257
|
|
|
220
258
|
---
|
|
221
259
|
|
|
222
|
-
##
|
|
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
|
|
223
275
|
|
|
224
|
-
|
|
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**:
|
|
225
277
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
`FastAPI(title="My API", description="...", version="1.0.0", openapi_url="/openapi.json", docs_url="/docs", redoc_url="/redoc")` accepts all standard metadata keyword arguments without constructor errors.
|
|
230
|
-
3. **Custom Exception Handlers (`@app.exception_handler`)**:
|
|
231
|
-
Register custom status code or exception class handlers via `@app.exception_handler(CustomException)`.
|
|
232
|
-
4. **`HTTPBearer()` Credentials Container**:
|
|
233
|
-
`security = HTTPBearer()` returns `HTTPAuthorizationCredentials(scheme="Bearer", credentials="...")` when passed to `Depends(security)`.
|
|
234
|
-
5. **Recursive Dependency Injection (`solve_dependency`)**:
|
|
235
|
-
Injects `request: Request` / `req` parameters automatically and recursively resolves nested `Depends(sub_dep)` calls.
|
|
236
|
-
6. **Dual Async / Sync `UploadFile` Methods**:
|
|
237
|
-
`UploadFile` methods (`file.read()`, `file.seek()`, `file.close()`) support both asynchronous (`await file.read()`) and synchronous (`file.read()`) execution, plus `.file` returns `io.BytesIO`.
|
|
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.
|
|
238
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"]}
|
|
239
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."""
|