If you're standing up a new Python API in 2026, the ecosystem has finally settled. Most "what should I use for X" questions have a clear answer if you've been paying attention the last 18 months. Here's the full stack I'd ship today, layer by layer, with the reasoning for each pick and the alternative I considered and ruled out.
Two prior posts feed into this one: FastAPI vs Litestar in 2026 covers the framework choice in depth, and Production FastAPI: A Best Practices Field Guide covers the conventions you'd layer on top.
The cheatsheet
| Layer | Pick | Runner-up |
|---|---|---|
| Framework | FastAPI | Litestar |
| Validation | Pydantic v2 | msgspec (FastAPI forces Pydantic) |
| Error responses | fastapi-problem | roll your own RFC 9457 handler |
| ORM | SQLAlchemy 2.0 async | Tortoise |
| DB driver | psycopg3 | asyncpg |
| Migrations | Alembic | — |
| Type checker | pyright (watch ty) | mypy |
| Lint/format | ruff | — |
| Package mgmt | uv | — |
| ASGI server | uvicorn | granian |
| Background jobs | taskiq | arq |
| Settings | pydantic-settings | — |
| Test runner | pytest | — |
| Test data | polyfactory | factory_boy |
| Test infra | testcontainers | — |
| Local dev | Docker Compose | — |
| Logging API | structlog | stdlib only |
| Telemetry | OpenTelemetry | — |
| Observability backend | Sentry | Dash0 / Honeycomb |
| IaC | OpenTofu | Terraform |
| Conventions | zhanymkanov/fastapi-best-practices | — |
Now the reasoning for each one.
Framework: FastAPI
FastAPI is the default for new Python APIs in 2026. I went through the full case for it in FastAPI vs Litestar in 2026, so the short version: Litestar has a better feature list on paper (DTOs, layered controllers, msgspec, a real plugin system), but FastAPI has roughly 12x the stars, the ecosystem most engineers already know, and overwhelmingly more LLM training data. That last factor decides it for most teams. When half your day is spent pair-programming with an AI, the framework your model knows fluently is the framework you ship faster in.
The exceptions: pick Litestar if you have a specific feature need it uniquely solves (WebSocket pub-sub via channels, heavy DTO transformations from SQLAlchemy models, a strong objection to Pydantic in your dependency tree). Otherwise FastAPI.
Validation: Pydantic v2
Pydantic v2 is required by FastAPI. I'd pick it anyway. The Rust-based core (pydantic-core) is roughly 10x faster than v1, the v2 API is cleaner, and model_validator / field_validator cover the edge cases that used to require custom code. Pair it with pydantic-settings for configuration, and split your settings across modules the way the best-practices guide recommends.
Worth knowing about the runner-up. msgspec is roughly 5-10x faster than Pydantic v2 in most benchmarks. Pydantic's core is in Rust; msgspec is written entirely in C with a smaller feature surface and a decoder that goes straight from JSON bytes to typed structs without building an intermediate Python dict. That makes it the right pick when serialization throughput is the actual bottleneck, like high-volume event ingestion or low-latency proxy services. Litestar uses msgspec as its default validation layer for exactly that reason. FastAPI doesn't give you the option: it's welded to Pydantic at the framework level, so "FastAPI + msgspec" isn't really a stack you can assemble without rewriting half the framework.
Error responses: fastapi-problem
Every HTTP API eventually invents its own error shape, which is exactly the problem RFC 9457 — Problem Details for HTTP APIs was written to solve. fastapi-problem (and its base library rfc9457) is the easiest way to wire it into FastAPI: subclass a base problem class with a title and status, raise it from your endpoint, and clients get a structured application/problem+json response with type, title, status, detail, and instance plus any extension members you add. No custom exception handlers, no per-service error format that downstream clients have to learn.
I covered the spec itself, what changed from RFC 7807, and the broader implementation landscape in RFC 9457: A Standard Shape for HTTP API Errors.
ORM: SQLAlchemy 2.0 async
SQLAlchemy 2.0 finally made async a first-class citizen. AsyncSession, async_sessionmaker, and the Mapped[] type-annotated model syntax give you a typed, async ORM with the deepest ecosystem in Python. The historical reason to reach for Tortoise, Piccolo, or Ormar ("I want async and SQLAlchemy is sync") no longer holds.
Use SQLAlchemy 2.0 with:
AsyncSessionfor app code (FastAPI dependency-injected).- Sync
Sessionfor Alembic migrations and ad-hoc scripts. Mapped[type]annotations on every column for type-checker support.
The other async ORMs are fine for projects that explicitly want a Django-like API (Tortoise) or a built-in admin UI (Piccolo). For everything else, SQLAlchemy.
Honorable mention: SQLModel. Tiangolo's library that combines SQLAlchemy and Pydantic into a single class so you don't maintain parallel model and schema definitions. Genuinely pleasant for small CRUD services. Two reasons it didn't make the recommended slot: it's still at v0.0.38 after almost five years (which signals the maintainer doesn't consider it stable enough for 1.0), and the "single class" win evaporates as soon as you need separate Create / Read / Update shapes, which is most production APIs. Worth knowing about; not what I'd reach for in a new service today.
Database driver: psycopg3
psycopg3 over asyncpg is the call that surprises some people. asyncpg has the per-query speed crown (a recent goldlapel benchmark puts it ~23% ahead on throughput at 500 concurrent users), 8k stars to psycopg3's 2.4k, and the binary protocol decoding wins on JSONB-heavy workloads. For a SQLAlchemy 2.0 app deployed against modern managed Postgres in 2026, psycopg3 is still the cleaner pick.
The big win is that one driver covers both sync and async. Migrations, scripts, and shells stay sync; FastAPI stays async; both go through psycopg3 with the same connection pool semantics. Feature parity is the second reason: psycopg3 has first-class support for COPY, server-side cursors, pgvector, and newer Postgres features that asyncpg either lags on or doesn't cover. And the SQLAlchemy 2.0 docs now default to psycopg3 as the recommended async driver (postgresql+psycopg://...) precisely because the integration story is smoother.
The deciding factor for most teams is connection pooling. Transaction-mode poolers like PgBouncer break asyncpg's prepared statement cache entirely. Supabase, Neon, and most serverless Postgres providers run transaction-mode pooling by default. You can disable asyncpg's cache (statement_cache_size=0) to make it work, but you give up the 15-30% prepared-statement speedup that was supposed to be its advantage. psycopg3 handles transaction-mode pools gracefully out of the box. asyncpg's statement cache also interacts poorly with SQLAlchemy's connection recycling, which is a separate footgun for the ORM crowd.
Pick asyncpg if you're on direct Postgres connections (no pooler), throughput is the bottleneck, and you're writing raw asyncpg.Pool code without SQLAlchemy. For everyone else — which is most of us, given how managed Postgres works in 2026 — psycopg3.
Migrations: Alembic
Alembic is the only real option, and it's been stable for years. Three rules from the best-practices guide worth repeating: migrations must be static and reversible, slugs must be descriptive, and the file template should be date-prefixed (%%(year)d-%%(month).2d-%%(day).2d_%%(slug)s) so the alembic history reads chronologically in any directory listing.
Type checker: pyright (with ty on deck)
pyright is my pick for 2026, but worth a closer look because the picture is moving fast.
The three contenders:
| mypy | pyright | ty | |
|---|---|---|---|
| Stars | 20,421 | 15,436 | 18,649 |
| Created | 2012 | 2019 | May 2025 |
| Language | Python | TypeScript | Rust |
| Maintainer | python org | Microsoft | Astral |
mypy is the spec-strict original, written in Python, and you feel that on every CI run. pyright is the TypeScript-based Microsoft alternative, roughly 10x faster on real codebases, and it's already what's running in your VS Code via Pylance. Using it at the CLI means CI checks the same thing your editor checks.
The dark horse: ty, Astral's Rust-based type checker. 18.6k stars in roughly a year, from the team behind ruff and uv. It's still in preview as of this writing, but Astral's track record on toolchain quality is unmatched, and ty is plausibly the answer by late 2026. Build with pyright now and revisit ty when it GAs.
Lint and format: ruff
ruff replaced black, isort, autoflake, flake8, and a long tail of plugins with one Rust binary that runs in milliseconds. There's no remaining argument for the older toolchain on a new project. Configure it once in pyproject.toml and stop thinking about formatting.
Package management: uv
uv is the third tool in Astral's set (alongside ruff and ty) and it has eaten poetry, pip-tools, pyenv, and pipx in a single binary. It's faster than every alternative by an order of magnitude, the lockfile is reproducible, and uv sync is the only command you need to remember. Use it for dependency management, virtual envs, and Python version installation. If you're still on poetry in 2026, migrate.
ASGI server: uvicorn
uvicorn is the default and there's no reason to switch. It's stable, well-maintained, what the FastAPI docs assume, and every observability tool integrates with it cleanly.
The escape hatch worth knowing about: granian, a Rust-based ASGI server with measurable throughput gains. If you're CPU-bound at the server layer (rare for typical CRUD APIs), give it a try. Otherwise, uvicorn.
Background jobs: taskiq (not Celery)
Celery has 28k stars and 776 open issues. That ratio tells the story. It's a 2009-era library with sync-first internals and async grafted on awkwardly, plus an enormous configuration surface and a worker model that doesn't match FastAPI's event-loop world. For an existing Celery deployment with Beat schedules and polyglot consumers, fine. For a new 2026 FastAPI project, it's the wrong call.
The async-native contenders:
| arq | taskiq | dramatiq | |
|---|---|---|---|
| Stars | 2,914 | 2,139 | 5,232 |
| Brokers | Redis | Redis, NATS, RabbitMQ, Kafka | Redis, RabbitMQ |
| Async-native | Yes | Yes (designed for it) | No (sync-first) |
| Maintainer | Samuel Colvin | taskiq-python org | Bogdan Popa |
taskiq is the one I'd reach for. Designed async-first, dependency injection that mirrors FastAPI's Depends(), and the broker is pluggable so you can start on Redis and move to NATS or RabbitMQ without rewriting tasks.
arq is the conservative pick if you only need Redis and prefer a smaller surface area. It's been around since 2016, Samuel Colvin (Pydantic) is behind it, and it's about as boring-and-stable as a task queue gets. Either is a defensible choice.
dramatiq is well-engineered but sync-first like Celery, plus the LGPL license is a yellow flag for some enterprises. Skip unless you have a specific reason.
Testing: pytest + testcontainers + polyfactory
pytest is the only real test runner. Nothing surprising here.
testcontainers-python is what you want for integration tests. Spin up a real Postgres or Redis in a container per test session, run your tests against it, throw it away. No more SQLite-pretending-to-be-Postgres, no docker-compose-up dance in CI, no mock-the-DB tests that pass while production breaks.
For test data:
| factory_boy | polyfactory | |
|---|---|---|
| Stars | 3,792 | 1,463 |
| Style | Declarative (manual field generators) | Type-hint-driven (auto-derives from model) |
| Best for | Django, legacy untyped models | Pydantic / SQLAlchemy 2.0 with Mapped[] |
polyfactory wins for a 2026 FastAPI stack. Your domain is Pydantic + SQLAlchemy 2.0 with full type annotations, which is exactly what polyfactory is designed to read: class UserFactory(ModelFactory[User]): ... is the entire factory. factory_boy makes you maintain a parallel declaration of each field; polyfactory derives it from the model itself.
factory_boy still wins for Django projects because of DjangoModelFactory's transactional fixture integration. For everything else in 2026, polyfactory.
Local development: Docker Compose
Docker Compose handles local dev: Postgres, Redis, anything else your service talks to, all defined in one docker-compose.yml you docker compose up and forget about. Pair it with testcontainers and you get the same images in dev and test, two different consumers, fewer "works on my machine" surprises.
Logging: structlog + stdlib + OpenTelemetry
This deserves its own diagram. The mental model: three independent concerns, not three stacked layers.
Application code └─ log = structlog.get_logger() ← API layer └─ stdlib logging (universal sink) ← compatibility layer └─ JSON to stdout, OR OTLP LoggingHandler ← transport layer └─ OpenTelemetry → backend (Sentry, ...) ← destination
structlog is the developer-facing logging API. Not because of JSON rendering (OTel's LoggingHandler will turn stdlib logs into OTLP just fine) but because bind(), contextvars.merge_contextvars, and the processor pipeline give you ergonomics stdlib alone never will. Inject trace_id and span_id via a custom processor and every log line correlates to the trace it came from.
Stdlib logging stays in the middle because every library you depend on (SQLAlchemy, uvicorn, httpx, boto3) emits there. structlog's ProcessorFormatter bridges the two so library logs come out in the same JSON shape as your app's logs.
OpenTelemetry handles transport. Auto-instrumentation for FastAPI, SQLAlchemy, and httpx gives you traces for free. Logs go out via OTLP (either directly from the app or through an OTel Collector sidecar that tails stdout).
Observability backend: Sentry
Sentry is where I'd send the telemetry. The 2026 framing matters: Sentry now accepts OTLP for both traces and logs (in beta but stable enough to ship on), which means you can treat it as one OTel-compatible backend among many. The setup:
- Always run OpenTelemetry. It's the universal export layer.
- Add the Sentry SDK with the
OpenTelemetryIntegrationwhen you want Sentry-specific error richness (locals on exceptions, breadcrumbs, release tracking, session replay). - Point the OTLP exporter env vars at Sentry's OTLP endpoints. Switch to Honeycomb, Dash0, Datadog, or Tempo+Loki by changing env vars. Zero application code changes.
Sentry's combination of error monitoring, traces, logs, replays, and a generous free tier makes it an easy starting point for most teams. If you're cost-sensitive at scale or have a strong OTel-purist preference, Dash0 and Honeycomb are the leading alternatives. Same OTLP plumbing, different backend.
Infrastructure as code: OpenTofu
Terraform (48k stars) was the default for a decade. HashiCorp relicensed it from MPL to the Business Source License in August 2023, which isn't OSI-approved open source and carries commercial-use restrictions. The Linux Foundation forked the last MPL release as OpenTofu, which now sits at 28.6k stars (impressive for a 2.5-year-old project) and is fully drop-in compatible with Terraform's HCL.
For a new project in 2026: OpenTofu. Same HCL syntax, same provider ecosystem, no licensing risk, no relicensing-surprise future. Spacelift, env0, Scalr, and Terramate all support it. Major cloud providers publish compatible providers.
The enterprise picture is more nuanced: shops with existing HCP Terraform contracts (the rebranded Terraform Cloud) mostly stay put because the migration isn't free and HashiCorp's commercial support matters in regulated industries. But OpenTofu has reached parity for new work, and the Linux Foundation backing makes it safe to bet on.
The reason to put IaC in a "platform-agnostic stack" article: your application code runs on any cloud, but if you bake yourself into AWS CloudFormation or GCP Deployment Manager, you've coupled your operations to a single vendor. HCL with OpenTofu keeps the door open.
Conventions: the zhanymkanov best-practices guide
zhanymkanov/fastapi-best-practices is the rulebook for how to actually structure a FastAPI codebase that survives growth: domain-driven layout, async-route discipline, Pydantic-everywhere, dependency composition, SQL-first data access, async test client from day 0. I went through the full list with commentary in Production FastAPI: A Best Practices Field Guide. The repo also ships an AGENTS.md for LLM consumption, which matters because most of the code in 2026 has an AI in the loop.
The 30-second summary
For a new Python API in 2026:
Write it in FastAPI with Pydantic v2, returning errors via fastapi-problem (RFC 9457). Persist to Postgres via SQLAlchemy 2.0 async on psycopg3, with Alembic for migrations. Use uv for packaging, ruff for lint/format, pyright for types (watch ty). Serve with uvicorn. Run background jobs in taskiq. Test with pytest + testcontainers + polyfactory. Develop locally with Docker Compose. Log through structlog, ship telemetry through OpenTelemetry, default the backend to Sentry. Deploy infrastructure with OpenTofu. Follow the zhanymkanov best-practices guide for conventions.
Every pick has a runner-up and the whole stack composes: one async runtime end to end, one type-hint discipline from request through ORM through tests, OTel transport regardless of where the telemetry lands.
If you disagree on any layer, the runner-up column is where to start the argument.