# Data flow — fastapi/fastapi

> Canonical page: https://codevetter.com/unpack/fastapi/data-flow

# Data flow — fastapi/fastapi

Scanned commit 50113da16fec53b66b80d75e80a89296de4fa5a5 (2026-09-01). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

Input arrives as an ASGI scope. Static declaration time happens first: at decoration, `get_dependant()` walks the endpoint signature with `analyze_param()` and sorts every parameter into `path_params`/`query_params`/`header_params`/`cookie_params`/`body_params`/`dependencies` on a `Dependant` dataclass, each field wrapped in a `ModelField` holding a Pydantic `TypeAdapter`. At request time the handler reads and decodes the body (form vs JSON vs raw bytes), then `solve_dependencies()` recursively resolves sub-dependencies, coerces each parameter group against its `ModelField`, and returns a `SolvedDependency` bundle of values, errors, background tasks and a mutable `Response`. Output flows back through `serialize_response()` (or the streaming serializers) and out as a Starlette `Response`. State owners are the router's route list, the memoised `app.openapi_schema`, and the per-request `AsyncExitStack`s in the ASGI scope.
- Body decoding branches on content type: form bodies go through `await request.form()` with `body.close` pushed onto the middleware exit stack; otherwise raw bytes are parsed as JSON only when the Content-Type maintype is `application` and the subtype is `json` or ends in `+json`, else passed through as bytes. — [fastapi/routing.py#L425-457](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L425-L457)
- A `json.JSONDecodeError` during body parsing is converted into a `RequestValidationError` with a synthetic `json_invalid` error at loc `("body", e.pos)`; any other parse exception becomes a 400 `HTTPException` with detail 'There was an error parsing the body'. — [fastapi/routing.py#L450-471](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L450-L471)
- `solve_dependencies()` resolves sub-dependencies depth-first before extracting path/query/header/cookie values, and memoises each result in `dependency_cache` keyed by `_get_cache_key` — a tuple of `(call, sorted oauth scopes, computed scope)` — so the same dependency called twice in one request runs once unless `use_cache=False`. — [fastapi/dependencies/utils.py#L618-681](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/dependencies/utils.py#L618-L681) [fastapi/dependencies/models.py#L79-93](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/dependencies/models.py#L79-L93)
- Generator dependencies are entered as context managers by `_solve_generator`: async generators via `asynccontextmanager`, sync generators via `contextmanager_in_threadpool`, and pushed onto `fastapi_inner_astack` unless the dependency declares `scope="function"`, in which case `fastapi_function_astack` is used so it tears down before the response is sent. — [fastapi/dependencies/utils.py#L566-576](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/dependencies/utils.py#L566-L576) [fastapi/dependencies/utils.py#L666-676](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/dependencies/utils.py#L666-L676) [fastapi/routing.py#L134-155](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L134-L155)
- `contextmanager_in_threadpool` deliberately gives `__exit__` its own `CapacityLimiter(1)` so a blocking teardown cannot deadlock against the shared AnyIO threadpool used by a connection pool inside the dependency. — [fastapi/concurrency.py#L17-41](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/concurrency.py#L17-L41)
- Form bodies flow through `_extract_form_body`, which reads `UploadFile` values into `bytes` when the declared annotation is bytes, expands sequence-of-bytes fields, and passes through any unmatched form keys as single values or lists. — [fastapi/dependencies/utils.py#L912-948](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/dependencies/utils.py#L912-L948)
- `request_body_to_args` collapses to a single un-embedded field when `len(body_fields) == 1 and not embed_body_fields`, and in that case a `BaseModel`-annotated form body is exploded into its model fields before validation. — [fastapi/dependencies/utils.py#L951-1000](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/dependencies/utils.py#L951-L1000)
- Response serialization has a fast path: when a `response_field` exists and no custom `response_class` was supplied, `serialize_response(dump_json=True)` calls `ModelField.serialize_json` and the bytes are wrapped in a bare `Response(media_type="application/json")`, skipping the dict → `json.dumps` round trip. — [fastapi/routing.py#L710-745](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L710-L745) [fastapi/routing.py#L301-340](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L301-L340) [fastapi/_compat/v2.py#L215-235](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/_compat/v2.py#L215-L235)
- Headers set on the injected `Response` object by dependencies are merged into the final response via `response.headers.raw.extend(solved_result.response.headers.raw)` on every branch (SSE, JSONL, raw stream, and normal). — [fastapi/routing.py#L624-627](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L624-L627) [fastapi/routing.py#L676](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L676) [fastapi/routing.py#L749](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L749)
- When `is_body_allowed_for_status_code(status_code)` is false (1xx, 204, 205, 304), the serialized body is discarded by setting `response.body = b""`. — [fastapi/routing.py#L746-748](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L746-L748) [fastapi/utils.py#L26-41](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/utils.py#L26-L41)
- Validation failures never reach the endpoint: `errors` accumulated by `solve_dependencies` short-circuit the handler and raise `RequestValidationError`, which the default handler renders as HTTP 422 with `{"detail": jsonable_encoder(exc.errors())}`. — [fastapi/routing.py#L752-756](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L752-L756) [fastapi/exception_handlers.py#L19-26](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/exception_handlers.py#L19-L26)
- The OpenAPI document is produced once and cached on `app.openapi_schema`, invalidated by comparing `self.router._get_routes_version()` — a recursive counter that sums `_routes_version` across all `_IncludedRouter` children — against `_openapi_routes_version`. — [fastapi/applications.py#L1070-1103](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/applications.py#L1070-L1103) [fastapi/routing.py#L2573-2585](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L2573-L2585)
- The SSE output path decouples generator iteration from the keepalive timer using two `anyio` memory object streams with `max_buffer_size=1`, a `_producer` task and a `_keepalive_inserter` task in a task group, so `anyio.fail_after` never wraps the user generator's `__anext__` directly. — [fastapi/routing.py#L552-599](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L552-L599)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
