FastAPI development
- Always define an application factory (
create_app) in<module>/__init__.pyor<module>/main.py(prefer the former). - Import routers in app factory and register them via
app.include_router. Define a helper_register_routersfunction colocated withcreate_app. - Define a custom exceptions module at
<module>/exceptions.pywith a base application error. Then register an exception handler via@app.exception_handler(BaseAppError)to return proper JSON responses. - Ensure pydantic's
RequestValidationErrorandValidationErrorare handled too and transformed to a consistent error response format. - All responses use generic envelope schemas (
APIResponse[T],APIListResponse[T],APIErrorResponse) so the OpenAPI spec surfaces the wrapping. Seereferences/response-schemas.md.
Routers
Routers are thin HTTP boundaries. They handle authentication, authorization, query parameter parsing, and response shaping, nothing else. Business logic lives in plain functions called "operations".
- Common dependencies are declared as annotated aliases, e.g:
UseDBSession = t.Annotated[Session, Depends(get_db_session)]— SQLAlchemy sessionUseSettings = t.Annotated[Settings, Depends(get_settings)]— Pydantic settings object
- Cross-cutting request context is bundled into a single
AppContextdataclass injected viaDepends. Adding new cross-cutting concerns (e.g. request ID) means updating the dataclass and its factory, not every operation signature. - Follow the naming pattern
UseX. If routers start to have many different dependencies, refactor the most common ones to aUseAppContext.
Operations
Operations are plain functions (or coroutines) that receive:
ctx: AppContextas the first argument (orsessionandsettings)- Input data (validated Pydantic schemas) as keyword arguments
They handle business logic and external service calls.
Testing
- Override individual dependencies via
app.dependency_overrides - Operations are tested by calling them directly with a constructed
AppContext
Source: richin13/dotfiles — distributed by TomeVault.