Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions modern_di_fastapi/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ def fetch_di_container(app_: fastapi.FastAPI) -> Container:

@contextlib.asynccontextmanager
async def _lifespan_manager(app_: fastapi.FastAPI) -> typing.AsyncIterator[None]:
container = fetch_di_container(app_)
try:
# ``async with`` reopens the root container on each startup (``__aenter__``)
# and closes it on shutdown, so a second lifespan cycle against the same
# container works instead of raising ContainerClosedError.
async with fetch_di_container(app_):
yield
finally:
await container.close_async()


def setup_di(app: fastapi.FastAPI, container: Container) -> Container:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ classifiers = [
"Typing :: Typed",
"Topic :: Software Development :: Libraries",
]
dependencies = ["fastapi>=0.100,<1", "modern-di>=2.16.1,<3"]
dependencies = ["fastapi>=0.100,<1", "modern-di>=2.19.0,<3"]
version = "0"

[project.urls]
Expand Down
25 changes: 25 additions & 0 deletions tests/test_lifespan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import typing

import fastapi
from starlette import status
from starlette.testclient import TestClient

from modern_di_fastapi import FromDI, fetch_di_container
from tests.dependencies import Dependencies, SimpleCreator


def test_lifespan_reopens_container_across_cycles(app: fastapi.FastAPI) -> None:
@app.get("/")
async def read_root(instance: typing.Annotated[SimpleCreator, FromDI(Dependencies.app_factory)]) -> None:
assert isinstance(instance, SimpleCreator)

container = fetch_di_container(app)

# First lifespan cycle: shutdown closes the root container.
with TestClient(app=app) as client:
assert client.get("/").status_code == status.HTTP_200_OK
assert container.closed

# Second cycle must reopen the same container instead of raising ContainerClosedError.
with TestClient(app=app) as client:
assert client.get("/").status_code == status.HTTP_200_OK
Loading