FastAPIMiddleTechnical
Как реализовать response models и почему они важны?
response_model в декораторе задаёт Pydantic-схему ответа: FastAPI фильтрует поля, предотвращает утечку внутренних данных и генерирует корректную OpenAPI-схему. Без него в ответ может попасть всё, что вернула функция.
Зачем нужны response models
Response model решает три задачи одновременно:
- Безопасность — автоматически отбрасывает поля, которых нет в схеме (например,
hashed_password). - Документация — OpenAPI получает точную схему ответа с типами и примерами.
- Контракт — ошибка несоответствия типов поймается на стадии разработки, а не в проде.
Базовый пример: разделение входных и выходных схем
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
from typing import Optional
import hashlib
app = FastAPI()
class UserCreate(BaseModel): # входящий запрос
username: str
email: EmailStr
password: str
class UserPublic(BaseModel): # исходящий ответ
id: int
username: str
email: EmailStr
class UserInDB(UserPublic): # внутреннее представление
hashed_password: str
fake_db: dict[int, UserInDB] = {}
@app.post("/users/", response_model=UserPublic, status_code=201)
async def create_user(data: UserCreate) -> UserInDB:
user = UserInDB(
id=len(fake_db) + 1,
username=data.username,
email=data.email,
hashed_password=hashlib.sha256(data.password.encode()).hexdigest(),
)
fake_db[user.id] = user
return user # FastAPI применит UserPublic, поле hashed_password исчезнет
Список ответов и Union
from typing import Union
class Cat(BaseModel):
name: str
meow: bool
class Dog(BaseModel):
name: str
bark: bool
@app.get("/pets/{pet_id}", response_model=Union[Cat, Dog])
async def get_pet(pet_id: int) -> Union[Cat, Dog]:
if pet_id % 2 == 0:
return Cat(name="Мурка", meow=True)
return Dog(name="Шарик", bark=True)
response_model_exclude_unset — убрать поля по умолчанию
class ItemUpdate(BaseModel):
name: Optional[str] = None
price: Optional[float] = None
in_stock: Optional[bool] = None
@app.patch("/items/{item_id}", response_model=ItemUpdate)
async def patch_item(
item_id: int,
item: ItemUpdate,
) -> ItemUpdate:
# вернём только те поля, которые клиент прислал
return item
# GET /items/1 с телом {"price": 9.99} вернёт {"price": 9.99}
# а не {"name": null, "price": 9.99, "in_stock": null}
Чтобы это работало, добавьте параметр декоратора: response_model_exclude_unset=True.
Несколько кодов ответа
from fastapi import HTTPException
from fastapi.responses import JSONResponse
@app.get(
"/items/{item_id}",
response_model=UserPublic,
responses={
404: {"description": "Товар не найден"},
403: {"description": "Нет доступа"},
},
)
async def get_item(item_id: int) -> UserPublic:
if item_id == 0:
raise HTTPException(status_code=404, detail="Не найдено")
...
Подводные камни
- Если функция возвращает ORM-объект (SQLAlchemy), Pydantic-модель должна иметь
model_config = ConfigDict(from_attributes=True), иначе получите ошибку сериализации. response_model=Noneотключает сериализацию полностью — FastAPI вернёт всё, что отдаст функция, без фильтрации.- При использовании
JSONResponseнапрямую response_model не применяется — данные уходят как есть. Union[ModelA, ModelB]в OpenAPI сгенерируетanyOf; некоторые клиентские генераторы (openapi-generator) плохо обрабатываютanyOf.response_model_exclude_unset=Trueиresponse_model_exclude_none=Trueне одно и то же: первое смотрит наmodel_fields_set, второе — на фактическое значениеNone.- Наследование моделей (UserPublic → UserInDB) удобно, но при рефакторинге легко случайно добавить приватное поле в базовый класс и оно просочится во все дочерние response_model.
- FastAPI не валидирует ответ в prod по умолчанию если указан
response_model_validate=False— не отключайте без причины.
Common mistakes
- Описывать response models только как термин и не показывать механизм на минимальном примере.
- Игнорировать ошибки, пустые данные, конкурентный доступ или границы транзакции.
- Не связывать поведение с официальным контрактом FastAPI и реальной эксплуатацией.
What the interviewer is testing
- Объясняет response models через последовательность действий, а не через набор ключевых слов.
- Приводит короткий кодовый пример или production-сценарий с ожидаемым поведением.
- Называет хотя бы один риск: производительность, безопасность, транзакции, память или сопровождение.