Tighten FastAPI repo boundary guidance
This commit is contained in:
+9
-24
@@ -67,14 +67,6 @@ Good source notes are dense evidence, not polished guidance.
|
|||||||
### 4) Synthesize conventions from the strongest repeated signals
|
### 4) Synthesize conventions from the strongest repeated signals
|
||||||
Start with the topics where evidence is strongest.
|
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:
|
Each convention doc should usually include:
|
||||||
- the convention
|
- the convention
|
||||||
- why it exists
|
- why it exists
|
||||||
@@ -100,14 +92,17 @@ Instead:
|
|||||||
- reduce duplicated guidance across docs
|
- reduce duplicated guidance across docs
|
||||||
- preserve subtle framework caveats that are easy to flatten away
|
- 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:
|
When that happens:
|
||||||
- one fresh pass over convention docs
|
- do not classify the whole snippet by file name or repo alone
|
||||||
- one fresh pass over source-note files
|
- split the evidence into **framework-owned signal** and **generic Python signal**
|
||||||
- one fresh pass doing citation audit across both
|
- 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
|
## 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.
|
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
|
## What to avoid
|
||||||
|
|
||||||
- documenting FastAPI from memory or tutorial vibes
|
- documenting FastAPI from memory or tutorial vibes
|
||||||
|
|||||||
@@ -21,14 +21,6 @@ Primary upstreams mined so far:
|
|||||||
- `fastapi-users/fastapi-users`
|
- `fastapi-users/fastapi-users`
|
||||||
- `fastapi/full-stack-fastapi-template`
|
- `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
|
## Current convention set
|
||||||
|
|
||||||
- `patterns/routes.md`
|
- `patterns/routes.md`
|
||||||
@@ -39,34 +31,16 @@ Why this mix works:
|
|||||||
- `patterns/testing.md`
|
- `patterns/testing.md`
|
||||||
- `comparison/fastapi-vs-python.md`
|
- `comparison/fastapi-vs-python.md`
|
||||||
|
|
||||||
## What belongs here
|
## Boundary rubric: does this belong in `fastapi-conventions`?
|
||||||
|
|
||||||
Examples:
|
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.
|
||||||
- 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
|
|
||||||
|
|
||||||
## 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.
|
Quick check:
|
||||||
|
- if FastAPI or Starlette owns the behavior, this repo should lead
|
||||||
## Reviewing this repo
|
- 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
|
||||||
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?
|
|
||||||
|
|
||||||
## Core rule
|
## Core rule
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,131 @@
|
|||||||
# FastAPI vs Python
|
# 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:
|
## Quick decision rule
|
||||||
- DI through callables and annotations instead of explicit constructor wiring
|
|
||||||
- framework-facing Pydantic models at transport boundaries
|
Keep the guidance in `python-patterns` when it still makes sense without HTTP or FastAPI.
|
||||||
- async-first request handlers even when core logic stays sync
|
|
||||||
- startup/lifespan hooks instead of plain context management entrypoints
|
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`
|
||||||
|
|||||||
Reference in New Issue
Block a user