Python Async & FastAPI
Build high-performance Python APIs with FastAPI - async/await, Pydantic validation, dependency injection, background tasks, and OpenAPI docs.
skill: Python Async & FastAPI category: Backend tags: [Python, FastAPI, asyncio, Pydantic, ASGI] readingTime: 5
FastAPI combines Python's expressiveness with performance near Node.js - using async/await and ASGI. Its killer feature: Pydantic V2 model definitions automatically become OpenAPI documentation, request validation, and response serialization. Automatic docs at /docs is a game-changer for API development.
Workflow
- Define Pydantic models and route handlersFastAPI uses Pydantic for request/response models - validation, serialization, and OpenAPI schema generation all come from one model definition.
- Use dependency injectionFastAPI's DI system injects shared dependencies (database, auth, rate limiting) into route handlers - enabling reuse and testability without global state.
- Write async database queries with SQLAlchemyFastAPI with async SQLAlchemy handles concurrent requests efficiently - async queries release the event loop while waiting for I/O.
- Add background tasks and middlewareBackground tasks run after returning the response - for sending emails, webhooks, analytics events. Middleware handles cross-cutting concerns (CORS, logging, rate limiting).
- Test with pytest-asyncioFastAPI is fully testable with httpx AsyncClient - routes, dependencies, and database interactions can be tested in isolation.
Output Format
FastAPI: Pydantic BaseModel for request/response, Depends() for auth + DB injection, async SQLAlchemy with asyncpg, BackgroundTasks for post-response work, and httpx AsyncClient for testing.
class CreateOrderRequest(BaseModel): total_cents: int = Field(gt=0)
@app.post("/orders", response_model=OrderResponse)
async def create(req: CreateOrderRequest, db = Depends(get_db))
BackgroundTasks.add_task(send_email, order_id)
async with AsyncClient(transport=ASGITransport(app), base_url="http://test")Examples
Input
Output
Edge Cases & Gotchas
- Pydantic v2 breaking changes from v1 - FastAPI >= 0.100 uses Pydantic v2 by default. v1 -> v2 changes: .dict() → .model_dump(), .parse_obj() → .model_validate(), orm_mode → model_config = {"from_attributes": True}.
- Event loop per test - pytest-asyncio creates a new event loop per test by default. If your database pool is created at module level (not per-test), use scope="session" for async fixtures to share across tests.
- FastAPI dependency injection and circular imports - DI functions can create circular import issues when split across files. Consolidate dependencies in a single dependencies.py file and import them explicitly.
- uvicorn worker count - FastAPI runs on Uvicorn (ASGI server). For production: gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app - 4 workers × cores handles CPU-bound tasks; for pure I/O, even 1 async worker can handle thousands of requests.
When to Break These Rules
- Use Django instead of FastAPI for full-stack Python applications that need an admin panel, ORM migrations out-of-the-box, and built-in auth - FastAPI requires assembling these separately.
- Use synchronous Flask or Django for CPU-bound Python applications where async doesn't help - async advantages come from I/O concurrency, not CPU parallelism.
Reference Files
https://fastapi.tiangolo.com/https://docs.pydantic.dev/latest/