FastAPI routes wired to a dependency-injector DeclarativeContainer, tested offline: working versions (tested 2026-10-16)
Build a DeclarativeContainer with Configuration, Singleton and Factory providers, wire it into FastAPI routes with @inject and Depends(Provide[...]), and test it in-process with httpx.ASGITransport.
passed5 passed · 0 failed2026-10-16 00:01 UTC- Status
- passed
- Last passed
- 2026-10-16 00:01 UTC
- Tests
- 5 / 5
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38826982604
The code
Exactly the files that passed. Your agent can pull them with get_recipe("fastapi-dependency-injector-wiring") (install), or see them on GitHub.
fastapi==0.143.2 dependency-injector==4.49.1 httpx==0.28.1 pytest==9.1.1 pytest-asyncio==1.4.0
from __future__ import annotations
from dependency_injector import containers, providers
class TodoStore:
def __init__(self) -> None:
self._items: dict[int, str] = {}
def add(self, title: str) -> int:
todo_id = len(self._items) + 1
self._items[todo_id] = title
return todo_id
def items(self) -> dict[int, str]:
return dict(self._items)
class TodoService:
def __init__(self, store: TodoStore, limit: int) -> None:
self.store = store
self.limit = limit
def create(self, title: str) -> int:
if len(self.store.items()) >= self.limit:
raise ValueError("todo limit reached")
return self.store.add(title)
def listing(self) -> list[dict[str, int | str]]:
return [{"id": i, "title": t} for i, t in sorted(self.store.items().items())]
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
store = providers.Singleton(TodoStore)
service = providers.Factory(
TodoService,
store=store,
limit=config.limit.as_int(),
)
from __future__ import annotations
from dependency_injector.wiring import Provide, inject
from fastapi import Depends, FastAPI, HTTPException
from pydantic import BaseModel
from container import Container, TodoService
container = Container()
container.config.from_dict({"limit": 2})
app = FastAPI()
class TodoIn(BaseModel):
title: str
class TodoOut(BaseModel):
id: int
@app.post("/todos", response_model=TodoOut)
@inject
def create_todo(
payload: TodoIn,
service: TodoService = Depends(Provide[Container.service]),
) -> TodoOut:
try:
return TodoOut(id=service.create(payload.title))
except ValueError as exc:
raise HTTPException(status_code=409, detail=str(exc)) from exc
@app.get("/todos")
@inject
def list_todos(
service: TodoService = Depends(Provide[Container.service]),
) -> list[dict[str, int | str]]:
return service.listing()
container.wire(modules=[__name__])
from __future__ import annotations
import httpx
import pytest
from dependency_injector import providers
from app import app, container
from container import TodoService
@pytest.fixture(autouse=True)
def reset_container():
container.config.reset_override()
container.config.from_dict({"limit": 2})
yield
container.config.reset_override()
container.config.from_dict({"limit": 2})
container.store.reset_override()
container.service.reset_override()
container.reset_singletons()
def make_client() -> httpx.AsyncClient:
return httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://test")
async def test_create_and_list_shares_singleton_store():
async with make_client() as client:
assert (await client.post("/todos", json={"title": "a"})).json() == {"id": 1}
assert (await client.post("/todos", json={"title": "b"})).json() == {"id": 2}
r = await client.get("/todos")
assert r.json() == [{"id": 1, "title": "a"}, {"id": 2, "title": "b"}]
async def test_config_limit_returns_409():
container.config.limit.override(1)
async with make_client() as client:
assert (await client.post("/todos", json={"title": "a"})).status_code == 200
r = await client.post("/todos", json={"title": "b"})
assert r.status_code == 409
assert r.json()["detail"] == "todo limit reached"
async def test_override_store_propagates_to_service():
class FakeStore:
def items(self):
return {7: "seeded"}
def add(self, title):
return 8
container.store.override(providers.Singleton(FakeStore))
async with make_client() as client:
r = await client.get("/todos")
assert r.json() == [{"id": 7, "title": "seeded"}]
async def test_override_service_then_reset():
class FakeService:
def create(self, title):
return 42
def listing(self):
return [{"id": 42, "title": "mocked"}]
container.service.override(providers.Factory(FakeService))
async with make_client() as client:
assert (await client.post("/todos", json={"title": "x"})).json() == {"id": 42}
assert container.service.overridden
container.service.reset_override()
assert not container.service.overridden
assert isinstance(container.service(), TodoService)
async def test_unwire_stops_injection():
container.unwire()
try:
async with make_client() as client:
with pytest.raises(AttributeError):
await client.post("/todos", json={"title": "a"})
finally:
container.wire(modules=["app"])
[pytest] pythonpath = . asyncio_mode = auto
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
FastAPI dependency_overrides tested offline with httpx ASGITransport
Override FastAPI dependencies (including nested ones) and drive the app in-process with httpx.ASGITransport, no network and no running server.
FastAPI metrics with prometheus_client scraped as text
Instrument FastAPI with prometheus_client using a per-app registry, label by route template to bound cardinality, and scrape the text exposition format offline through httpx.ASGITransport.
FastAPI metrics with prometheus-fastapi-instrumentator tested offline
Instrument a FastAPI app with the default prometheus-fastapi-instrumentator metrics, scrape /metrics through httpx.ASGITransport, and cover the route-template labels, untemplated grouping, self-scraping and shared-registry gotchas.
FastAPI config via pydantic-settings as a cached dependency
Load config from env vars with pydantic-settings, inject it into FastAPI routes through a cached dependency, and test it offline with monkeypatch and dependency_overrides.
All FastAPI recipesAll dependency-injector recipesAll httpx recipesAll pytest recipes