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.
Files changed (115) hide show
  1. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/Cargo.lock +1 -1
  2. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/Cargo.toml +1 -1
  3. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/PKG-INFO +1 -1
  4. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/pyproject.toml +1 -1
  5. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/__init__.py +30 -2
  6. pyrustapi-0.3.34/python/rustapi/resolver.py +66 -0
  7. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/router.py +29 -0
  8. pyrustapi-0.3.34/python/rustapi/security/__init__.py +178 -0
  9. pyrustapi-0.3.34/python/rustapi/uploads.py +58 -0
  10. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/src/lib.rs +47 -2
  11. pyrustapi-0.3.34/test-reports/junit.xml +1 -0
  12. pyrustapi-0.3.34/tests/test_fastapi_all_6_mismatches.py +113 -0
  13. pyrustapi-0.3.33/python/rustapi/security/__init__.py +0 -80
  14. pyrustapi-0.3.33/test-reports/junit.xml +0 -1
  15. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/.github/workflows/CI.yml +0 -0
  16. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/.github/workflows/pages.yml +0 -0
  17. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/.github/workflows/release.yml +0 -0
  18. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/.github/workflows/static.yml +0 -0
  19. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/.gitignore +0 -0
  20. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/LICENSE +0 -0
  21. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/README.md +0 -0
  22. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/ROADMAP.md +0 -0
  23. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmark_dashboard.html +0 -0
  24. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/benchmark.sh +0 -0
  25. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/fastapi_app.py +0 -0
  26. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/fastapi_cpu_app.py +0 -0
  27. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/fastapi_edge_app.py +0 -0
  28. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/fastapi_oha.py +0 -0
  29. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/large.json +0 -0
  30. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/mixed.json +0 -0
  31. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/rustapi_app.py +0 -0
  32. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/rustapi_cpu_app.py +0 -0
  33. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/rustapi_edge_app.py +0 -0
  34. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/rustapi_multiworker_app.py +0 -0
  35. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/rustapi_oha.py +0 -0
  36. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/rustapi_tier3_app.py +0 -0
  37. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/tier_app.py +0 -0
  38. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/data/validate.json +0 -0
  39. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/run_3tier_benchmark.py +0 -0
  40. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/run_comparison_benchmark.py +0 -0
  41. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/run_cpu_stability_benchmark.py +0 -0
  42. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/run_edgecase_benchmark.py +0 -0
  43. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/run_native_tier3_benchmark.py +0 -0
  44. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/run_real_oha_benchmark.py +0 -0
  45. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks/run_release_highspeed_benchmark.py +0 -0
  46. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/benchmarks.html +0 -0
  47. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/crud_benchmark/benchmark_db.sqlite +0 -0
  48. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/crud_benchmark/benchmark_db.sqlite-shm +0 -0
  49. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/crud_benchmark/benchmark_db.sqlite-wal +0 -0
  50. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/crud_benchmark/benchmark_results.md +0 -0
  51. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/crud_benchmark/fastapi_crud.py +0 -0
  52. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/crud_benchmark/run_crud_benchmark.py +0 -0
  53. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/crud_benchmark/rustapi_crud.py +0 -0
  54. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/3tier_architecture_and_benchmarks.md +0 -0
  55. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/DeepAnalytics.md +0 -0
  56. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/api-reference.html +0 -0
  57. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/benchmark_dashboard.html +0 -0
  58. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/benchmarks.html +0 -0
  59. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/database.html +0 -0
  60. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/database_engine.md +0 -0
  61. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/docs.md +0 -0
  62. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/fastapi_compatibility_and_features.md +0 -0
  63. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/getting-started.html +0 -0
  64. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/getting_started.md +0 -0
  65. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/index.html +0 -0
  66. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/mcp-server.html +0 -0
  67. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/mcp_server.md +0 -0
  68. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/native_business_logic.md +0 -0
  69. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/performance_and_benchmarks.md +0 -0
  70. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/power_primitives.md +0 -0
  71. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/pyrustapi-logo (1).png +0 -0
  72. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/report_01_.md +0 -0
  73. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/response_types.md +0 -0
  74. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/security-power-modules.html +0 -0
  75. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/docs/swagger_ui_and_dx_deviations.md +0 -0
  76. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/examples/app.py +0 -0
  77. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/index.html +0 -0
  78. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/performance-reports/cpu_intensive_stability_report.md +0 -0
  79. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/performance-reports/fastapi_vs_hybrid_vs_native.md +0 -0
  80. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/performance-reports/fastapi_vs_rustapi_comparison.md +0 -0
  81. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/performance-reports/fastapi_vs_rustapi_crud_benchmark.md +0 -0
  82. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/performance-reports/performance_and_benchmarks.md +0 -0
  83. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/performance-reports/rust_native_edgecases_report.md +0 -0
  84. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/performance-reports/three_tier_architecture_evaluation.md +0 -0
  85. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/pyrustapi-logo.png +0 -0
  86. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/pyrustapi/__init__.py +0 -0
  87. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/__init__.pyi +0 -0
  88. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/background.py +0 -0
  89. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/depends.py +0 -0
  90. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/encoders.py +0 -0
  91. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/exceptions.py +0 -0
  92. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/middleware/__init__.py +0 -0
  93. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/middleware/cors.py +0 -0
  94. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/openapi/__init__.py +0 -0
  95. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/openapi/docs.py +0 -0
  96. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/param_functions.py +0 -0
  97. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/py.typed +0 -0
  98. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/responses/__init__.py +0 -0
  99. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/rust.rs +0 -0
  100. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/sse.py +0 -0
  101. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/staticfiles.py +0 -0
  102. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/python/rustapi/status.py +0 -0
  103. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/scratch/.gitkeep +0 -0
  104. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_3tier_architecture.py +0 -0
  105. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_basic.py +0 -0
  106. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_database_engine.py +0 -0
  107. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_fastapi_compatibility.py +0 -0
  108. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_file_uploads_and_dx.py +0 -0
  109. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_imports.py +0 -0
  110. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_native_routes.py +0 -0
  111. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_overrides_and_coercion.py +0 -0
  112. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_reload_and_logging.py +0 -0
  113. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_response_model.py +0 -0
  114. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_responses_and_req_json.py +0 -0
  115. {pyrustapi-0.3.33 → pyrustapi-0.3.34}/tests/test_rust_power_modules.py +0 -0
@@ -1427,7 +1427,7 @@ dependencies = [
1427
1427
 
1428
1428
  [[package]]
1429
1429
  name = "rustapi"
1430
- version = "0.3.33"
1430
+ version = "0.3.34"
1431
1431
  dependencies = [
1432
1432
  "argon2",
1433
1433
  "base64 0.23.0",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "rustapi"
3
- version = "0.3.33"
3
+ version = "0.3.34"
4
4
  edition = "2021"
5
5
  description = "A FastAPI-style Python web framework with a Rust tokio/hyper core, plus a built-in MCP server (JSON-RPC over Streamable HTTP)."
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyrustapi
3
- Version: 0.3.33
3
+ Version: 0.3.34
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: Programming Language :: Python :: 3
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "pyrustapi"
7
- version = "0.3.33"
7
+ version = "0.3.34"
8
8
  description = "A FastAPI-style Python web framework with a Rust tokio/hyper core, and a built-in MCP server."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -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 __init__(self, *args, **kwargs):
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.33"
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
- fn read(&self, py: Python<'_>) -> PyObject {
510
- PyBytes::new_bound(py, &self.file_data).into()
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