Tighten FastAPI repo boundary guidance

This commit is contained in:
Rodin
2026-06-02 00:53:41 +00:00
parent a23e494026
commit 303927c2d6
3 changed files with 144 additions and 63 deletions
+9 -24
View File
@@ -67,14 +67,6 @@ Good source notes are dense evidence, not polished guidance.
### 4) Synthesize conventions from the strongest repeated signals
Start with the topics where evidence is strongest.
In this repo that meant:
- routes
- dependencies
- errors
- testing
- pydantic boundaries
- persistence
Each convention doc should usually include:
- the convention
- why it exists
@@ -100,14 +92,17 @@ Instead:
- reduce duplicated guidance across docs
- preserve subtle framework caveats that are easy to flatten away
That was the right next move for this repo once the first source base existed.
## Handling mixed-concern source code
## Fresh-context refinement pattern
Upstream service code will often mix FastAPI behavior, Pydantic boundary behavior, and ordinary Python design in the same function or line.
A good refinement split is:
- one fresh pass over convention docs
- one fresh pass over source-note files
- one fresh pass doing citation audit across both
When that happens:
- do not classify the whole snippet by file name or repo alone
- split the evidence into **framework-owned signal** and **generic Python signal**
- keep the request/response, dependency, lifecycle, and HTTP-boundary lesson here
- push reusable language-level guidance back to `python-patterns`
If the claim still makes full sense after removing FastAPI and HTTP, it is probably too generic to live here as a FastAPI convention.
## Review checklist
@@ -132,16 +127,6 @@ When the repo is ready for human review:
This repo intentionally avoids pushing or creating remotes unless explicitly requested.
## How to repeat this process next time
1. Define the scope split first.
2. Pick a compact but high-signal upstream set.
3. Build `sources/` before `patterns/`.
4. Synthesize the strongest conventions first.
5. Add comparison notes where the framework bends Python defaults.
6. Run a fresh-context refinement wave.
7. Initialize git only when the repo is reviewable.
## What to avoid
- documenting FastAPI from memory or tutorial vibes
+7 -33
View File
@@ -21,14 +21,6 @@ Primary upstreams mined so far:
- `fastapi-users/fastapi-users`
- `fastapi/full-stack-fastapi-template`
Why this mix works:
- FastAPI: router composition, dependencies, semantic exceptions, override-based testing seams
- Starlette: lifespan, middleware, exception-layer mechanics, application assembly
- Pydantic: request/response model boundaries and validation/serialization caveats
- HTTPX: ASGI and transport-based testing seams
- FastAPI Users: dependency-driven auth and router factory patterns
- Full-stack FastAPI Template: real service module structure, session/auth wiring, and schema transitions
## Current convention set
- `patterns/routes.md`
@@ -39,34 +31,16 @@ Why this mix works:
- `patterns/testing.md`
- `comparison/fastapi-vs-python.md`
## What belongs here
## Boundary rubric: does this belong in `fastapi-conventions`?
Examples:
- thin route handlers vs embedded business logic
- request/response schema boundaries
- dependency injection shape and lifetime
- error envelope and exception translation
- app startup/shutdown and resource wiring
- sync/async boundaries at the HTTP layer
Use this repo when the rule is mainly about the HTTP or app boundary: routes, `Depends(...)`, request/response schemas, HTTP error meaning, lifespan, or ASGI-level testing.
## What does **not** belong here
Push it back to the sibling `python-patterns` repo when the rule would still make sense in a CLI, library, or batch job.
Do not duplicate generic Python rules unless FastAPI deliberately bends them. Link back to `python-patterns` instead.
## Reviewing this repo
Recommended review order:
1. `README.md`
2. `PROCESS.md`
3. `sources/*.md` for evidence quality
4. `patterns/*.md` for synthesis quality
5. `comparison/*.md` for split-of-concerns clarity
Questions to ask during review:
- Is the guidance actually FastAPI/service-boundary specific?
- Are framework seams described where the framework really owns behavior?
- Are testing and dependency claims grounded in real source and tests?
- Are caveats preserved instead of over-generalized?
Quick check:
- if FastAPI or Starlette owns the behavior, this repo should lead
- if deleting the HTTP boundary leaves most of the rule intact, it is probably too generic for this repo
- if the split is awkward, prefer the higher-level doc and cross-link rather than duplicating guidance
## Core rule
+128 -6
View File
@@ -1,9 +1,131 @@
# FastAPI vs Python
Use this to capture places where FastAPI/Starlette conventions deliberately differ from broader Python guidance.
Use this file when a rule *sounds* generic but actually changes once FastAPI owns the boundary.
Examples to watch for:
- DI through callables and annotations instead of explicit constructor wiring
- framework-facing Pydantic models at transport boundaries
- async-first request handlers even when core logic stays sync
- startup/lifespan hooks instead of plain context management entrypoints
## 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`