diff --git a/docs/adr/0001-pure-asgi-middleware.md b/docs/adr/0001-pure-asgi-middleware.md index 8e1d5ba..900b13f 100644 --- a/docs/adr/0001-pure-asgi-middleware.md +++ b/docs/adr/0001-pure-asgi-middleware.md @@ -1,10 +1,11 @@ # The container is opened in pure ASGI middleware, not `BaseHTTPMiddleware` -**Decision:** `_DIMiddleware` is a plain ASGI callable, not a `BaseHTTPMiddleware` subclass. +`_DIMiddleware` is a plain ASGI callable rather than a `BaseHTTPMiddleware` subclass. `BaseHTTPMiddleware` is the ergonomic choice, but it runs the downstream app in a separate anyio -task, so `contextvars` set around `call_next` are not visible in the endpoint, and it handles `http` -only, so a `Scope.SESSION` child for a WebSocket would need a second mechanism. The plain callable -costs about fifteen lines of raw-protocol handling and in return has the ASGI `scope` dict in hand, -which is how the child container reaches `@inject` without a `contextvar`. **Revisit trigger:** -Starlette ships a supported middleware base that shares context with the downstream app *and* -covers `websocket` scopes; either half alone leaves this integration writing the raw protocol. +task, so `contextvars` set around `call_next` are not visible in the endpoint, and it handles +`http` scopes only, so the `Scope.SESSION` child a WebSocket needs would take a second mechanism. +The plain callable costs about fifteen lines of raw-protocol handling and in return holds the ASGI +`scope` dict, which is how the child container reaches `@inject` without a `contextvar`. That trade +only flips if Starlette ships a supported middleware base that both shares context with the +downstream app and covers `websocket` scopes; either half alone leaves this integration writing the +raw protocol anyway. diff --git a/docs/adr/0002-child-container-stays-internal.md b/docs/adr/0002-child-container-stays-internal.md index c77d055..c0914cf 100644 --- a/docs/adr/0002-child-container-stays-internal.md +++ b/docs/adr/0002-child-container-stays-internal.md @@ -1,13 +1,12 @@ # The per-connection child container has no public accessor -**Decision:** the child container is reachable only through `@inject` + `FromDI`; the ASGI scope -key it lives under stays private and there is no `fetch_di_child_container(connection)`. The -middleware deletes its scope entry in a `finally` when the connection ends, because the container's -context holds the connection, the connection owns the `scope` dict, and the dict held the container: -a cycle per request that left finished requests to the garbage collector. An accessor advertises the -entry as something a caller may hold, and any holder that outlives the connection either revives the -cycle or reads a deleted entry, so there is no version of it that is both safe and useful. Class-based -`HTTPEndpoint` / `WebSocketEndpoint` were once the gap this left; `@inject` now binds as a method -(modern-python/modern-di-starlette#28), so it was a missing decorator path, not a missing accessor. -**Revisit trigger:** a use for the child container appears outside the connection's own call stack; -the lifetime question above has to be answered first, and the answer is the accessor's contract. +The child container is reachable only through `@inject` and `FromDI`: the ASGI scope key it lives +under stays private, and there is no `fetch_di_child_container(connection)` beside the root +container's `fetch_di_container(app)`. The middleware deletes its scope entry in a `finally` when +the connection ends, because the container's context holds the connection, the connection owns the +`scope` dict, and the dict held the container: a cycle per request that left finished requests to +the garbage collector. An accessor advertises that entry as something a caller may hold, and a +holder that outlives the connection either revives the cycle or reads a deleted entry, so no +version of it is both safe and useful. Class-based `HTTPEndpoint` and `WebSocketEndpoint` were once +the gap this left, but `@inject` now binds as a method, so that was a missing decorator path rather +than a missing accessor.