This rule raises an issue when FastAPI route handler parameters use Depends(), Query(), Path(),
Body(), or similar dependency injection functions as default parameter values instead of within Annotated type hints.
FastAPI originally allowed declaring dependencies by using them as default parameter values. While this approach still works, it has several drawbacks that make code harder to maintain and understand.
When dependencies are declared as default values, the actual type of the parameter becomes ambiguous. For example, db =
Depends(get_db) doesn’t clearly indicate what type db will be at runtime. This makes the code harder to read and reduces the
effectiveness of IDE autocompletion and type checking tools.
The modern Annotated syntax separates the type information from the dependency declaration, making both aspects explicit. This
approach provides better clarity about what the parameter actually is, while still specifying how it should be injected.
Using Annotated consistently across your codebase also aligns with FastAPI’s current recommendations and ensures your code follows
modern Python typing conventions introduced in Python 3.9.
The old syntax mixes concerns by using the default value mechanism for dependency injection, which is semantically confusing since the "default" isn’t really a default value in the traditional sense.
Using the deprecated syntax makes code less maintainable in several ways:
Replace dependency declarations in default values with Annotated type hints. Import Annotated from the
typing module (Python 3.9+) or typing_extensions (Python 3.8 and earlier). The first argument to Annotated is
the actual type of the parameter, and the second argument is the dependency injection specification.
from fastapi import Depends, FastAPI
app = FastAPI()
def get_db():
return "database_connection"
@app.get("/items/")
def read_items(db = Depends(get_db)): # Noncompliant
return {"db": db}
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
def get_db():
return "database_connection"
@app.get("/items/")
def read_items(db: Annotated[str, Depends(get_db)]):
return {"db": db}
The same pattern applies to query parameters, path parameters, and request body parameters. Use Annotated to combine the type with the
parameter specification.
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/search/")
def search_items(
q: str = Query(None, max_length=50) # Noncompliant
):
return {"query": q}
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/search/")
def search_items(
q: Annotated[str | None, Query(max_length=50)] = None
):
return {"query": q}
For complex dependencies that return specific types, specify the return type explicitly in the Annotated declaration to improve type
safety.
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0):
return {"q": q, "skip": skip}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)): # Noncompliant
return commons
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0):
return {"q": q, "skip": skip}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons