Skip to content

Lifespan and request.state question #33

Description

@serhiiur

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions