Skip to content

Structure a FastAPI app

One module owns the dependency vocabulary; routers consume aliases; tests override factories. Nothing else needs to know how anything is built.

The dependencies module

myapp/dependencies.py
from typing import Annotated

from wireme import Wired, wired

from myapp.database import Connection, Settings
from myapp.services import UserService

settings = Settings()  # validates the environment at import time


def get_settings() -> Settings:
    return settings


type SettingsDep = Annotated[Settings, wired(get_settings)]


def get_connection(*, config: SettingsDep = Wired()):
    connection = Connection(config.database_url)
    try:
        yield connection
    finally:
        connection.close()


type ConnectionDep = Annotated[Connection, wired(get_connection)]


def get_user_service(*, connection: ConnectionDep = Wired()) -> UserService:
    return UserService(connection=connection)


type UserServiceDep = Annotated[UserService, wired(get_user_service)]

The router

Routers import aliases, not factories. FromWeb bridges each alias while FastAPI keeps the request lifecycle: the connection opens on first use, survives streaming, and closes after the response.

myapp/routers/users.py
from fastapi import APIRouter

from wireme.fastapi import FromWeb

from myapp.dependencies import UserServiceDep

router = APIRouter()


@router.get("/users")
def list_users(service: FromWeb[UserServiceDep]) -> list[str]:
    return service.list_users()

The tests

tests/test_users.py
from wireme.fastapi import override_web_dependency

from myapp.dependencies import get_user_service


def test_users_endpoint(app, client):
    def get_test_service() -> UserService:
        return UserService(connection=FakeConnection())

    with override_web_dependency(app, get_user_service, get_test_service):
        assert client.get("/users").json() == []

Replacements may be sync, async, generator, or async-generator factories with different parameter lists. Register routes before entering the override context so all bridges are known.

Swap one layer, keep the rest

Overriding get_connection instead of get_user_service keeps the real service logic in play against a fake connection: choose the override depth per test.