# 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`