Skip to main content

Parameter Metadata and Extraction

FastAPI uses a sophisticated dependency resolution system to map incoming request data—such as query parameters, headers, and cookies—into function arguments. This process relies on two primary internal structures: ParamDetails for initial parameter analysis and Dependant for representing the complete tree of requirements for an endpoint.

Map Request Data to Dependencies​

To map request data to a dependency or endpoint, you typically use Annotated with FastAPI's parameter classes (Query, Header, Cookie, Path). Internally, FastAPI uses get_dependant to inspect these annotations and build a Dependant object.

from typing import Annotated
from fastapi import FastAPI, Header, Query
from pydantic import BaseModel

app = FastAPI()

class CommonHeaders(BaseModel):
user_agent: str | None = None
host: str | None = None

@app.get("/items/")
async def read_items(
# Individual query parameter
q: Annotated[str | None, Query(max_length=50)] = None,
# Grouped headers using a Pydantic model
headers: Annotated[CommonHeaders, Header()] = Header(),
):
return {"q": q, "headers": headers}

How it Works Internally​

  1. Analysis: When the application starts, get_dependant (in fastapi/dependencies/utils.py) iterates through the parameters of your route handler.
  2. Parameter Inspection: For each parameter, it calls analyze_param. This function returns a ParamDetails object (defined in fastapi/dependencies/utils.py), which captures:
    • type_annotation: The Python type of the parameter.
    • depends: Any Depends object associated with the parameter.
    • field: A ModelField (from fastapi._compat) if the parameter represents request data (Query, Header, etc.).
  3. Model Building: The Dependant object (defined in fastapi/dependencies/models.py) is populated with these ModelField objects into specific lists: query_params, header_params, cookie_params, etc.
  4. Resolution: At runtime, solve_dependencies is called. It uses request_params_to_args to extract values from the Starlette Request object based on the metadata stored in the Dependant.

Grouping Parameters with Pydantic Models​

FastAPI supports "flattening" Pydantic models when they are used as parameter containers. This allows you to group multiple query parameters or headers into a single object.

from typing import Annotated
from fastapi import FastAPI, Query
from pydantic import BaseModel, Field

app = FastAPI()

class FilterParams(BaseModel):
limit: int = Field(100, gt=0, le=100)
offset: int = Field(0, ge=0)
order_by: str = "created_at"

@app.get("/items/")
async def read_items(filter_query: Annotated[FilterParams, Query()]):
# FastAPI flattens FilterParams and looks for 'limit', 'offset', and 'order_by'
# in the request query string.
return filter_query

When request_params_to_args encounters a ModelField whose annotation is a BaseModel, it uses get_cached_model_fields to extract the individual fields of that model and map them to the incoming request parameters.

Handling Headers and Underscores​

By default, FastAPI automatically converts underscores to hyphens for Header parameters. For example, a field named user_agent in a Pydantic model used with Header() will look for the User-Agent HTTP header.

from typing import Annotated
from fastapi import FastAPI, Header
from pydantic import BaseModel

app = FastAPI()

class CustomHeaders(BaseModel):
# This will look for the 'X-Custom-Token' header
x_custom_token: str
# This will look for 'user_agent' literally because conversion is disabled
user_agent: Annotated[str, Header(convert_underscores=False)]

@app.get("/")
async def root(headers: Annotated[CustomHeaders, Header()]):
return headers

This logic is implemented in analyze_param and request_params_to_args within fastapi/dependencies/utils.py, where it checks the convert_underscores attribute of the FieldInfo.

Troubleshooting​

Path Parameter Default Values​

Path parameters cannot have default values in the function signature. If you attempt to provide one, FastAPI will raise an assertion error during the analyze_param phase.

# This will cause an error during startup
@app.get("/items/{item_id}")
async def read_item(item_id: str = "default_id"):
...

Annotated Default Values​

When using Annotated, the default value for the parameter must be set using the = operator, not inside the Query() or Header() call.

# CORRECT
async def read_items(q: Annotated[str, Query()] = "default"): ...

# INCORRECT - will raise an AssertionError in analyze_param
async def read_items(q: Annotated[str, Query(default="default")]): ...

FastAPI enforces this in analyze_param to ensure consistency between the type system and the runtime default values.

Scope Restrictions​

Dependencies can have different scopes (function or request). A dependency with request scope (like one that uses yield) cannot depend on a dependency with function scope. This is validated in get_dependant and will raise a DependencyScopeError.

from fastapi import Depends

def func_scope_dep():
return "data"

async def req_scope_dep(data: str = Depends(func_scope_dep)):
yield data

# This will raise DependencyScopeError because req_scope_dep
# (request scope) depends on func_scope_dep (function scope)