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

5.5 KiB

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