Files
fastapi-conventions/comparison/fastapi-vs-python.md
T
2026-06-02 00:53:41 +00:00

132 lines
5.5 KiB
Markdown

# FastAPI vs Python
Use this file when a rule *sounds* generic but actually changes once FastAPI owns the boundary.
## Quick decision rule
Keep the guidance in `python-patterns` when it still makes sense without HTTP or FastAPI.
Put it here when FastAPI owns the boundary: route handler shape, `Depends(...)` wiring, request/response schemas, auth checks, exception-to-response translation, app lifecycle, or app-level tests.
## Where FastAPI deliberately bends the generic Python advice
### 1) Dependency injection: explicit constructor wiring vs signature-driven request wiring
In general Python code, explicit construction is usually clearer than hidden injection. Prefer normal parameters, constructors, or small factories.
In FastAPI routes, request-scoped wiring belongs in dependency callables and handler signatures.
**Python default:**
- pass collaborators explicitly
- construct objects in obvious application setup code
- avoid magical ambient injection
**FastAPI adjustment:**
- use `Depends(...)` for sessions, current-user lookup, auth gates, and other request-scoped collaborators
- keep the injected object visible in the handler signature
- do not drag `Depends(...)` down into reusable domain code
See:
- sibling repo: `python-patterns/patterns/module-design.md`
- `patterns/dependencies.md`
### 2) Data models: internal value objects vs transport schemas
Generic Python guidance says not to make one model own validation, persistence, transport, and domain behavior all at once.
FastAPI sharpens that rule: Pydantic is especially valuable at the HTTP boundary, so boundary models are the default even when internal models differ.
**Python default:**
- keep internal state in small honest types
- validate and serialize explicitly at boundaries
- split models when concerns diverge
**FastAPI adjustment:**
- use request and response models aggressively at the API boundary
- let Pydantic own coercion and OpenAPI-facing schema behavior
- translate inward when storage or domain shape differs
See:
- sibling repo: `python-patterns/patterns/data-models.md`
- `patterns/pydantic-boundaries.md`
### 3) Async design: separate public sync/async APIs vs one app stack choice
Generic Python guidance often prefers separate sync and async surfaces when semantics differ.
A FastAPI service is different: the app already chose an HTTP execution model. Inside that service, the important question is not "should I expose both sync and async APIs?" but "where is the sync/async boundary, and is it honest?"
**Python default:**
- expose separate sync and async entrypoints when callers need different semantics
- do not hide event-loop control behind a fake sync wrapper
**FastAPI adjustment:**
- keep the app stack consistent and deliberate
- it is fine for async handlers to call sync-oriented persistence if that is the real stack choice and the framework/runtime setup supports it
- do not force async everywhere just because FastAPI can run async handlers
- do not hide blocking work accidentally behind an async-looking boundary
See:
- sibling repo: `python-patterns/patterns/async-boundaries.md`
- `patterns/persistence.md`
### 4) Errors: domain exception taxonomy vs HTTP meaning at the edge
Generic Python guidance focuses on exception types that help callers make decisions.
FastAPI adds a second responsibility: some failures must be expressed as HTTP status codes and response bodies.
**Python default:**
- raise exceptions that match the semantic failure
- keep transport concerns out of reusable core logic
**FastAPI adjustment:**
- raise `HTTPException` for request-level failures such as 400/401/403/404
- let FastAPI/Pydantic produce validation failures where possible
- translate domain or infrastructure exceptions at the HTTP boundary
- centralize response-envelope formatting in handlers or middleware, not in every route
See:
- sibling repo: `python-patterns/patterns/error-handling.md`
- `patterns/errors.md`
### 5) Testing: direct unit tests vs boundary-driven app tests
Generic Python testing guidance says to keep tests explicit, reusable, and fixture-driven.
FastAPI changes the highest-value integration seam: you usually want to test the app through HTTP/ASGI boundaries rather than by calling route functions directly.
**Python default:**
- test framework-agnostic business logic directly
- share setup with fixtures instead of ad hoc duplication
**FastAPI adjustment:**
- test routes through `TestClient` or HTTPX ASGI clients
- override request-time collaborators with `app.dependency_overrides`
- enter lifespan-aware client contexts when startup/shutdown matters
- still test pure business logic as plain Python when the framework is irrelevant
See:
- sibling repo: `python-patterns/patterns/testing.md`
- `patterns/testing.md`
### 6) Application assembly: normal context management vs lifespan-owned resources
In generic Python, resource lifetime is usually expressed with constructors, factories, and context managers.
In FastAPI and Starlette, app-wide resources often belong in lifespan, because the framework owns when the application starts and stops.
**Python default:**
- make resource lifetime explicit
- prefer normal context managers and obvious setup code
**FastAPI adjustment:**
- use lifespan for app-wide startup/shutdown resources
- use dependencies to hand request-scoped access to those resources into handlers
- do not hide startup work inside request dependencies
See:
- sibling repo: `python-patterns/patterns/module-design.md`
- `patterns/dependencies.md`
- `patterns/persistence.md`