Injecting dependencies

Open long-lived clients once (DB pool, HTTP client, cache). Pass them into handlers. Short-lived work (sessions, cursors) stays inside the handler.

Stario does not ship a DI framework. bootstrap is the composition root: startup before yield, teardown after. Chat room uses this pattern with db and Relay. See Chat room.

Handler factories

A function named like the route closes over dependencies and returns the inner handler:

python
import stario.responses as responses
from stario import App, Context, Route, Span, UrlPath, Writer
 
USERS_PATH = UrlPath("/users")
USERS = Route.get(USERS_PATH)
USER = Route.get(USERS_PATH / "{id}")
 
 
def list_users(db):
    async def handler(c: Context, w: Writer) -> None:
        rows = await db.list_users()
        responses.json(w, {"users": rows})
 
    return handler
 
 
def get_user(db):
    async def handler(c: Context, w: Writer) -> None:
        row = await db.fetch_user_by_id(c.route.params["id"])
        if row is None:
            responses.text(w, "Not found", status=404)
            return
        responses.json(w, row)
 
    return handler
 
 
async def bootstrap(app: App, span: Span):
    # Database is your pool or engine type.
    with Database() as db:
        app.add(USERS, list_users(db))
        app.add(USER, get_user(db))
        yield

Each handler shares the same db instance. No global registry.

Other shapes

A class that takes dependencies in __init__ and exposes methods as handlers works the same way. In bootstrap, instantiate once and pass bound methods to app.add.

When you have more than one long-lived client, group them in a small frozen dataclass (Services) and pass one object into the factories.

If every client is a context manager, contextlib.AsyncExitStack avoids a deep with chain. Enter sync clients with stack.enter_context, async clients with await stack.enter_async_context, then yield. The stack unwinds in reverse enter order.

For larger apps, expose register_users(app, db) that registers paths from a feature urls.py. See Structuring apps.

Dependencies vs state

Dependencies live for the process. Handlers reach them through closures, Services, or attributes on a long-lived instance.

c.state is per request. Middleware puts request-scoped data there (who is authenticated, parsed tokens). Shared database access still comes from objects you created in bootstrap.

Runtime. Structuring apps.