pyrustapi 0.3.33__tar.gz → 0.3.34__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.33 → pyrustapi-0.3.34}/Cargo.lock +1 -1
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/Cargo.toml +1 -1
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/PKG-INFO +1 -1
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/pyproject.toml +1 -1
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/__init__.py +30 -2
- pyrustapi-0.3.34/python/rustapi/resolver.py +66 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/router.py +29 -0
- pyrustapi-0.3.34/python/rustapi/security/__init__.py +178 -0
- pyrustapi-0.3.34/python/rustapi/uploads.py +58 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/src/lib.rs +47 -2
- pyrustapi-0.3.34/test-reports/junit.xml +1 -0
- pyrustapi-0.3.34/tests/test_fastapi_all_6_mismatches.py +113 -0
- pyrustapi-0.3.33/python/rustapi/security/__init__.py +0 -80
- pyrustapi-0.3.33/test-reports/junit.xml +0 -1
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/.github/workflows/CI.yml +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/.github/workflows/pages.yml +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/.github/workflows/release.yml +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/.github/workflows/static.yml +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/.gitignore +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/LICENSE +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/README.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/ROADMAP.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmark_dashboard.html +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/benchmark.sh +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/fastapi_app.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/fastapi_cpu_app.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/fastapi_edge_app.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/fastapi_oha.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/large.json +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/mixed.json +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/rustapi_app.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/rustapi_cpu_app.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/rustapi_edge_app.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/rustapi_multiworker_app.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/rustapi_oha.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/rustapi_tier3_app.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/tier_app.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/validate.json +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/run_3tier_benchmark.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/run_comparison_benchmark.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/run_cpu_stability_benchmark.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/run_edgecase_benchmark.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/run_native_tier3_benchmark.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/run_real_oha_benchmark.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/run_release_highspeed_benchmark.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks.html +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/crud_benchmark/benchmark_db.sqlite +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/crud_benchmark/benchmark_db.sqlite-shm +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/crud_benchmark/benchmark_db.sqlite-wal +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/crud_benchmark/benchmark_results.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/crud_benchmark/fastapi_crud.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/crud_benchmark/run_crud_benchmark.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/crud_benchmark/rustapi_crud.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/3tier_architecture_and_benchmarks.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/DeepAnalytics.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/api-reference.html +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/benchmark_dashboard.html +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/benchmarks.html +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/database.html +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/database_engine.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/docs.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/fastapi_compatibility_and_features.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/getting-started.html +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/getting_started.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/index.html +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/mcp-server.html +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/mcp_server.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/native_business_logic.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/performance_and_benchmarks.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/power_primitives.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/pyrustapi-logo (1).png +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/report_01_.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/response_types.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/security-power-modules.html +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/swagger_ui_and_dx_deviations.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/examples/app.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/index.html +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/performance-reports/cpu_intensive_stability_report.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/performance-reports/fastapi_vs_hybrid_vs_native.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/performance-reports/fastapi_vs_rustapi_comparison.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/performance-reports/fastapi_vs_rustapi_crud_benchmark.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/performance-reports/performance_and_benchmarks.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/performance-reports/rust_native_edgecases_report.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/performance-reports/three_tier_architecture_evaluation.md +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/pyrustapi-logo.png +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/pyrustapi/__init__.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/__init__.pyi +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/background.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/depends.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/encoders.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/exceptions.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/middleware/__init__.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/middleware/cors.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/openapi/__init__.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/openapi/docs.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/param_functions.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/py.typed +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/responses/__init__.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/rust.rs +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/sse.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/staticfiles.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/status.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/scratch/.gitkeep +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_3tier_architecture.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_basic.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_database_engine.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_fastapi_compatibility.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_file_uploads_and_dx.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_imports.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_native_routes.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_overrides_and_coercion.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_reload_and_logging.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_response_model.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_responses_and_req_json.py +0 -0
- {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_rust_power_modules.py +0 -0
|
@@ -35,14 +35,42 @@ from . import responses, middleware
|
|
|
35
35
|
class FastAPI(Engine):
|
|
36
36
|
"""FastAPI-compatible application class wrapping the Rust Tokio core engine."""
|
|
37
37
|
|
|
38
|
-
def
|
|
38
|
+
def __new__(cls, *args, **kwargs):
|
|
39
|
+
return super().__new__(cls)
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
title: str = "RustAPI",
|
|
44
|
+
description: str = "",
|
|
45
|
+
version: str = "0.1.0",
|
|
46
|
+
openapi_url: Optional[str] = "/openapi.json",
|
|
47
|
+
docs_url: Optional[str] = "/docs",
|
|
48
|
+
redoc_url: Optional[str] = "/redoc",
|
|
49
|
+
swagger_ui_oauth2_redirect_url: Optional[str] = "/docs/oauth2-redirect",
|
|
50
|
+
**kwargs: Any,
|
|
51
|
+
):
|
|
39
52
|
super().__init__()
|
|
53
|
+
self.title = title
|
|
54
|
+
self.description = description
|
|
55
|
+
self.version = version
|
|
56
|
+
self.openapi_url = openapi_url
|
|
57
|
+
self.docs_url = docs_url
|
|
58
|
+
self.redoc_url = redoc_url
|
|
59
|
+
self.swagger_ui_oauth2_redirect_url = swagger_ui_oauth2_redirect_url
|
|
60
|
+
self.exception_handlers: Dict[Any, Any] = {}
|
|
40
61
|
self.middlewares: list = []
|
|
41
62
|
|
|
42
63
|
def add_middleware(self, middleware_cls: type, **kwargs: Any):
|
|
43
64
|
"""Add middleware (such as CORSMiddleware) to application configuration."""
|
|
44
65
|
self.middlewares.append((middleware_cls, kwargs))
|
|
45
66
|
|
|
67
|
+
def exception_handler(self, exc_class_or_status_code: Any):
|
|
68
|
+
"""Register an exception handler decorator for an exception class or status code."""
|
|
69
|
+
def decorator(func: Any):
|
|
70
|
+
self.exception_handlers[exc_class_or_status_code] = func
|
|
71
|
+
return func
|
|
72
|
+
return decorator
|
|
73
|
+
|
|
46
74
|
def frontend(self, path: str = "/", directory: str = "dist"):
|
|
47
75
|
"""Serve a built static frontend app (e.g. Vite, React, Vue, Svelte output directory)."""
|
|
48
76
|
from .staticfiles import StaticFiles
|
|
@@ -124,7 +152,7 @@ class RedirectResponse(Response):
|
|
|
124
152
|
return Response.__new__(cls, content="", status_code=status_code, headers=h)
|
|
125
153
|
|
|
126
154
|
|
|
127
|
-
__version__ = "0.3.
|
|
155
|
+
__version__ = "0.3.34"
|
|
128
156
|
__all__ = [
|
|
129
157
|
"Engine",
|
|
130
158
|
"FastAPI",
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import asyncio
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
from .depends import Depends
|
|
5
|
+
from .exceptions import HTTPException
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def solve_dependency(
|
|
9
|
+
dep_target: Any,
|
|
10
|
+
request: Any,
|
|
11
|
+
dependency_overrides: Optional[Dict[Any, Any]] = None,
|
|
12
|
+
cache: Optional[Dict[Any, Any]] = None,
|
|
13
|
+
) -> Any:
|
|
14
|
+
"""Recursively resolve FastAPI dependencies with request injection and caching."""
|
|
15
|
+
if cache is None:
|
|
16
|
+
cache = {}
|
|
17
|
+
|
|
18
|
+
if dependency_overrides and dep_target in dependency_overrides:
|
|
19
|
+
dep_target = dependency_overrides[dep_target]
|
|
20
|
+
|
|
21
|
+
if isinstance(dep_target, Depends):
|
|
22
|
+
dep_target = dep_target.dependency
|
|
23
|
+
|
|
24
|
+
if not callable(dep_target):
|
|
25
|
+
return dep_target
|
|
26
|
+
|
|
27
|
+
target_id = id(dep_target)
|
|
28
|
+
if target_id in cache:
|
|
29
|
+
return cache[target_id]
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
sig = inspect.signature(dep_target)
|
|
33
|
+
kwargs = {}
|
|
34
|
+
for param_name, param in sig.parameters.items():
|
|
35
|
+
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
# 1. Inject request if parameter expects Request
|
|
39
|
+
if param_name in ("request", "req") or (param.annotation and getattr(param.annotation, "__name__", "") in ("PyRequest", "Request")):
|
|
40
|
+
kwargs[param_name] = request
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
# 2. Check if default value is Depends(...)
|
|
44
|
+
default_val = param.default
|
|
45
|
+
if isinstance(default_val, Depends):
|
|
46
|
+
sub_target = default_val.dependency or param.annotation
|
|
47
|
+
kwargs[param_name] = await solve_dependency(sub_target, request, dependency_overrides, cache)
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
except (ValueError, TypeError):
|
|
51
|
+
sig = None
|
|
52
|
+
kwargs = {}
|
|
53
|
+
|
|
54
|
+
if not kwargs and not sig:
|
|
55
|
+
try:
|
|
56
|
+
res = dep_target(request) if callable(dep_target) else dep_target
|
|
57
|
+
except TypeError:
|
|
58
|
+
res = dep_target()
|
|
59
|
+
else:
|
|
60
|
+
if asyncio.iscoroutinefunction(dep_target) or (hasattr(dep_target, "__call__") and asyncio.iscoroutinefunction(dep_target.__call__)):
|
|
61
|
+
res = await dep_target(**kwargs)
|
|
62
|
+
else:
|
|
63
|
+
res = dep_target(**kwargs)
|
|
64
|
+
|
|
65
|
+
cache[target_id] = res
|
|
66
|
+
return res
|
|
@@ -45,6 +45,35 @@ class APIRouter:
|
|
|
45
45
|
return func
|
|
46
46
|
return decorator
|
|
47
47
|
|
|
48
|
+
def include_router(
|
|
49
|
+
self,
|
|
50
|
+
router: APIRouter,
|
|
51
|
+
prefix: str = "",
|
|
52
|
+
tags: Optional[List[str]] = None,
|
|
53
|
+
dependencies: Optional[List[Any]] = None,
|
|
54
|
+
**kwargs: Any,
|
|
55
|
+
):
|
|
56
|
+
"""Include a sub-router into this APIRouter instance."""
|
|
57
|
+
base_prefix = f"{prefix.rstrip('/')}{router.prefix}"
|
|
58
|
+
merged_tags = list(dict.fromkeys((tags or []) + (router.tags or [])))
|
|
59
|
+
merged_deps = (dependencies or []) + (router.dependencies or [])
|
|
60
|
+
|
|
61
|
+
for item in router.routes:
|
|
62
|
+
method, sub_path, func, response_model, route_kwargs = item
|
|
63
|
+
full_path = f"{base_prefix}{sub_path}".replace("//", "/")
|
|
64
|
+
if not full_path.startswith("/"):
|
|
65
|
+
full_path = f"/{full_path}"
|
|
66
|
+
|
|
67
|
+
r_kw = route_kwargs.copy() if route_kwargs else {}
|
|
68
|
+
r_tags = list(dict.fromkeys(merged_tags + r_kw.get("tags", [])))
|
|
69
|
+
r_deps = merged_deps + r_kw.get("dependencies", [])
|
|
70
|
+
if r_tags:
|
|
71
|
+
r_kw["tags"] = r_tags
|
|
72
|
+
if r_deps:
|
|
73
|
+
r_kw["dependencies"] = r_deps
|
|
74
|
+
|
|
75
|
+
self.routes.append((method, full_path, func, response_model, r_kw))
|
|
76
|
+
|
|
48
77
|
def get(self, path: str, response_model: Optional[Type[Any]] = None, **kwargs: Any):
|
|
49
78
|
return self._add("GET", path, response_model=response_model, **kwargs)
|
|
50
79
|
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from typing import Optional, Dict, Any
|
|
2
|
+
from ..exceptions import HTTPException
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class HTTPAuthorizationCredentials:
|
|
6
|
+
"""FastAPI-compatible HTTP Authorization credentials container."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, scheme: str, credentials: str):
|
|
9
|
+
self.scheme = scheme
|
|
10
|
+
self.credentials = credentials
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HTTPBasic:
|
|
14
|
+
"""HTTP Basic authentication dependency helper."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, *, realm: Optional[str] = None, auto_error: bool = True):
|
|
17
|
+
self.realm = realm
|
|
18
|
+
self.auto_error = auto_error
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class HTTPBearer:
|
|
22
|
+
"""HTTP Bearer authentication dependency helper."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, *, bearerFormat: Optional[str] = None, auto_error: bool = True):
|
|
25
|
+
self.bearerFormat = bearerFormat
|
|
26
|
+
self.auto_error = auto_error
|
|
27
|
+
|
|
28
|
+
def __call__(self, request: Any = None, req: Any = None) -> Optional[HTTPAuthorizationCredentials]:
|
|
29
|
+
r = request or req
|
|
30
|
+
if not r or not hasattr(r, "headers"):
|
|
31
|
+
if self.auto_error:
|
|
32
|
+
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
auth_header = r.headers.get("authorization") or r.headers.get("Authorization")
|
|
36
|
+
if not auth_header:
|
|
37
|
+
if self.auto_error:
|
|
38
|
+
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
scheme, _, credentials = auth_header.partition(" ")
|
|
42
|
+
if scheme.lower() != "bearer" or not credentials:
|
|
43
|
+
if self.auto_error:
|
|
44
|
+
raise HTTPException(status_code=401, detail="Invalid authentication scheme")
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
return HTTPAuthorizationCredentials(scheme="Bearer", credentials=credentials)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class OAuth2PasswordBearer:
|
|
51
|
+
"""OAuth2 password flow with bearer token dependency helper."""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
tokenUrl: str,
|
|
56
|
+
scheme_name: Optional[str] = None,
|
|
57
|
+
scopes: Optional[Dict[str, str]] = None,
|
|
58
|
+
description: Optional[str] = None,
|
|
59
|
+
auto_error: bool = True,
|
|
60
|
+
):
|
|
61
|
+
self.tokenUrl = tokenUrl
|
|
62
|
+
self.scheme_name = scheme_name
|
|
63
|
+
self.scopes = scopes or {}
|
|
64
|
+
self.description = description
|
|
65
|
+
self.auto_error = auto_error
|
|
66
|
+
|
|
67
|
+
def __call__(self, request: Any = None, req: Any = None) -> Optional[str]:
|
|
68
|
+
r = request or req
|
|
69
|
+
if not r or not hasattr(r, "headers"):
|
|
70
|
+
if self.auto_error:
|
|
71
|
+
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
auth_header = r.headers.get("authorization") or r.headers.get("Authorization")
|
|
75
|
+
if not auth_header:
|
|
76
|
+
if self.auto_error:
|
|
77
|
+
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
scheme, _, token = auth_header.partition(" ")
|
|
81
|
+
if scheme.lower() != "bearer" or not token:
|
|
82
|
+
if self.auto_error:
|
|
83
|
+
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
return token
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class OAuth2PasswordRequestForm:
|
|
90
|
+
"""OAuth2 password request form for token extraction."""
|
|
91
|
+
|
|
92
|
+
def __init__(
|
|
93
|
+
self,
|
|
94
|
+
grant_type: str = "password",
|
|
95
|
+
username: str = "",
|
|
96
|
+
password: str = "",
|
|
97
|
+
scope: str = "",
|
|
98
|
+
client_id: Optional[str] = None,
|
|
99
|
+
client_secret: Optional[str] = None,
|
|
100
|
+
):
|
|
101
|
+
self.grant_type = grant_type
|
|
102
|
+
self.username = username
|
|
103
|
+
self.password = password
|
|
104
|
+
self.scopes = scope.split()
|
|
105
|
+
self.client_id = client_id
|
|
106
|
+
self.client_secret = client_secret
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class APIKeyHeader:
|
|
110
|
+
"""API Key authentication header dependency helper."""
|
|
111
|
+
|
|
112
|
+
def __init__(self, *, name: str, auto_error: bool = True):
|
|
113
|
+
self.name = name
|
|
114
|
+
self.auto_error = auto_error
|
|
115
|
+
|
|
116
|
+
def __call__(self, request: Any = None, req: Any = None) -> Optional[str]:
|
|
117
|
+
r = request or req
|
|
118
|
+
if not r or not hasattr(r, "headers"):
|
|
119
|
+
if self.auto_error:
|
|
120
|
+
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
key = r.headers.get(self.name) or r.headers.get(self.name.lower())
|
|
124
|
+
if not key and self.auto_error:
|
|
125
|
+
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
126
|
+
return key
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class APIKeyQuery:
|
|
130
|
+
"""API Key authentication query parameter dependency helper."""
|
|
131
|
+
|
|
132
|
+
def __init__(self, *, name: str, auto_error: bool = True):
|
|
133
|
+
self.name = name
|
|
134
|
+
self.auto_error = auto_error
|
|
135
|
+
|
|
136
|
+
def __call__(self, request: Any = None, req: Any = None) -> Optional[str]:
|
|
137
|
+
r = request or req
|
|
138
|
+
if not r or not hasattr(r, "query_params"):
|
|
139
|
+
if self.auto_error:
|
|
140
|
+
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
key = r.query_params.get(self.name)
|
|
144
|
+
if not key and self.auto_error:
|
|
145
|
+
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
146
|
+
return key
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class APIKeyCookie:
|
|
150
|
+
"""API Key authentication cookie dependency helper."""
|
|
151
|
+
|
|
152
|
+
def __init__(self, *, name: str, auto_error: bool = True):
|
|
153
|
+
self.name = name
|
|
154
|
+
self.auto_error = auto_error
|
|
155
|
+
|
|
156
|
+
def __call__(self, request: Any = None, req: Any = None) -> Optional[str]:
|
|
157
|
+
r = request or req
|
|
158
|
+
if not r or not hasattr(r, "cookies"):
|
|
159
|
+
if self.auto_error:
|
|
160
|
+
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
key = r.cookies.get(self.name)
|
|
164
|
+
if not key and self.auto_error:
|
|
165
|
+
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
166
|
+
return key
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
__all__ = [
|
|
170
|
+
"HTTPAuthorizationCredentials",
|
|
171
|
+
"HTTPBasic",
|
|
172
|
+
"HTTPBearer",
|
|
173
|
+
"OAuth2PasswordBearer",
|
|
174
|
+
"OAuth2PasswordRequestForm",
|
|
175
|
+
"APIKeyHeader",
|
|
176
|
+
"APIKeyQuery",
|
|
177
|
+
"APIKeyCookie",
|
|
178
|
+
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import io
|
|
2
|
+
from typing import Optional, Dict, Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AwaitableBytes(bytes):
|
|
6
|
+
"""Bytes subclass that is directly awaitable for async compatibility (await file.read())."""
|
|
7
|
+
def __await__(self):
|
|
8
|
+
async def _wrap():
|
|
9
|
+
return self
|
|
10
|
+
return _wrap().__await__()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AwaitableInt(int):
|
|
14
|
+
"""Integer subclass that is directly awaitable for async compatibility (await file.seek())."""
|
|
15
|
+
def __await__(self):
|
|
16
|
+
async def _wrap():
|
|
17
|
+
return self
|
|
18
|
+
return _wrap().__await__()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AwaitableNone:
|
|
22
|
+
"""None wrapper that is directly awaitable for async compatibility (await file.close())."""
|
|
23
|
+
def __await__(self):
|
|
24
|
+
async def _wrap():
|
|
25
|
+
return None
|
|
26
|
+
return _wrap().__await__()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class UploadFile:
|
|
30
|
+
"""FastAPI-compatible UploadFile supporting both async (await file.read()) and sync access."""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
file: Optional[io.BytesIO] = None,
|
|
35
|
+
filename: Optional[str] = "",
|
|
36
|
+
content_type: str = "",
|
|
37
|
+
headers: Optional[Dict[str, str]] = None,
|
|
38
|
+
):
|
|
39
|
+
self.file = file or io.BytesIO()
|
|
40
|
+
self.filename = filename or ""
|
|
41
|
+
self.content_type = content_type or ""
|
|
42
|
+
self.headers = headers or {}
|
|
43
|
+
|
|
44
|
+
def read(self, size: int = -1) -> AwaitableBytes:
|
|
45
|
+
content = self.file.read(size)
|
|
46
|
+
return AwaitableBytes(content)
|
|
47
|
+
|
|
48
|
+
def seek(self, offset: int = 0) -> AwaitableInt:
|
|
49
|
+
res = self.file.seek(offset)
|
|
50
|
+
return AwaitableInt(res)
|
|
51
|
+
|
|
52
|
+
def write(self, data: bytes) -> AwaitableInt:
|
|
53
|
+
res = self.file.write(data)
|
|
54
|
+
return AwaitableInt(res)
|
|
55
|
+
|
|
56
|
+
def close(self) -> AwaitableNone:
|
|
57
|
+
self.file.close()
|
|
58
|
+
return AwaitableNone()
|
|
@@ -506,8 +506,53 @@ struct PyUploadFile {
|
|
|
506
506
|
|
|
507
507
|
#[pymethods]
|
|
508
508
|
impl PyUploadFile {
|
|
509
|
-
|
|
510
|
-
|
|
509
|
+
#[new]
|
|
510
|
+
#[pyo3(signature = (filename="".to_string(), content_type="".to_string(), file_data=Vec::new()))]
|
|
511
|
+
fn new(filename: String, content_type: String, file_data: Vec<u8>) -> Self {
|
|
512
|
+
PyUploadFile { filename, content_type, file_data }
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
#[pyo3(signature = (_size=-1))]
|
|
516
|
+
fn read(&self, py: Python<'_>, _size: i64) -> PyResult<PyObject> {
|
|
517
|
+
let bytes_obj = PyBytes::new_bound(py, &self.file_data);
|
|
518
|
+
if let Ok(py_module) = py.import_bound("rustapi.uploads") {
|
|
519
|
+
if let Ok(awaitable_cls) = py_module.getattr("AwaitableBytes") {
|
|
520
|
+
if let Ok(res) = awaitable_cls.call1((&bytes_obj,)) {
|
|
521
|
+
return Ok(res.unbind());
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
Ok(bytes_obj.into())
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
#[pyo3(signature = (_offset=0))]
|
|
529
|
+
fn seek(&self, py: Python<'_>, _offset: i64) -> PyResult<PyObject> {
|
|
530
|
+
if let Ok(py_module) = py.import_bound("rustapi.uploads") {
|
|
531
|
+
if let Ok(awaitable_cls) = py_module.getattr("AwaitableInt") {
|
|
532
|
+
if let Ok(res) = awaitable_cls.call1((0,)) {
|
|
533
|
+
return Ok(res.unbind());
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
Ok(0.to_object(py))
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
fn close(&self, py: Python<'_>) -> PyResult<PyObject> {
|
|
541
|
+
if let Ok(py_module) = py.import_bound("rustapi.uploads") {
|
|
542
|
+
if let Ok(awaitable_cls) = py_module.getattr("AwaitableNone") {
|
|
543
|
+
if let Ok(res) = awaitable_cls.call0() {
|
|
544
|
+
return Ok(res.unbind());
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
Ok(py.None())
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
#[getter]
|
|
552
|
+
fn file(&self, py: Python<'_>) -> PyResult<PyObject> {
|
|
553
|
+
let io = py.import_bound("io")?;
|
|
554
|
+
let bytes_obj = PyBytes::new_bound(py, &self.file_data);
|
|
555
|
+
io.call_method1("BytesIO", (bytes_obj,)).map(|b| b.unbind())
|
|
511
556
|
}
|
|
512
557
|
}
|
|
513
558
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?><testsuites name="pytest tests"><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="68" time="2.909" timestamp="2026-08-05T23:40:38.618470+05:30" hostname="boopathirajsMBP.lan"><testcase classname="tests.test_3tier_architecture" name="test_tier1_pure_python" time="0.015" /><testcase classname="tests.test_3tier_architecture" name="test_tier2_hybrid_rust_db" time="0.004" /><testcase classname="tests.test_3tier_architecture" name="test_tier2_hybrid_rust_template" time="0.005" /><testcase classname="tests.test_3tier_architecture" name="test_tier2_hybrid_rust_jwt" time="0.005" /><testcase classname="tests.test_3tier_architecture" name="test_tier3_native_rust_routes" time="0.009" /><testcase classname="tests.test_basic" name="test_root_endpoint" time="0.008" /><testcase classname="tests.test_basic" name="test_sync_endpoint" time="0.016" /><testcase classname="tests.test_basic" name="test_async_endpoint" time="0.106" /><testcase classname="tests.test_basic" name="test_path_and_query_params" time="0.005" /><testcase classname="tests.test_basic" name="test_pydantic_validation" time="0.004" /><testcase classname="tests.test_basic" name="test_openapi_generation" time="0.015" /><testcase classname="tests.test_basic" name="test_docs_endpoint_serves_swagger_ui" time="0.003" /><testcase classname="tests.test_basic" name="test_not_found_error" time="0.003" /><testcase classname="tests.test_basic" name="test_mcp_initialize" time="0.003" /><testcase classname="tests.test_basic" name="test_mcp_tools_list" time="0.003" /><testcase classname="tests.test_basic" name="test_mcp_tools_call" time="0.004" /><testcase classname="tests.test_basic" name="test_mcp_tools_call_string_result" time="0.003" /><testcase classname="tests.test_basic" name="test_mcp_tools_call_unknown" time="0.004" /><testcase classname="tests.test_basic" name="test_mcp_resources_list_and_read" time="0.021" /><testcase classname="tests.test_basic" name="test_mcp_resources_read_unknown" time="0.003" /><testcase classname="tests.test_basic" name="test_mcp_prompts_list_and_get" time="0.006" /><testcase classname="tests.test_basic" name="test_mcp_ping" time="0.004" /><testcase classname="tests.test_basic" name="test_mcp_unknown_method" time="0.004" /><testcase classname="tests.test_basic" name="test_mcp_notification_no_response_body" time="0.003" /><testcase classname="tests.test_database_engine" name="test_native_database_execute_and_query" time="0.007" /><testcase classname="tests.test_database_engine" name="test_direct_db_methods" time="0.002" /><testcase classname="tests.test_fastapi_all_6_mismatches" name="test_1_apirouter_include_router" time="0.002" /><testcase classname="tests.test_fastapi_all_6_mismatches" name="test_2_fastapi_constructor_kwargs" time="0.002" /><testcase classname="tests.test_fastapi_all_6_mismatches" name="test_3_exception_handlers" time="0.004" /><testcase classname="tests.test_fastapi_all_6_mismatches" name="test_4_http_bearer_and_credentials" time="0.001" /><testcase classname="tests.test_fastapi_all_6_mismatches" name="test_5_dependency_resolver_with_request_and_nested_depends" time="0.008" /><testcase classname="tests.test_fastapi_all_6_mismatches" name="test_6_upload_file_sync_and_async_methods" time="0.001" /><testcase classname="tests.test_fastapi_compatibility" name="test_fastapi_and_request_aliases" time="0.000" /><testcase classname="tests.test_fastapi_compatibility" name="test_status_codes" time="0.001" /><testcase classname="tests.test_fastapi_compatibility" name="test_openapi_docs_html_generation" time="0.001" /><testcase classname="tests.test_fastapi_compatibility" name="test_jsonable_encoder" time="0.002" /><testcase classname="tests.test_fastapi_compatibility" name="test_sse_helpers" time="0.001" /><testcase classname="tests.test_fastapi_compatibility" name="test_param_functions" time="0.001" /><testcase classname="tests.test_fastapi_compatibility" name="test_security_helpers" time="0.001" /><testcase classname="tests.test_fastapi_compatibility" name="test_websocket_exceptions" time="0.052" /><testcase classname="tests.test_fastapi_compatibility" name="test_frontend_app_serving" time="0.008" /><testcase classname="tests.test_fastapi_compatibility" name="test_app_and_router_advanced_fastapi_features" time="0.005" /><testcase classname="tests.test_fastapi_compatibility" name="test_user_router_tags_in_openapi_spec" time="0.311" /><testcase classname="tests.test_file_uploads_and_dx" name="test_single_file_upload" time="0.007" /><testcase classname="tests.test_file_uploads_and_dx" name="test_binary_image_upload" time="0.023" /><testcase classname="tests.test_file_uploads_and_dx" name="test_multiple_file_upload" time="0.004" /><testcase classname="tests.test_file_uploads_and_dx" name="test_custom_openapi_route" time="0.003" /><testcase classname="tests.test_imports" name="test_public_package_exports_work" time="0.001" /><testcase classname="tests.test_imports" name="test_pyrustapi_alias_import_works" time="0.001" /><testcase classname="tests.test_imports" name="test_repo_root_import_prefers_workspace_package" time="0.261" /><testcase classname="tests.test_native_routes" name="test_native_route_registration_and_execution" time="0.003" /><testcase classname="tests.test_overrides_and_coercion" name="test_dependency_overrides" time="0.009" /><testcase classname="tests.test_overrides_and_coercion" name="test_strict_type_coercion_valid_int" time="0.005" /><testcase classname="tests.test_overrides_and_coercion" name="test_strict_type_coercion_invalid_int_returns_422" time="0.006" /><testcase classname="tests.test_overrides_and_coercion" name="test_missing_required_query_param_returns_422" time="0.007" /><testcase classname="tests.test_reload_and_logging" name="test_reload_mode_spawns_server_and_outputs_logs" time="0.811" /><testcase classname="tests.test_response_model" name="test_single_response_model_filters_private_fields" time="0.022" /><testcase classname="tests.test_response_model" name="test_list_response_model_filters_items" time="0.004" /><testcase classname="tests.test_responses_and_req_json" name="test_html_response" time="0.009" /><testcase classname="tests.test_responses_and_req_json" name="test_plain_text_response" time="0.004" /><testcase classname="tests.test_responses_and_req_json" name="test_json_response" time="0.005" /><testcase classname="tests.test_responses_and_req_json" name="test_redirect_response" time="0.008" /><testcase classname="tests.test_responses_and_req_json" name="test_req_json_parsing" time="0.087" /><testcase classname="tests.test_rust_power_modules" name="test_jwt_encode_and_decode" time="0.007" /><testcase classname="tests.test_rust_power_modules" name="test_jwt_decode_invalid_secret_raises_value_error" time="0.001" /><testcase classname="tests.test_rust_power_modules" name="test_argon2_password_hashing_and_verification" time="0.136" /><testcase classname="tests.test_rust_power_modules" name="test_minijinja_template_rendering" time="0.001" /><testcase classname="tests.test_rust_power_modules" name="test_integrated_http_auth_route" time="0.075" /></testsuite></testsuites>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import requests
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
import asyncio
|
|
6
|
+
from rustapi import FastAPI, APIRouter, Request, Depends, HTTPException, status
|
|
7
|
+
from rustapi.security import HTTPBearer, HTTPAuthorizationCredentials, OAuth2PasswordBearer
|
|
8
|
+
from rustapi.uploads import UploadFile
|
|
9
|
+
from rustapi.responses import JSONResponse
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_1_apirouter_include_router():
|
|
13
|
+
sub_router = APIRouter(prefix="/v1", tags=["sub"])
|
|
14
|
+
|
|
15
|
+
@sub_router.get("/ping")
|
|
16
|
+
def ping():
|
|
17
|
+
return {"ping": "pong"}
|
|
18
|
+
|
|
19
|
+
parent_router = APIRouter(prefix="/api", tags=["parent"])
|
|
20
|
+
parent_router.include_router(sub_router, prefix="/nested")
|
|
21
|
+
|
|
22
|
+
app = FastAPI()
|
|
23
|
+
app.include_router(parent_router)
|
|
24
|
+
|
|
25
|
+
# Check that route was registered under /api/nested/v1/ping
|
|
26
|
+
route_paths = [r[1] for r in parent_router.routes]
|
|
27
|
+
assert "/nested/v1/ping" in route_paths
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_2_fastapi_constructor_kwargs():
|
|
31
|
+
app = FastAPI(
|
|
32
|
+
title="My Custom API",
|
|
33
|
+
description="A great API",
|
|
34
|
+
version="2.0.0",
|
|
35
|
+
openapi_url="/custom_openapi.json",
|
|
36
|
+
docs_url="/custom_docs",
|
|
37
|
+
redoc_url="/custom_redoc",
|
|
38
|
+
)
|
|
39
|
+
assert app.title == "My Custom API"
|
|
40
|
+
assert app.description == "A great API"
|
|
41
|
+
assert app.version == "2.0.0"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_3_exception_handlers():
|
|
45
|
+
app = FastAPI()
|
|
46
|
+
|
|
47
|
+
class CustomError(Exception):
|
|
48
|
+
def __init__(self, message: str):
|
|
49
|
+
self.message = message
|
|
50
|
+
|
|
51
|
+
@app.exception_handler(CustomError)
|
|
52
|
+
def handle_custom_error(request: Request, exc: CustomError):
|
|
53
|
+
return JSONResponse(status_code=418, content={"custom_detail": exc.message})
|
|
54
|
+
|
|
55
|
+
assert CustomError in app.exception_handlers
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_4_http_bearer_and_credentials():
|
|
59
|
+
bearer_scheme = HTTPBearer()
|
|
60
|
+
|
|
61
|
+
# Fake request with headers
|
|
62
|
+
class FakeRequest:
|
|
63
|
+
headers = {"authorization": "Bearer secret_token_123"}
|
|
64
|
+
|
|
65
|
+
creds = bearer_scheme(FakeRequest())
|
|
66
|
+
assert isinstance(creds, HTTPAuthorizationCredentials)
|
|
67
|
+
assert creds.scheme == "Bearer"
|
|
68
|
+
assert creds.credentials == "secret_token_123"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_5_dependency_resolver_with_request_and_nested_depends():
|
|
72
|
+
from rustapi.resolver import solve_dependency
|
|
73
|
+
|
|
74
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
|
75
|
+
|
|
76
|
+
async def get_current_user(request: Request, token: str = Depends(oauth2_scheme)):
|
|
77
|
+
assert token == "secret_token_abc"
|
|
78
|
+
return {"username": "alice", "path": request.path}
|
|
79
|
+
|
|
80
|
+
async def get_admin_user(request: Request, user=Depends(get_current_user)):
|
|
81
|
+
return {"admin": user["username"], "path": user["path"]}
|
|
82
|
+
|
|
83
|
+
class FakeReq:
|
|
84
|
+
path = "/admin/dashboard"
|
|
85
|
+
headers = {"authorization": "Bearer secret_token_abc"}
|
|
86
|
+
|
|
87
|
+
res = asyncio.run(solve_dependency(get_admin_user, FakeReq()))
|
|
88
|
+
assert res == {"admin": "alice", "path": "/admin/dashboard"}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_6_upload_file_sync_and_async_methods():
|
|
92
|
+
import io
|
|
93
|
+
|
|
94
|
+
file_obj = UploadFile(file=io.BytesIO(b"Hello World Content"), filename="photo.png", content_type="image/png")
|
|
95
|
+
assert file_obj.filename == "photo.png"
|
|
96
|
+
assert file_obj.content_type == "image/png"
|
|
97
|
+
|
|
98
|
+
async def _test():
|
|
99
|
+
# Async read
|
|
100
|
+
content_async = await file_obj.read()
|
|
101
|
+
assert content_async == b"Hello World Content"
|
|
102
|
+
assert isinstance(content_async, bytes)
|
|
103
|
+
|
|
104
|
+
# Sync seek & read
|
|
105
|
+
file_obj.seek(0)
|
|
106
|
+
content_sync = file_obj.read()
|
|
107
|
+
assert content_sync == b"Hello World Content"
|
|
108
|
+
|
|
109
|
+
# Async seek & close
|
|
110
|
+
await file_obj.seek(0)
|
|
111
|
+
await file_obj.close()
|
|
112
|
+
|
|
113
|
+
asyncio.run(_test())
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
from typing import Optional, Dict, Any
|
|
2
|
-
from ..exceptions import HTTPException
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class HTTPBasic:
|
|
6
|
-
"""HTTP Basic authentication dependency helper."""
|
|
7
|
-
|
|
8
|
-
def __init__(self, *, realm: Optional[str] = None, auto_error: bool = True):
|
|
9
|
-
self.realm = realm
|
|
10
|
-
self.auto_error = auto_error
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class HTTPBearer:
|
|
14
|
-
"""HTTP Bearer authentication dependency helper."""
|
|
15
|
-
|
|
16
|
-
def __init__(self, *, bearerFormat: Optional[str] = None, auto_error: bool = True):
|
|
17
|
-
self.bearerFormat = bearerFormat
|
|
18
|
-
self.auto_error = auto_error
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class OAuth2PasswordBearer:
|
|
22
|
-
"""OAuth2 password flow with bearer token dependency helper."""
|
|
23
|
-
|
|
24
|
-
def __init__(
|
|
25
|
-
self,
|
|
26
|
-
tokenUrl: str,
|
|
27
|
-
scheme_name: Optional[str] = None,
|
|
28
|
-
scopes: Optional[Dict[str, str]] = None,
|
|
29
|
-
description: Optional[str] = None,
|
|
30
|
-
auto_error: bool = True,
|
|
31
|
-
):
|
|
32
|
-
self.tokenUrl = tokenUrl
|
|
33
|
-
self.scheme_name = scheme_name
|
|
34
|
-
self.scopes = scopes or {}
|
|
35
|
-
self.description = description
|
|
36
|
-
self.auto_error = auto_error
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class OAuth2PasswordRequestForm:
|
|
40
|
-
"""OAuth2 password request form for token extraction."""
|
|
41
|
-
|
|
42
|
-
def __init__(
|
|
43
|
-
self,
|
|
44
|
-
grant_type: str = "password",
|
|
45
|
-
username: str = "",
|
|
46
|
-
password: str = "",
|
|
47
|
-
scope: str = "",
|
|
48
|
-
client_id: Optional[str] = None,
|
|
49
|
-
client_secret: Optional[str] = None,
|
|
50
|
-
):
|
|
51
|
-
self.grant_type = grant_type
|
|
52
|
-
self.username = username
|
|
53
|
-
self.password = password
|
|
54
|
-
self.scopes = scope.split()
|
|
55
|
-
self.client_id = client_id
|
|
56
|
-
self.client_secret = client_secret
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
class APIKeyHeader:
|
|
60
|
-
"""API Key authentication header dependency helper."""
|
|
61
|
-
|
|
62
|
-
def __init__(self, *, name: str, auto_error: bool = True):
|
|
63
|
-
self.name = name
|
|
64
|
-
self.auto_error = auto_error
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class APIKeyQuery:
|
|
68
|
-
"""API Key authentication query parameter dependency helper."""
|
|
69
|
-
|
|
70
|
-
def __init__(self, *, name: str, auto_error: bool = True):
|
|
71
|
-
self.name = name
|
|
72
|
-
self.auto_error = auto_error
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
class APIKeyCookie:
|
|
76
|
-
"""API Key authentication cookie dependency helper."""
|
|
77
|
-
|
|
78
|
-
def __init__(self, *, name: str, auto_error: bool = True):
|
|
79
|
-
self.name = name
|
|
80
|
-
self.auto_error = auto_error
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="utf-8"?><testsuites name="pytest tests"><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="62" time="2.856" timestamp="2026-08-05T21:23:22.017465+05:30" hostname="boopathirajsMBP.lan"><testcase classname="tests.test_3tier_architecture" name="test_tier1_pure_python" time="0.026" /><testcase classname="tests.test_3tier_architecture" name="test_tier2_hybrid_rust_db" time="0.004" /><testcase classname="tests.test_3tier_architecture" name="test_tier2_hybrid_rust_template" time="0.004" /><testcase classname="tests.test_3tier_architecture" name="test_tier2_hybrid_rust_jwt" time="0.006" /><testcase classname="tests.test_3tier_architecture" name="test_tier3_native_rust_routes" time="0.019" /><testcase classname="tests.test_basic" name="test_root_endpoint" time="0.008" /><testcase classname="tests.test_basic" name="test_sync_endpoint" time="0.003" /><testcase classname="tests.test_basic" name="test_async_endpoint" time="0.106" /><testcase classname="tests.test_basic" name="test_path_and_query_params" time="0.004" /><testcase classname="tests.test_basic" name="test_pydantic_validation" time="0.004" /><testcase classname="tests.test_basic" name="test_openapi_generation" time="0.004" /><testcase classname="tests.test_basic" name="test_docs_endpoint_serves_swagger_ui" time="0.004" /><testcase classname="tests.test_basic" name="test_not_found_error" time="0.003" /><testcase classname="tests.test_basic" name="test_mcp_initialize" time="0.008" /><testcase classname="tests.test_basic" name="test_mcp_tools_list" time="0.008" /><testcase classname="tests.test_basic" name="test_mcp_tools_call" time="0.006" /><testcase classname="tests.test_basic" name="test_mcp_tools_call_string_result" time="0.004" /><testcase classname="tests.test_basic" name="test_mcp_tools_call_unknown" time="0.004" /><testcase classname="tests.test_basic" name="test_mcp_resources_list_and_read" time="0.008" /><testcase classname="tests.test_basic" name="test_mcp_resources_read_unknown" time="0.005" /><testcase classname="tests.test_basic" name="test_mcp_prompts_list_and_get" time="0.007" /><testcase classname="tests.test_basic" name="test_mcp_ping" time="0.005" /><testcase classname="tests.test_basic" name="test_mcp_unknown_method" time="0.014" /><testcase classname="tests.test_basic" name="test_mcp_notification_no_response_body" time="0.008" /><testcase classname="tests.test_database_engine" name="test_native_database_execute_and_query" time="0.010" /><testcase classname="tests.test_database_engine" name="test_direct_db_methods" time="0.001" /><testcase classname="tests.test_fastapi_compatibility" name="test_fastapi_and_request_aliases" time="0.001" /><testcase classname="tests.test_fastapi_compatibility" name="test_status_codes" time="0.001" /><testcase classname="tests.test_fastapi_compatibility" name="test_openapi_docs_html_generation" time="0.001" /><testcase classname="tests.test_fastapi_compatibility" name="test_jsonable_encoder" time="0.003" /><testcase classname="tests.test_fastapi_compatibility" name="test_sse_helpers" time="0.001" /><testcase classname="tests.test_fastapi_compatibility" name="test_param_functions" time="0.001" /><testcase classname="tests.test_fastapi_compatibility" name="test_security_helpers" time="0.001" /><testcase classname="tests.test_fastapi_compatibility" name="test_websocket_exceptions" time="0.001" /><testcase classname="tests.test_fastapi_compatibility" name="test_frontend_app_serving" time="0.005" /><testcase classname="tests.test_fastapi_compatibility" name="test_app_and_router_advanced_fastapi_features" time="0.015" /><testcase classname="tests.test_fastapi_compatibility" name="test_user_router_tags_in_openapi_spec" time="0.316" /><testcase classname="tests.test_file_uploads_and_dx" name="test_single_file_upload" time="0.008" /><testcase classname="tests.test_file_uploads_and_dx" name="test_binary_image_upload" time="0.016" /><testcase classname="tests.test_file_uploads_and_dx" name="test_multiple_file_upload" time="0.004" /><testcase classname="tests.test_file_uploads_and_dx" name="test_custom_openapi_route" time="0.015" /><testcase classname="tests.test_imports" name="test_public_package_exports_work" time="0.001" /><testcase classname="tests.test_imports" name="test_pyrustapi_alias_import_works" time="0.002" /><testcase classname="tests.test_imports" name="test_repo_root_import_prefers_workspace_package" time="0.320" /><testcase classname="tests.test_native_routes" name="test_native_route_registration_and_execution" time="0.003" /><testcase classname="tests.test_overrides_and_coercion" name="test_dependency_overrides" time="0.012" /><testcase classname="tests.test_overrides_and_coercion" name="test_strict_type_coercion_valid_int" time="0.006" /><testcase classname="tests.test_overrides_and_coercion" name="test_strict_type_coercion_invalid_int_returns_422" time="0.005" /><testcase classname="tests.test_overrides_and_coercion" name="test_missing_required_query_param_returns_422" time="0.008" /><testcase classname="tests.test_reload_and_logging" name="test_reload_mode_spawns_server_and_outputs_logs" time="1.182" /><testcase classname="tests.test_response_model" name="test_single_response_model_filters_private_fields" time="0.020" /><testcase classname="tests.test_response_model" name="test_list_response_model_filters_items" time="0.010" /><testcase classname="tests.test_responses_and_req_json" name="test_html_response" time="0.033" /><testcase classname="tests.test_responses_and_req_json" name="test_plain_text_response" time="0.009" /><testcase classname="tests.test_responses_and_req_json" name="test_json_response" time="0.007" /><testcase classname="tests.test_responses_and_req_json" name="test_redirect_response" time="0.013" /><testcase classname="tests.test_responses_and_req_json" name="test_req_json_parsing" time="0.080" /><testcase classname="tests.test_rust_power_modules" name="test_jwt_encode_and_decode" time="0.009" /><testcase classname="tests.test_rust_power_modules" name="test_jwt_decode_invalid_secret_raises_value_error" time="0.002" /><testcase classname="tests.test_rust_power_modules" name="test_argon2_password_hashing_and_verification" time="0.167" /><testcase classname="tests.test_rust_power_modules" name="test_minijinja_template_rendering" time="0.007" /><testcase classname="tests.test_rust_power_modules" name="test_integrated_http_auth_route" time="0.109" /></testsuite></testsuites>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyrustapi-0.3.33 → pyrustapi-0.3.34}/performance-reports/fastapi_vs_rustapi_crud_benchmark.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyrustapi-0.3.33 → pyrustapi-0.3.34}/performance-reports/three_tier_architecture_evaluation.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|