-
-
Notifications
You must be signed in to change notification settings - Fork 139
Description
Hi @Kludex. Big thanks for such a comprehensive resource on using FastAPI. I've got a question ragarding to the 5th and 6th tips:
is there a way to get access to request.state objects without calling asgi-lifespan's LifespanManager during testing? Here's a minimal example:
api.py:
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from httpx import AsyncClient
@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncIterator[dict[str, AsyncClient]]:
async with AsyncClient() as client:
yield {"client": client}
app = FastAPI(lifespan=lifespan)
@app.get("/test")
async def test(request: Request) -> dict[str, bool]:
client = request.state.client
return {"result": isinstance(client, AsyncClient)}test.py
from collections.abc import AsyncIterator # noqa: I001
import pytest
# from asgi_lifespan import LifespanManager
from httpx import ASGITransport, AsyncClient
from api import app
@pytest.fixture(scope="session")
def anyio_backend() -> str:
return "asyncio"
@pytest.fixture(scope="session")
async def client() -> AsyncIterator[AsyncClient]:
# async with LifespanManager(app) as manager:
transport = ASGITransport(app)
async with AsyncClient(base_url="http://test", transport=transport) as ac:
yield ac
@pytest.mark.anyio
async def test(client: AsyncClient) -> None:
with pytest.raises(AttributeError) as e:
resp = await client.get("/test")
assert "'State' object has no attribute 'client'" in str(e.value)As a result, the test passes, indicating that the State object has no attribute client, though we declared it in the lifespan. However, if we uncomment LifespanManager and replace app by manager.app when defining the transport object, the test will fail (as expected), because this time the client object is in the application's state.
It's worth mentioning that this problem doesn't appear when we declare state's objects in the old way like app.state.client = client in the lifespan and it doesn't require using asgi-lifespan's LifespanManager.