Skip to content

asgi

The AsgiHandler lets Zapros talk directly to ASGI apps such as FastAPI, Litestar, and Starlette without making real network requests.

Basic Usage

python
from litestar import Litestar, get
from zapros import AsyncClient
from zapros import AsgiHandler


@get("/hello")
async def hello() -> dict:
    return {"message": "Hello, World!"}


app = Litestar(route_handlers=[hello])
handler = AsgiHandler(app)

async with AsyncClient(handler=handler) as client:
     response = await client.get("http://testserver/hello")
     print(response.json)

Configuration Options

The AsgiHandler accepts several configuration parameters:

python
handler = AsgiHandler(
    app,
    client=(
        "127.0.0.1",
        123,
    ),  # Client address tuple (host, port)
    root_path="",  # ASGI root_path for the application
    http_version="1.1",  # HTTP protocol version
    enable_lifespan=True,  # Enable ASGI lifespan protocol
    startup_timeout=10.0,  # Timeout for lifespan startup (seconds)
    shutdown_timeout=10.0,  # Timeout for lifespan shutdown (seconds)
)

Timeouts

When an ASGI application's lifespan startup or shutdown takes too long, Zapros raises timeout exceptions:

  • AsgiLifespanStartupTimeoutError — raised if the application does not complete startup within startup_timeout seconds
  • AsgiLifespanShutdownTimeoutError — raised if the application does not complete shutdown within shutdown_timeout seconds

Both default to 10 seconds. Set to None for no timeout, or adjust for applications with longer initialization:

python
from zapros import AsgiHandler

handler = AsgiHandler(
    app,
    startup_timeout=30.0,
    shutdown_timeout=5.0,
)

Trio Support

The AsgiHandler fully supports running under Trio. To respect Trio's philosophy that background tasks should only run within a nursery, you must use the handler as an async context manager when running in a Trio context:

python
import trio
from litestar import Litestar, get
from zapros import AsyncClient
from zapros import AsgiHandler


@get("/hello")
async def hello() -> dict:
    return {"message": "Hello, World!"}


app = Litestar(route_handlers=[hello])


async def main():
    async with AsgiHandler(app) as handler:
        async with AsyncClient(handler=handler) as client:
            response = await client.get("http://testserver/hello")
            print(response.json)


trio.run(main)

Important: If you attempt to use AsgiHandler in a Trio run without the async context manager, a RuntimeError will be raised:

RuntimeError: When using `AsgiHandler` with Trio, you must use it as an async context manager (i.e. `async with AsgiHandler(...) as handler:`)

When using the handler as a context manager with Trio, lifespan management is handled automatically—the nursery ensures proper startup and shutdown sequencing.

Lifespan Management

Zapros implements the ASGI lifespan protocol, allowing ASGI applications to implement startup and shutdown logic. The AsgiHandler supports this:

python
from litestar import Litestar, get
from litestar.datastructures import (
    State,
)


@get("/state")
async def get_state(
    state: State,
) -> dict:
    return {"value": state.get("initialized", False)}


async def on_startup(
    app: Litestar,
) -> None:
    app.state.initialized = True


app = Litestar(
    route_handlers=[get_state],
    on_startup=[on_startup],
)

handler = AsgiHandler(app, enable_lifespan=True)
try:
    async with AsyncClient(handler=handler) as client:
         response = await client.get(
             "http://testserver/state",
         )
         print(response.json)
finally:
    await handler.aclose()