3.1.0
modern-di-starlette 3.1.0 — the request container stops leaking into the GC
Every request used to leave a reference cycle behind. The middleware put the
per-request child container into the ASGI scope dict and left it there; the
container's context holds the connection, and the connection owns that same
scope dict. So the container — and the connection and every dependency
resolved during the request — could only be reclaimed by the garbage collector,
never by reference counting.
No public API change. FromDI, fetch_di_container, inject, setup_di,
starlette_request_provider and starlette_websocket_provider keep their
signatures and behavior.
Fix
-
The request container is removed from the ASGI scope when the request
ends, in afinallyso it also runs when the application raises. The
container was already bounded by theasync witharound it, so nothing may
read the entry after that block — the entry outliving the request was the bug.Measured over 200 requests with the collector disabled, on a minimal app:
modern-di this integration cyclic objects per request 3.0.0 3.0.0 34 3.1.1 3.0.0 34 3.0.0 3.1.0 34 3.1.1 3.1.0 0 Bare Starlette with no DI produces 0, so all 34 were ours.
There were two independent cycles and both had to go. The other one —
everyContainerstoring itself in its own_scope_map— was fixed upstream
in modern-di 3.1.1.
Neither fix alone moves the number, which is why this release requires it.
Packaging
modern-difloor raised to>=3.1.1,<4(from>=3,<4). Required, not
cosmetic: this release's regression test fails against modern-di 3.0.0, which
the old range permitted, so leaving the floor alone would claim support for a
version the suite does not pass on.
Feature
- A canonical
examples/app.py— a minimal runnable Starlette app wiring
modern-di end to end, covered by a test so it cannot rot.
Downstream
Upgrading pulls modern-di 3.1.1 or newer. No code change needed on your side;
no API of this package moved.
If you read scope["modern_di_container"] directly rather than through
FromDI / fetch_di_container, note that the entry is now gone once the
request finishes. Reading it during the request is unchanged.
Internals
- 10 tests, 100% line coverage, Python 3.10–3.14;
ruff,tyclean. architecture/container-lifecycle.mdrecords that deleting the scope entry is
load-bearing, so it is not removed later as tidy-up.