Factory.ai

Open-Source Wikis

/

FastAPI

/

FastAPI

/

Architecture

fastapi/fastapi

Architecture

FastAPI is a thin, opinionated layer above two libraries:

  • Starlette — supplies the ASGI application, routing primitives, request/response objects, WebSocket handling, middleware stack, background tasks, and TestClient.
  • Pydantic v2 — supplies the validation engine, JSON serialization, and JSON-Schema generation that drives both runtime parsing and OpenAPI output.

FastAPI itself contributes:

  1. A signature-driven dependency-injection system.
  2. Automatic OpenAPI 3.1 schema generation from those signatures.
  3. Path, Query, Header, Cookie, Body, Form, File, Depends, and Security markers that make the signature unambiguous.
  4. Standardized validation-error responses and a small set of security scheme classes.
  5. A bundled Swagger UI and ReDoc, served from /docs and /redoc.

Component map

graph TD
    User[User code: app = FastAPI; @app.get etc.]
    User --> App["FastAPI class<br/>fastapi/applications.py"]

    App -->|inherits| Starlette["Starlette<br/>(third-party)"]
    App -->|owns| Router["APIRouter<br/>fastapi/routing.py"]
    App -->|registers| ExHandlers["Exception handlers<br/>fastapi/exception_handlers.py"]
    App -->|builds| OpenAPI["OpenAPI generator<br/>fastapi/openapi/utils.py"]
    App -->|serves| DocsUI["Swagger UI / ReDoc HTML<br/>fastapi/openapi/docs.py"]

    Router -->|each route owns a| Dependant["Dependant tree<br/>fastapi/dependencies/models.py"]
    Router -->|wraps with| ExitStack["AsyncExitStackMiddleware<br/>fastapi/middleware/asyncexitstack.py"]

    Dependant -->|built by| Resolver["solve_dependencies / get_dependant<br/>fastapi/dependencies/utils.py"]
    Resolver -->|uses| Compat["_compat (Pydantic v2 wrapper)<br/>fastapi/_compat/v2.py"]
    Resolver -->|uses| Params["Param / Body / Form / File<br/>fastapi/params.py"]

    OpenAPI -->|reads| Models["OpenAPI models<br/>fastapi/openapi/models.py"]
    OpenAPI -->|reads| Dependant
    OpenAPI -->|reads| Compat

    Security["fastapi/security/*"] -->|implements| SecBase["SecurityBase<br/>fastapi/security/base.py"]
    Security -->|appears as| Dependant

Request lifecycle

For an HTTP request to app:

sequenceDiagram
    participant Client
    participant ASGI as ASGI server (uvicorn)
    participant Stack as Starlette middleware stack<br/>(ServerError → user → Exception → AsyncExitStack)
    participant Route as APIRoute.get_route_handler
    participant Solve as solve_dependencies
    participant Endpoint as User function
    participant Serialize as serialize_response

    Client->>ASGI: HTTP request
    ASGI->>Stack: scope, receive, send
    Stack->>Route: dispatched by path match
    Route->>Solve: walk Dependant tree
    Solve->>Solve: resolve Path/Query/Header/Cookie/Body
    Solve->>Solve: call sub-Depends (with caching)
    Solve-->>Route: kwargs + values, plus errors
    alt validation errors
        Route-->>Stack: raise RequestValidationError
        Stack-->>Client: 422 JSON
    else ok
        Route->>Endpoint: await endpoint(**values)
        Endpoint-->>Route: return value
        Route->>Serialize: validate against response_model
        Serialize-->>Client: JSONResponse / StreamingResponse / …
    end

The two AsyncExitStacks scoped per-request (fastapi_inner_astack, fastapi_function_astack, plus the outer fastapi_middleware_astack) are how dependencies declared with yield get their cleanup blocks called after the response is sent. See request_response() in fastapi/routing.py and AsyncExitStackMiddleware in fastapi/middleware/asyncexitstack.py.

Why Pydantic and Starlette show through

FastAPI deliberately re-exports many third-party types so user code only imports from fastapi:

  • fastapi.status is starlette.status.
  • fastapi.responses.JSONResponse, HTMLResponse, StreamingResponse, etc. are Starlette responses.
  • fastapi.testclient.TestClient is starlette.testclient.TestClient.
  • fastapi.staticfiles.StaticFiles, fastapi.templating.Jinja2Templates, fastapi.requests.Request, fastapi.websockets.WebSocket all forward to Starlette.

Look at the one-line shim files (fastapi/staticfiles.py, fastapi/templating.py, fastapi/testclient.py, fastapi/requests.py) to see this pattern.

What _compat is for

fastapi/_compat/__init__.py re-exports a small, stable surface (ModelField, Undefined, get_definitions, get_schema_from_model_field, …). The actual implementations now live in fastapi/_compat/v2.py because Pydantic v1 support was dropped — see the PydanticV1NotSupportedError raised in fastapi/utils.py:create_model_field. The _compat boundary used to switch between Pydantic v1 and v2 implementations; today it isolates the rest of FastAPI from Pydantic v2 internals.

Testing architecture

Tests live in tests/. There are three flavours:

  • Unit tests for the framework (tests/test_*.py).
  • Tutorial tests (tests/test_tutorial/) — every example in docs_src/ is exercised, so the docs cannot drift from the framework's behaviour.
  • Benchmarks (tests/benchmarks/) — pytest-codspeed micro-benchmarks for CodSpeed.

The pyproject coverage config measures fastapi, tests, and docs_src together; that's why the docs source is a first-class part of the codebase.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Architecture – FastAPI wiki | Factory