Module 2 · FastAPI · Drills
Reading the deep-dive is not knowing it. Type every one of these into a real app/ folder and run it before you reveal the solution. Effortful recall is the point.
uvicorn or pytest. Attempt it first, run it, then click "Show solution" to compare. If yours works differently but correctly — great, that's fluency. Tick each box as you go; your progress is saved in this browser.
Drill 1 Depends
Write a dependency get_config that returns a dict {"app": "DocChat", "version": "1.0"}, then inject it into a GET /info endpoint that returns it.
from fastapi import Depends, FastAPI
app = FastAPI()
def get_config():
return {"app": "DocChat", "version": "1.0"}
@app.get("/info")
def info(config: dict = Depends(get_config)):
return config
Drill 2 CORS
Add CORSMiddleware to an app so a Next.js frontend on http://localhost:3000 can call it. Allow all methods and headers.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Remember: CORS is enforced by the browser, not by Postman or curl. If a request works in Postman but fails from React, this middleware is what's missing.
Drill 3 APIRouter
Move a GET /documents/ endpoint out of main.py into an APIRouter (in routers/documents.py) with a /documents prefix, then mount it with include_router.
routers/documents.py
from fastapi import APIRouter router = APIRouter(prefix="/documents", tags=["documents"]) @router.get("/") def list_docs(): return [{"id": 1, "title": "Intro"}]
main.py
from fastapi import FastAPI from routers import documents app = FastAPI() app.include_router(documents.router)
Drill 4 TestClient
Write a pytest test using TestClient that asserts GET /documents/ returns status 200. Assume the router from Drill 3.
tests/test_documents.py
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_documents_returns_200():
response = client.get("/documents/")
assert response.status_code == 200
Run it with pytest. TestClient drives the app in-process — no uvicorn, no real network, instant feedback.
Drill 5 BackgroundTasks
Add a POST /documents endpoint that returns {"status": "processing"} immediately and schedules a process_document function as a background task.
from fastapi import APIRouter, BackgroundTasks router = APIRouter(prefix="/documents") def process_document(doc_id: int): print(f"processing {doc_id}...") # chunk + embed later @router.post("/") def upload(doc_id: int, tasks: BackgroundTasks): tasks.add_task(process_document, doc_id) return {"status": "processing"}
The response returns first; process_document runs after. That's exactly how DocChat will accept an upload and embed it without making the user wait.
get_current_settings dependency that reads config from the environment using pydantic-settings, then inject it into an endpoint. This keeps secrets (DB URL, API key) out of code and makes them swappable in tests — the production-grade config pattern.
Build · settings dependency
Create a Settings class reading database_url and openai_api_key from the environment / a .env file. Expose it through a get_current_settings dependency and use it in GET /health.
config.py
from functools import lru_cache
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
database_url: str
openai_api_key: str
model_config = SettingsConfigDict(env_file=".env")
@lru_cache
def get_current_settings() -> Settings:
return Settings() # cached — built once, reused
main.py
from fastapi import Depends, FastAPI from config import Settings, get_current_settings app = FastAPI() @app.get("/health") def health(settings: Settings = Depends(get_current_settings)): return {"db_configured": bool(settings.database_url)}
Note @lru_cache: the settings are read once and reused across requests. In a test you can override the dependency — app.dependency_overrides[get_current_settings] = lambda: fake — to inject test config without touching the environment.
Click a card to flip it. Say the answer out loud before you flip — that's the rep that builds storage strength.
Depends?localhost:3000.APIRouter do?app.include_router(...).TestClient for?yield dependency give you?yield, teardown after (in finally) — guaranteed cleanup of DB sessions etc.BackgroundTasks?Tick each only if you can do it without looking:
Depends dependency and a yield dependency with cleanupAPIRouter and mount it with include_routerCORSMiddleware and explain why the browser needs itTestClient test asserting a 200pydantic-settings and a BackgroundTask