CodeVetter · Repo Unpack · Behavior traces
fastapi/fastapi
Three flows dominate: application construction, a normal HTTP request, and OpenAPI generation. Construction is eager — dependants and response fields are computed at decoration time, so route registration errors surface at import. A request walks ASGI middleware, router matching (now indirected through `_IncludedRouter` for included routers), the exit-stack-wrapping `request_response` shim, dependency solving, endpoint invocation, and serialization. Streaming requests diverge at the handler into three branches (SSE, JSONL, raw generator). WebSockets follow a parallel but simpler path with no body parsing and no response serialization.
- Startup trace: `FastAPI.__init__` assigns metadata, asserts `title` and `version` when `openapi_url` is truthy, constructs `self.router = APIRouter(...)` with `dependency_overrides_provider=self`, seeds three default `exception_handlers`, and finally calls `self.setup()` which registers the `/openapi.json`, `/docs`, oauth2-redirect and `/redoc` routes.
- Route registration trace: `@app.get(...)` → `app.router.add_api_route` → `APIRoute.__init__` → `_populate_api_route_state(...)` (which builds `dependant`, `body_field`, `response_field`, `stream_item_field`, `unique_id`) → `self.app = request_response(self.get_route_handler())`.
- Lifespan trace: `APIRouter.app` routes `scope["type"] == "lifespan"` to `self.lifespan`; when no custom lifespan is given, `_DefaultLifespan` (re-vendored after Starlette removed it) calls `router._startup()` / `router._shutdown()` so `on_startup`/`on_shutdown` handlers still work, and `include_router` merges child lifespans via `_merge_lifespan_context`.
- Matching trace for an included router: `APIRouter.app` iterates `self.routes`, an `_IncludedRouter` entry answers `matches()` by building `_EffectiveRouteContext` objects that apply the accumulated prefix/tags/dependencies, then `handle()` dispatches through `_handle_selected`.
- Request trace (happy path): `request_response.app` creates the `Request`, opens `fastapi_inner_astack` and `fastapi_function_astack`, awaits the handler, sends the response, then asserts `response_awaited` — raising a `FastAPIError` naming the bare-`except`-in-a-yield-dependency footgun if the stack unwound without sending.
- Handler trace: `get_request_handler.app` → `_extract_endpoint_context(dependant.call)` → body read → `solve_dependencies(...)` → branch on `is_sse_stream` / `is_json_stream` / generator / plain → `run_endpoint_function` → `serialize_response` → `_build_response_args` → Response.
- Sync endpoints are offloaded: `run_endpoint_function` calls `run_in_threadpool(dependant.call, **values)` when `_is_coroutine_callable(dependant.call)` is false, and `serialize_response` likewise runs `field.validate` in a threadpool for sync endpoints.
- SSE trace: the generator is obtained, wrapped in `_sse_producer_cm()` entered on `fastapi_inner_astack` (so teardown is structured rather than via `GeneratorExit`), each item routed through `_serialize_sse_item` → `format_sse_event`, then yielded through `_sse_with_checkpoints` which `await anyio.sleep(0)` after each chunk so cancellation can be delivered.
- WebSocket trace: `websocket_session` opens the same two exit stacks, `get_websocket_app.app` builds an endpoint context labelled `WS <path>`, calls `solve_dependencies` with no body, raises `WebSocketRequestValidationError` on errors, and otherwise awaits the endpoint directly — there is no response serialization step.
- WebSocket validation failures close the socket with code 1008 (`WS_1008_POLICY_VIOLATION`) and the encoded errors as the reason, via `websocket_request_validation_exception_handler`.
- Frontend fallback trace: `_FrontendStaticFiles.get_response` rejects non-GET/HEAD with 405 or 404, looks up the static resource, redirects directory hits missing a trailing slash, then applies the `404.html` / `index.html` fallback — the latter only if `_is_frontend_navigation_request` finds `text/html` or `application/xhtml+xml` with non-zero q in the Accept header.
- OpenAPI trace: `app.openapi()` → `get_openapi(...)` → `get_fields_from_routes` (collects body/response/stream/param `ModelField`s across `iter_route_contexts`) → `get_flat_models_from_fields` → `get_model_name_map` → `get_definitions` → per-route `get_openapi_path` → `jsonable_encoder(OpenAPI(**output), by_alias=True, exclude_none=True)`.
- Docs page trace: a GET to `/docs` runs the closure registered in `setup()`, which prefixes `openapi_url` with the scope's `root_path` and calls `get_swagger_ui_html`, emitting an HTML page that loads Swagger UI from `https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/`.