Factory.ai

Open-Source Wikis

/

FastAPI

/

Features

/

Dependency injection

fastapi/fastapi

Dependency injection

The defining feature of FastAPI. The framework reads a function's signature, builds a Dependant tree, and resolves it on every request. Each piece below is implemented in fastapi/dependencies/utils.py (resolver) and fastapi/dependencies/models.py (the Dependant dataclass).

What gets injected

A path-operation function can declare any combination of:

| Annotation | What it becomes | | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | --------------- | | name: int (in path) | Path parameter | | name: int or name: int | None = ... | Query parameter | | Annotated[T, Path/Query/Header/Cookie/Body/Form/File(...)] | Explicit parameter (the marker carries metadata; see Parameters) | | Pydantic model annotation | Body parameter (or query if the model is configured for queries) | | Annotated[T, Depends(callable)] or name: T = Depends(callable) | Sub-dependency | | Annotated[T, Security(callable, scopes=[...])] | Security sub-dependency that contributes OAuth2 scopes | | Request, Response, BackgroundTasks, WebSocket, HTTPConnection | Special direct-injection parameters | | SecurityScopes | The OAuth2 scopes the operation declares |

The Dependant dataclass

fastapi/dependencies/models.py:Dependant holds everything inferred from a callable's signature:

@dataclass
class Dependant:
    path_params: list[ModelField] = ...
    query_params: list[ModelField] = ...
    header_params: list[ModelField] = ...
    cookie_params: list[ModelField] = ...
    body_params:  list[ModelField] = ...
    dependencies: list["Dependant"] = ...
    name: str | None = None
    call: Callable[..., Any] | None = None
    request_param_name: str | None = None
    websocket_param_name: str | None = None
    http_connection_param_name: str | None = None
    response_param_name: str | None = None
    background_tasks_param_name: str | None = None
    security_scopes_param_name: str | None = None
    own_oauth_scopes: list[str] | None = None
    parent_oauth_scopes: list[str] | None = None
    use_cache: bool = True
    path: str | None = None
    scope: Literal["function", "request"] | None = None

Plus several @cached_property accessors that the runtime resolver hits hot:

  • is_coroutine_callable, is_gen_callable, is_async_gen_callable — kind of the callable.
  • oauth_scopes — merged parent + own scopes for security purposes.
  • computed_scope — defaults to "request" if the callable is a generator, otherwise inherits from scope or None. Drives which AsyncExitStack cleanup goes on.
  • cache_key(callable, scopes_for_cache, computed_scope_str). Two Depends(...) of the same callable share results across a request when this matches and use_cache=True.
  • _is_security_schemeTrue if the callable is an instance of SecurityBase. Drives OpenAPI security generation.

Building the tree (registration time)

get_dependant(call, name, path, security_scopes) walks the callable's signature and produces a Dependant. For each parameter:

  1. Look at Annotated[...] metadata first.
  2. If a special parameter type (Request, Response, etc.), record the name on the dependant and skip.
  3. If a Depends/Security marker, recurse via get_param_sub_dependant and append to dependencies.
  4. Otherwise classify as path/query/header/cookie/body via analyze_param.

This walk happens once per route at registration time. The route stashes the dependant on APIRoute.dependant and reuses it forever. OpenAPI generation reads it; the resolver reads it.

Resolving (request time)

solve_dependencies(*, request, dependant, body, dependency_overrides_provider, async_exit_stack, embed_body_fields) is the resolver. It:

  1. Iterates dependant.dependencies and resolves each sub-dependency recursively.
  2. Calls each sub-dependency with the values it produced. For def callables it uses run_in_threadpool. For generator callables, it enters their context manager via the appropriate AsyncExitStack.
  3. Caches results on a per-request cache keyed by Dependant.cache_key.
  4. After sub-dependencies are resolved, parses the request to populate this level's path_params, query_params, header_params, cookie_params, and body_params. Validation errors are accumulated, never raised inline.
  5. Populates special parameters (request_param_name, etc.) on the values dict.
  6. Applies dependency_overrides (set in tests via app.dependency_overrides[real] = stub).
  7. Returns the values dict, errors list, dependency cache, and accumulated background tasks.

The cache is what makes a single dependency resolve once per request even when many sub-dependencies request it. Setting Depends(callable, use_cache=False) opts out — useful for "fresh per call" dependencies like a request-scoped UUID.

Yield dependencies and scope

A dependency callable that uses yield is a context manager:

def get_db():
    db = Session(...)
    try:
        yield db
    finally:
        db.close()

Dependant.computed_scope defaults to "request" for generator callables — the cleanup runs on scope["fastapi_inner_astack"], after the response is fully sent. Setting scope="function" runs cleanup on scope["fastapi_function_astack"], immediately after the endpoint returns.

DependencyScopeError is raised if a function-scoped dependency depends on a request-scoped dependency — the lifetimes would not nest.

dependency_overrides

FastAPI.dependency_overrides is a dict[Callable, Callable]. When a dependant is resolved, the resolver checks the dict and substitutes the override before calling. This is the standard pattern for unit-testing endpoints that depend on a database or external API:

app.dependency_overrides[get_db] = get_test_db

Implementation: see solve_dependencies and dependency_overrides_provider parameter.

Integration points

  • OpenAPIfastapi/openapi/utils.py reads each route's Dependant to build the parameters/body/security sections.
  • Security — every SecurityBase instance is a callable inserted via Security(...); its presence flips Dependant._is_security_scheme and causes the OpenAPI generator to emit securitySchemes.
  • RoutingAPIRoute builds its Dependant once and uses it on every request.

Common pitfalls

  • A dependency uses yield but the route never sees the cleanup run. Cause: the user manually try-suppressed the exception inside the generator. The framework now raises a verbose FastAPIError (see request_response in fastapi/routing.py) explaining what happened.
  • Two dependencies share state when they should not. Cause: same callable + use_cache=True (default). Pass use_cache=False or split the callable.
  • Security scopes do not show up in OpenAPI. Cause: scope was passed via Depends(...) instead of Security(...). Only Security carries scopes.

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

Dependency injection – FastAPI wiki | Factory