This is an issue when a FastAPI route decorator includes a response_model parameter that duplicates the function’s return type
annotation.
FastAPI automatically infers the response model from a function’s return type annotation. When you specify the same type in both the return
annotation and the response_model parameter, you create unnecessary duplication.
This duplication has several drawbacks:
FastAPI’s automatic inference from return type annotations is the recommended approach. The response_model parameter exists for
special cases where you need to override the default behavior, such as when returning a different model internally than what should be serialized in
the response.
This issue affects code maintainability and clarity. While it doesn’t cause runtime errors or security vulnerabilities, it makes the codebase harder to maintain and understand. Developers may waste time keeping redundant specifications synchronized or debugging issues caused by mismatches between the two declarations.
Remove the response_model parameter from the route decorator and rely solely on the function’s return type annotation. FastAPI will
automatically use the return type annotation to generate the response schema and validate the response.
@app.post("/items/", response_model=Item) # Noncompliant
async def create_item(item: Item) -> Item:
return item
@app.post("/items/")
async def create_item(item: Item) -> Item:
return item
For GET endpoints, the same principle applies. Remove the redundant response_model parameter and let FastAPI infer the response type
from the return annotation.
@app.get("/users/{user_id}", response_model=User) # Noncompliant
def get_user(user_id: int) -> User:
return fetch_user(user_id)
@app.get("/users/{user_id}")
def get_user(user_id: int) -> User:
return fetch_user(user_id)
When you need to return a different type internally than what should be serialized (a legitimate use case for response_model), keep
the response_model parameter. This example shows when response_model is appropriate because the internal return type
includes sensitive data that shouldn’t be exposed.
@app.get("/users/{user_id}", response_model=UserPublic)
def get_user(user_id: int) -> UserPublic: # Noncompliant: types match
user = fetch_user_with_password(user_id)
return UserPublic(**user.dict())
@app.get("/users/{user_id}", response_model=UserPublic)
def get_user(user_id: int) -> UserInternal:
# Returns UserInternal but serializes as UserPublic
return fetch_user_with_password(user_id)