Skip to content

3.1.0

Choose a tag to compare

@github-actions github-actions released this 27 Jul 19:46
3.1.0
a9cc8fc

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 a finally so it also runs when the application raises. The
    container was already bounded by the async with around 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 —
    every Container storing 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-di floor 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, ty clean.
  • architecture/container-lifecycle.md records that deleting the scope entry is
    load-bearing, so it is not removed later as tidy-up.