pyrustapi 0.3.34__tar.gz → 0.3.86__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/Cargo.lock +1 -1
  2. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/Cargo.toml +1 -1
  3. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/PKG-INFO +1 -1
  4. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/fastapi_compatibility_and_features.md +68 -0
  5. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/pyproject.toml +1 -1
  6. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/__init__.py +1 -1
  7. pyrustapi-0.3.86/tests/test_python_ecosystem_compatibility.py +114 -0
  8. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/.github/workflows/CI.yml +0 -0
  9. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/.github/workflows/pages.yml +0 -0
  10. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/.github/workflows/release.yml +0 -0
  11. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/.github/workflows/static.yml +0 -0
  12. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/.gitignore +0 -0
  13. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/LICENSE +0 -0
  14. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/README.md +0 -0
  15. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/ROADMAP.md +0 -0
  16. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmark_dashboard.html +0 -0
  17. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/benchmark.sh +0 -0
  18. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/data/fastapi_app.py +0 -0
  19. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/data/fastapi_cpu_app.py +0 -0
  20. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/data/fastapi_edge_app.py +0 -0
  21. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/data/fastapi_oha.py +0 -0
  22. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/data/large.json +0 -0
  23. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/data/mixed.json +0 -0
  24. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/data/rustapi_app.py +0 -0
  25. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/data/rustapi_cpu_app.py +0 -0
  26. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/data/rustapi_edge_app.py +0 -0
  27. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/data/rustapi_multiworker_app.py +0 -0
  28. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/data/rustapi_oha.py +0 -0
  29. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/data/rustapi_tier3_app.py +0 -0
  30. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/data/tier_app.py +0 -0
  31. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/data/validate.json +0 -0
  32. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/run_3tier_benchmark.py +0 -0
  33. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/run_comparison_benchmark.py +0 -0
  34. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/run_cpu_stability_benchmark.py +0 -0
  35. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/run_edgecase_benchmark.py +0 -0
  36. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/run_native_tier3_benchmark.py +0 -0
  37. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/run_real_oha_benchmark.py +0 -0
  38. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks/run_release_highspeed_benchmark.py +0 -0
  39. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/benchmarks.html +0 -0
  40. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/crud_benchmark/benchmark_db.sqlite +0 -0
  41. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/crud_benchmark/benchmark_db.sqlite-shm +0 -0
  42. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/crud_benchmark/benchmark_db.sqlite-wal +0 -0
  43. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/crud_benchmark/benchmark_results.md +0 -0
  44. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/crud_benchmark/fastapi_crud.py +0 -0
  45. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/crud_benchmark/run_crud_benchmark.py +0 -0
  46. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/crud_benchmark/rustapi_crud.py +0 -0
  47. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/3tier_architecture_and_benchmarks.md +0 -0
  48. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/DeepAnalytics.md +0 -0
  49. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/api-reference.html +0 -0
  50. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/benchmark_dashboard.html +0 -0
  51. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/benchmarks.html +0 -0
  52. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/database.html +0 -0
  53. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/database_engine.md +0 -0
  54. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/docs.md +0 -0
  55. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/getting-started.html +0 -0
  56. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/getting_started.md +0 -0
  57. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/index.html +0 -0
  58. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/mcp-server.html +0 -0
  59. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/mcp_server.md +0 -0
  60. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/native_business_logic.md +0 -0
  61. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/performance_and_benchmarks.md +0 -0
  62. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/power_primitives.md +0 -0
  63. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/pyrustapi-logo (1).png +0 -0
  64. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/report_01_.md +0 -0
  65. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/response_types.md +0 -0
  66. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/security-power-modules.html +0 -0
  67. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/docs/swagger_ui_and_dx_deviations.md +0 -0
  68. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/examples/app.py +0 -0
  69. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/index.html +0 -0
  70. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/performance-reports/cpu_intensive_stability_report.md +0 -0
  71. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/performance-reports/fastapi_vs_hybrid_vs_native.md +0 -0
  72. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/performance-reports/fastapi_vs_rustapi_comparison.md +0 -0
  73. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/performance-reports/fastapi_vs_rustapi_crud_benchmark.md +0 -0
  74. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/performance-reports/performance_and_benchmarks.md +0 -0
  75. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/performance-reports/rust_native_edgecases_report.md +0 -0
  76. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/performance-reports/three_tier_architecture_evaluation.md +0 -0
  77. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/pyrustapi-logo.png +0 -0
  78. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/pyrustapi/__init__.py +0 -0
  79. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/__init__.pyi +0 -0
  80. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/background.py +0 -0
  81. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/depends.py +0 -0
  82. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/encoders.py +0 -0
  83. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/exceptions.py +0 -0
  84. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/middleware/__init__.py +0 -0
  85. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/middleware/cors.py +0 -0
  86. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/openapi/__init__.py +0 -0
  87. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/openapi/docs.py +0 -0
  88. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/param_functions.py +0 -0
  89. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/py.typed +0 -0
  90. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/resolver.py +0 -0
  91. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/responses/__init__.py +0 -0
  92. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/router.py +0 -0
  93. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/rust.rs +0 -0
  94. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/security/__init__.py +0 -0
  95. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/sse.py +0 -0
  96. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/staticfiles.py +0 -0
  97. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/status.py +0 -0
  98. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/python/rustapi/uploads.py +0 -0
  99. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/scratch/.gitkeep +0 -0
  100. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/src/lib.rs +0 -0
  101. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/test-reports/junit.xml +0 -0
  102. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/tests/test_3tier_architecture.py +0 -0
  103. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/tests/test_basic.py +0 -0
  104. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/tests/test_database_engine.py +0 -0
  105. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/tests/test_fastapi_all_6_mismatches.py +0 -0
  106. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/tests/test_fastapi_compatibility.py +0 -0
  107. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/tests/test_file_uploads_and_dx.py +0 -0
  108. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/tests/test_imports.py +0 -0
  109. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/tests/test_native_routes.py +0 -0
  110. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/tests/test_overrides_and_coercion.py +0 -0
  111. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/tests/test_reload_and_logging.py +0 -0
  112. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/tests/test_response_model.py +0 -0
  113. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/tests/test_responses_and_req_json.py +0 -0
  114. {pyrustapi-0.3.34 → pyrustapi-0.3.86}/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.34"
1430
+ version = "0.3.86"
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.34"
3
+ version = "0.3.86"
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.34
3
+ Version: 0.3.86
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: Programming Language :: Python :: 3
@@ -168,4 +168,72 @@ def get_item(
168
168
  # Convert dataclass/Pydantic model to JSON dict
169
169
  user_dict = jsonable_encoder(user)
170
170
  return {"item_id": item_id, "query": q, "user": user_dict}
171
+
172
+
173
+ ---
174
+
175
+ ## 7. Python Ecosystem Compatibility Matrix
176
+
177
+ `rustapi` is built to seamlessly integrate with standard Python database, machine learning, security, and utility libraries:
178
+
179
+ | Library Domain | Package | Verified Feature & Usage | Status |
180
+ | :--- | :--- | :--- | :--- |
181
+ | **Data Validation** | `pydantic` v2 | `BaseModel`, `Field`, `validator`, `EmailStr` in route bodies | ✅ Verified |
182
+ | **HTTP Clients** | `httpx`, `requests` | `httpx.AsyncClient` & `requests` testing, multi-part uploads & streams | ✅ Verified |
183
+ | **Databases & ORMs** | `sqlite3`, `sqlalchemy` | Session context managers & query execution inside `Depends(get_db)` | ✅ Verified |
184
+ | **Image Processing** | `PIL` / `Pillow` | Image decoding & matrix transformations on `UploadFile` streams | ✅ Verified |
185
+ | **JWT & Security** | `pyjwt`, `passlib`, `argon2` | `jwt.encode`, `jwt.decode`, password hashing inside auth endpoints | ✅ Verified |
186
+ | **Numeric & ML** | `numpy` | `np.array` operations on request payloads & image byte arrays | ✅ Verified |
187
+
188
+ ```python
189
+ import io
190
+ import jwt
191
+ import numpy as np
192
+ from PIL import Image
193
+ from pydantic import BaseModel, Field
194
+ from rustapi import FastAPI, Request, Depends, HTTPException
195
+ from rustapi.uploads import UploadFile
196
+
197
+ app = FastAPI()
198
+
199
+ class AnalysisModel(BaseModel):
200
+ project_name: str = Field(..., min_length=2)
201
+
202
+ @app.post("/analyze")
203
+ async def analyze(req: Request):
204
+ if "photo" not in req.files:
205
+ raise HTTPException(status_code=400, detail="No photo uploaded")
206
+
207
+ file_obj: UploadFile = req.files["photo"][0]
208
+ img_bytes = await file_obj.read() # Both await file.read() and file.read() work
209
+
210
+ # Process image with Pillow & NumPy
211
+ image = Image.open(io.BytesIO(img_bytes)).convert("L")
212
+ array = np.array(image)
213
+ brightness = float(np.mean(array))
214
+
215
+ # Encode JWT response
216
+ token = jwt.encode({"brightness": brightness}, "secret", algorithm="HS256")
217
+ return {"status": "success", "token": token}
218
+ ```
219
+
220
+ ---
221
+
222
+ ## 8. FastAPI Migration Mismatch Resolution (v0.3.34)
223
+
224
+ The following 6 FastAPI compatibility enhancements have been resolved natively in `pyrustapi` v0.3.34:
225
+
226
+ 1. **Sub-Router Nesting (`APIRouter.include_router`)**:
227
+ `router.include_router(sub_router, prefix="/v1", tags=["sub"])` allows sub-routers to mount additional sub-routers with inherited path prefixes and tags.
228
+ 2. **`FastAPI` Constructor Kwargs**:
229
+ `FastAPI(title="My API", description="...", version="1.0.0", openapi_url="/openapi.json", docs_url="/docs", redoc_url="/redoc")` accepts all standard metadata keyword arguments without constructor errors.
230
+ 3. **Custom Exception Handlers (`@app.exception_handler`)**:
231
+ Register custom status code or exception class handlers via `@app.exception_handler(CustomException)`.
232
+ 4. **`HTTPBearer()` Credentials Container**:
233
+ `security = HTTPBearer()` returns `HTTPAuthorizationCredentials(scheme="Bearer", credentials="...")` when passed to `Depends(security)`.
234
+ 5. **Recursive Dependency Injection (`solve_dependency`)**:
235
+ Injects `request: Request` / `req` parameters automatically and recursively resolves nested `Depends(sub_dep)` calls.
236
+ 6. **Dual Async / Sync `UploadFile` Methods**:
237
+ `UploadFile` methods (`file.read()`, `file.seek()`, `file.close()`) support both asynchronous (`await file.read()`) and synchronous (`file.read()`) execution, plus `.file` returns `io.BytesIO`.
238
+
171
239
  ```
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "pyrustapi"
7
- version = "0.3.34"
7
+ version = "0.3.86"
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"
@@ -152,7 +152,7 @@ class RedirectResponse(Response):
152
152
  return Response.__new__(cls, content="", status_code=status_code, headers=h)
153
153
 
154
154
 
155
- __version__ = "0.3.34"
155
+ __version__ = "0.3.86"
156
156
  __all__ = [
157
157
  "Engine",
158
158
  "FastAPI",
@@ -0,0 +1,114 @@
1
+ import io
2
+ import sqlite3
3
+ import threading
4
+ import time
5
+ import requests
6
+ import jwt
7
+ import httpx
8
+ import numpy as np
9
+ from PIL import Image
10
+ from pydantic import BaseModel, Field
11
+ from rustapi import FastAPI, Request, Depends, HTTPException, status
12
+ from rustapi.uploads import UploadFile
13
+
14
+
15
+ # 1. Pydantic Model
16
+ class ImageAnalysisRequest(BaseModel):
17
+ project_name: str = Field(..., min_length=2)
18
+ threshold: float = Field(0.5, ge=0.0, le=1.0)
19
+
20
+
21
+ # 2. Database Setup (SQLite stdlib)
22
+ def init_db():
23
+ conn = sqlite3.connect(":memory:", check_same_thread=False)
24
+ conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT, token TEXT)")
25
+ conn.execute("INSERT INTO users (username, token) VALUES ('alice', 'secret_jwt_123')")
26
+ conn.commit()
27
+ return conn
28
+
29
+ db_conn = init_db()
30
+
31
+ # DB Dependency
32
+ def get_db():
33
+ return db_conn
34
+
35
+
36
+ # 3. Application
37
+ app = FastAPI(title="Ecosystem Compatibility Test App")
38
+
39
+
40
+ @app.post("/analyze-image")
41
+ async def analyze_image(req: Request, db=Depends(get_db)):
42
+ # A. Parse form fields or JSON body with Pydantic
43
+ try:
44
+ json_body = await req.json()
45
+ except Exception:
46
+ json_body = {}
47
+
48
+ project_name = req.form.get("project_name") or json_body.get("project_name") or "default_project"
49
+ threshold = float(req.form.get("threshold") or json_body.get("threshold") or 0.5)
50
+ model = ImageAnalysisRequest(project_name=project_name, threshold=threshold)
51
+
52
+ # B. Query SQLite database
53
+ cursor = db.cursor()
54
+ cursor.execute("SELECT username FROM users WHERE token = 'secret_jwt_123'")
55
+ row = cursor.fetchone()
56
+ user = row[0] if row else "unknown"
57
+
58
+ # C. Read uploaded file & process image with PIL (Pillow) & NumPy
59
+ if "photo" not in req.files or not req.files["photo"]:
60
+ raise HTTPException(status_code=400, detail="No photo uploaded")
61
+
62
+ photo_file: UploadFile = req.files["photo"][0]
63
+ img_bytes = await photo_file.read()
64
+
65
+ image = Image.open(io.BytesIO(img_bytes)).convert("L")
66
+ img_array = np.array(image)
67
+
68
+ # Perform NumPy calculation (mean brightness)
69
+ mean_brightness = float(np.mean(img_array))
70
+
71
+ # D. JWT Encoding
72
+ payload = {"sub": user, "project": model.project_name, "brightness": mean_brightness}
73
+ encoded_token = jwt.encode(payload, "secret_key_999", algorithm="HS256")
74
+
75
+ return {
76
+ "status": "success",
77
+ "user": user,
78
+ "project": model.project_name,
79
+ "mean_brightness": mean_brightness,
80
+ "jwt_token": encoded_token,
81
+ }
82
+
83
+
84
+ def test_python_ecosystem_compatibility():
85
+ port = 8996
86
+ thread = threading.Thread(target=lambda: app.run(host="127.0.0.1", port=port), daemon=True)
87
+ thread.start()
88
+ time.sleep(0.4)
89
+
90
+ # Generate sample 100x100 grayscale image in memory using PIL
91
+ img = Image.new("L", (100, 100), color=128)
92
+ img_byte_arr = io.BytesIO()
93
+ img.save(img_byte_arr, format="PNG")
94
+ png_bytes = img_byte_arr.getvalue()
95
+
96
+ # Call endpoint using HTTPX
97
+ with httpx.Client(base_url=f"http://127.0.0.1:{port}") as client:
98
+ files = {"photo": ("test_image.png", png_bytes, "image/png")}
99
+ data = {"project_name": "SkinAnalysis", "threshold": "0.75"}
100
+
101
+ response = client.post("/analyze-image", data=data, files=files)
102
+ assert response.status_code == 200
103
+
104
+ res_json = response.json()
105
+ assert res_json["status"] == "success"
106
+ assert res_json["user"] == "alice"
107
+ assert res_json["project"] == "SkinAnalysis"
108
+ assert res_json["mean_brightness"] == 128.0
109
+
110
+ # Decode JWT token to verify PyJWT compatibility
111
+ decoded = jwt.decode(res_json["jwt_token"], "secret_key_999", algorithms=["HS256"])
112
+ assert decoded["sub"] == "alice"
113
+ assert decoded["project"] == "SkinAnalysis"
114
+ assert decoded["brightness"] == 128.0
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