API schemas that forbid extra fields¶
The request and response models of a public API set model_config = ConfigDict(extra="forbid")
and live under api/<version>/schemas/requests/ and api/<version>/schemas/responses/.
What to read¶
CreateUserRequestis an inbound model. A payload with a key that the model does not declare fails validation with a 422.UserResponseis an outbound model with the same setting. FastAPI already limits the output to the fields of the response model, so on a responseforbiddoes something else: a handler that returns adictwith a key the model does not declare fails with a 500, andmodel_validate(row, from_attributes=True)ignores the setting and drops the extra attributes.- The router of
api/<version>/routers/imports the schemas and defines none. It builds the response with the constructor, one field at a time.
What to adapt¶
CreateUserRequestandUserResponseare one example. Setmodel_configon every model of the schema modules of the API.- The models of the domain and the models internal to a service do not take the setting.
- A field typed
EmailStrneeds theemailextra of pydantic (pydantic[email]); the example usesstrwith a length.
References¶
code/app/api/v1/schemas/requests/users.py¶
an inbound model: extra=forbid rejects a key that the model does not declare with a 422
"""The request schemas of the endpoints of users."""
from __future__ import annotations
from pydantic import BaseModel, ConfigDict, Field
class CreateUserRequest(BaseModel):
"""The payload that creates a user. A key that the model does not declare is a 422."""
model_config = ConfigDict(extra="forbid")
email: str = Field(min_length=3, max_length=254)
display_name: str = Field(min_length=1, max_length=80)
code/app/api/v1/schemas/responses/users.py¶
an outbound model with the same setting
"""The response schemas of the endpoints of users."""
from __future__ import annotations
from pydantic import BaseModel, ConfigDict
class UserResponse(BaseModel):
"""A user as the API returns it."""
model_config = ConfigDict(extra="forbid")
id: int
email: str
display_name: str
code/app/api/v1/routers/users.py¶
the router imports the schemas and builds the response field by field
"""The endpoints of users."""
from itertools import count
from fastapi import APIRouter, status
from app.api.v1.schemas.requests.users import CreateUserRequest
from app.api.v1.schemas.responses.users import UserResponse
router = APIRouter(prefix="/users", tags=["users"])
_ids = count(1)
@router.post("", status_code=status.HTTP_201_CREATED)
def create_user(payload: CreateUserRequest) -> UserResponse:
"""Create a user and return it, built field by field from what was stored."""
user_id = next(_ids)
return UserResponse(id=user_id, email=payload.email, display_name=payload.display_name)
https://fastapi.tiangolo.com/tutorial/response-model/#return-type-and-data-filtering¶
FastAPI already limits the output to the fields of the response model
Cited by¶
api-schemas-extra-forbid(BTFAST001)- CES-4