simple-module-hosting 0.0.14__tar.gz → 0.0.16__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.
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/PKG-INFO +3 -3
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/pyproject.toml +3 -3
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/_error_handlers.py +14 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/_observability.py +12 -4
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/_phase_helpers.py +3 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/logging.py +3 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_logging.py +31 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/.gitignore +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/LICENSE +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/README.md +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/__init__.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/__main__.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/_host_services.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/_hydrate_step.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/_inertia_setup.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/_inertia_shared.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/app_builder.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/bootstrap_settings.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/health.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/host_cli.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/host_settings.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/i18n_deps.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/i18n_manifest.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/i18n_middleware.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/inertia_deps.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/inertia_utils.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/manifest.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/middleware.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/migrations.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/permissions.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/py.typed +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/redirects.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/settings.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_app.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_check_migrations.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_health.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_host_cli.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_hosting_permissions.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_i18n_manifest.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_inertia_i18n_shared_props.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_lifespan_order.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_locale_middleware.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_manifest.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_middleware_order.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_redirects.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_session_cookie_security.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_settings_i18n.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_settings_secrets.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_strict_discovery_wiring.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_tenant_middleware.py +0 -0
- {simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_translator_dep.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simple_module_hosting
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.16
|
|
4
4
|
Summary: FastAPI + Inertia.js host runtime for simple_module — app_builder, middleware stack, CLI (sm / simple-module), scaffolding
|
|
5
5
|
Project-URL: Homepage, https://github.com/antosubash/simple_module_python
|
|
6
6
|
Project-URL: Repository, https://github.com/antosubash/simple_module_python
|
|
@@ -26,8 +26,8 @@ Requires-Dist: fastapi-inertia>=1.0
|
|
|
26
26
|
Requires-Dist: fastapi>=0.115
|
|
27
27
|
Requires-Dist: httpx>=0.27
|
|
28
28
|
Requires-Dist: jinja2>=3.1
|
|
29
|
-
Requires-Dist: simple-module-core==0.0.
|
|
30
|
-
Requires-Dist: simple-module-db==0.0.
|
|
29
|
+
Requires-Dist: simple-module-core==0.0.16
|
|
30
|
+
Requires-Dist: simple-module-db==0.0.16
|
|
31
31
|
Requires-Dist: starlette>=0.44
|
|
32
32
|
Requires-Dist: tomlkit>=0.13
|
|
33
33
|
Requires-Dist: uvicorn[standard]>=0.34
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "simple_module_hosting"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.16"
|
|
4
4
|
description = "FastAPI + Inertia.js host runtime for simple_module — app_builder, middleware stack, CLI (sm / simple-module), scaffolding"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -26,8 +26,8 @@ dependencies = [
|
|
|
26
26
|
"fastapi-inertia>=1.0",
|
|
27
27
|
"httpx>=0.27",
|
|
28
28
|
"jinja2>=3.1",
|
|
29
|
-
"simple_module_core==0.0.
|
|
30
|
-
"simple_module_db==0.0.
|
|
29
|
+
"simple_module_core==0.0.16",
|
|
30
|
+
"simple_module_db==0.0.16",
|
|
31
31
|
"starlette>=0.44",
|
|
32
32
|
"tomlkit>=0.13",
|
|
33
33
|
"uvicorn[standard]>=0.34",
|
|
@@ -4,6 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
|
|
7
|
+
from fastapi.encoders import jsonable_encoder
|
|
8
|
+
from fastapi.exceptions import RequestValidationError
|
|
7
9
|
from fastapi.responses import JSONResponse
|
|
8
10
|
from inertia import (
|
|
9
11
|
Inertia,
|
|
@@ -49,6 +51,18 @@ async def not_found_error_handler(request: Request, exc: NotFoundError) -> Respo
|
|
|
49
51
|
return await render_error_page(request, 404, str(exc))
|
|
50
52
|
|
|
51
53
|
|
|
54
|
+
async def request_validation_error_handler(
|
|
55
|
+
request: Request, exc: RequestValidationError
|
|
56
|
+
) -> Response:
|
|
57
|
+
"""Return an Inertia error page for browser requests with invalid params."""
|
|
58
|
+
accept = request.headers.get("accept", "")
|
|
59
|
+
if "text/html" in accept:
|
|
60
|
+
return await render_error_page(
|
|
61
|
+
request, 422, "The requested URL contains invalid parameters."
|
|
62
|
+
)
|
|
63
|
+
return JSONResponse(status_code=422, content={"detail": jsonable_encoder(exc.errors())})
|
|
64
|
+
|
|
65
|
+
|
|
52
66
|
async def unhandled_exception_handler(request: Request, exc: Exception) -> Response:
|
|
53
67
|
logger.exception("Unhandled exception: %s", exc)
|
|
54
68
|
return await render_error_page(request, 500, "")
|
|
@@ -26,10 +26,16 @@ _QUIET_PREFIXES = ("/health", "/static/")
|
|
|
26
26
|
class CorrelationIdMiddleware:
|
|
27
27
|
"""Generate or propagate a correlation ID for every request.
|
|
28
28
|
|
|
29
|
-
Reads the incoming ``X-Correlation-ID`` header (or generates a UUID4) and
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
Reads the incoming ``X-Correlation-ID`` header (or generates a UUID4) and:
|
|
30
|
+
|
|
31
|
+
* stores it in the ``simple_module_hosting.logging.correlation_id``
|
|
32
|
+
ContextVar so the stdlib logging filter (or a user-supplied structlog
|
|
33
|
+
processor — see ``docs/framework/middleware.md``) picks it up with no
|
|
34
|
+
per-handler plumbing;
|
|
35
|
+
* exposes it on ``request.state.correlation_id`` for handlers that
|
|
36
|
+
prefer the request object over the contextvar;
|
|
37
|
+
* echoes the value back as the ``X-Correlation-ID`` response header so
|
|
38
|
+
clients can cross-reference their request with server-side logs.
|
|
33
39
|
"""
|
|
34
40
|
|
|
35
41
|
HEADER = "X-Correlation-ID"
|
|
@@ -43,6 +49,8 @@ class CorrelationIdMiddleware:
|
|
|
43
49
|
return
|
|
44
50
|
|
|
45
51
|
cid = Headers(scope=scope).get(self.HEADER) or uuid.uuid4().hex
|
|
52
|
+
# Skip allocating a Request wrapper — downstream Request(scope).state reads this same dict.
|
|
53
|
+
scope.setdefault("state", {})["correlation_id"] = cid
|
|
46
54
|
|
|
47
55
|
async def send_with_header(message: Message) -> None:
|
|
48
56
|
if message["type"] == _MSG_RESPONSE_START:
|
|
@@ -12,6 +12,7 @@ from pathlib import Path
|
|
|
12
12
|
from typing import TYPE_CHECKING
|
|
13
13
|
|
|
14
14
|
from fastapi import APIRouter, FastAPI
|
|
15
|
+
from fastapi.exceptions import RequestValidationError
|
|
15
16
|
from fastapi.routing import APIRoute
|
|
16
17
|
from fastapi.staticfiles import StaticFiles
|
|
17
18
|
from inertia import (
|
|
@@ -26,6 +27,7 @@ from starlette.middleware.sessions import SessionMiddleware
|
|
|
26
27
|
from simple_module_hosting._error_handlers import (
|
|
27
28
|
http_exception_handler,
|
|
28
29
|
not_found_error_handler,
|
|
30
|
+
request_validation_error_handler,
|
|
29
31
|
unhandled_exception_handler,
|
|
30
32
|
)
|
|
31
33
|
from simple_module_hosting.i18n_middleware import LocaleMiddleware
|
|
@@ -53,6 +55,7 @@ def register_exception_handlers(app: FastAPI, modules: list) -> None:
|
|
|
53
55
|
)
|
|
54
56
|
app.add_exception_handler(HTTPException, http_exception_handler)
|
|
55
57
|
app.add_exception_handler(NotFoundError, not_found_error_handler)
|
|
58
|
+
app.add_exception_handler(RequestValidationError, request_validation_error_handler)
|
|
56
59
|
app.add_exception_handler(Exception, unhandled_exception_handler)
|
|
57
60
|
for mod in modules:
|
|
58
61
|
mod.register_exception_handlers(app)
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import logging
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
7
8
|
|
|
8
9
|
import httpx
|
|
9
10
|
from simple_module_hosting.logging import (
|
|
@@ -12,6 +13,13 @@ from simple_module_hosting.logging import (
|
|
|
12
13
|
correlation_id,
|
|
13
14
|
setup_logging,
|
|
14
15
|
)
|
|
16
|
+
from simple_module_hosting.middleware import CorrelationIdMiddleware
|
|
17
|
+
from starlette.applications import Starlette
|
|
18
|
+
from starlette.responses import JSONResponse
|
|
19
|
+
from starlette.routing import Route
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from starlette.requests import Request
|
|
15
23
|
|
|
16
24
|
# ── JsonFormatter ──────────────────────────────────────────────────────
|
|
17
25
|
|
|
@@ -174,6 +182,29 @@ class TestCorrelationIdMiddleware:
|
|
|
174
182
|
r2 = await client.get("/health")
|
|
175
183
|
assert r1.headers["x-correlation-id"] != r2.headers["x-correlation-id"]
|
|
176
184
|
|
|
185
|
+
async def test_state_contextvar_and_header_agree(self):
|
|
186
|
+
# Background tasks read the ContextVar; handlers read request.state;
|
|
187
|
+
# clients read the response header — all three must agree per request.
|
|
188
|
+
async def echo(request: Request) -> JSONResponse:
|
|
189
|
+
return JSONResponse(
|
|
190
|
+
{
|
|
191
|
+
"state": request.state.correlation_id,
|
|
192
|
+
"contextvar": correlation_id.get(""),
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
app = Starlette(routes=[Route("/echo", echo)])
|
|
197
|
+
app.add_middleware(CorrelationIdMiddleware)
|
|
198
|
+
|
|
199
|
+
transport = httpx.ASGITransport(app=app)
|
|
200
|
+
async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as c:
|
|
201
|
+
resp = await c.get("/echo", headers={CorrelationIdMiddleware.HEADER: "trace-xyz"})
|
|
202
|
+
|
|
203
|
+
assert resp.headers[CorrelationIdMiddleware.HEADER] == "trace-xyz"
|
|
204
|
+
body = resp.json()
|
|
205
|
+
assert body["state"] == "trace-xyz"
|
|
206
|
+
assert body["contextvar"] == "trace-xyz"
|
|
207
|
+
|
|
177
208
|
|
|
178
209
|
# ── Request logging middleware (integration) ────────────────────────────
|
|
179
210
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/__init__.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/_hydrate_step.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/app_builder.py
RENAMED
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/health.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/host_cli.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/host_settings.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/i18n_deps.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/i18n_manifest.py
RENAMED
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/inertia_deps.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/inertia_utils.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/manifest.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/middleware.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/migrations.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/permissions.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/py.typed
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/redirects.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/simple_module_hosting/settings.py
RENAMED
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_check_migrations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_hosting_permissions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_locale_middleware.py
RENAMED
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_middleware_order.py
RENAMED
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_session_cookie_security.py
RENAMED
|
File without changes
|
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_settings_secrets.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_strict_discovery_wiring.py
RENAMED
|
File without changes
|
{simple_module_hosting-0.0.14 → simple_module_hosting-0.0.16}/tests/test_tenant_middleware.py
RENAMED
|
File without changes
|
|
File without changes
|