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
- Analysis: When the application starts,
get_dependant(infastapi/dependencies/utils.py) iterates through the parameters of your route handler. - Parameter Inspection: For each parameter, it calls
analyze_param. This function returns aParamDetailsobject (defined infastapi/dependencies/utils.py), which captures:type_annotation: The Python type of the parameter.depends: AnyDependsobject associated with the parameter.field: AModelField(fromfastapi._compat) if the parameter represents request data (Query, Header, etc.).
- Model Building: The
Dependantobject (defined infastapi/dependencies/models.py) is populated with theseModelFieldobjects into specific lists:query_params,header_params,cookie_params, etc. - Resolution: At runtime,
solve_dependenciesis called. It usesrequest_params_to_argsto extract values from the StarletteRequestobject based on the metadata stored in theDependant.
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)