PydanticSeniorTechnical

Как Pydantic интегрируется с FastAPI для валидации запросов и ответов?

FastAPI автоматически валидирует тело запроса через Pydantic-модели, возвращает 422 при ошибках и сериализует ответы через response_model, отфильтровывая лишние поля. OpenAPI-схема генерируется из Field-аннотаций без дополнительного кода.

Как Pydantic интегрируется с FastAPI

FastAPI использует Pydantic v2 как основной механизм валидации и сериализации. Интеграция работает на трёх уровнях: тело запроса, параметры пути/запроса и тело ответа.

Валидация входящих запросов

Когда FastAPI получает HTTP-запрос, он автоматически парсит тело и передаёт данные в Pydantic-модель. При ошибке возвращается 422 Unprocessable Entity с детализированным JSON-ответом.

from fastapi import FastAPI
from pydantic import BaseModel, Field, EmailStr

app = FastAPI()

class UserCreate(BaseModel):
    name: str = Field(min_length=2, max_length=100)
    email: EmailStr
    age: int = Field(ge=18, le=120)

class UserResponse(BaseModel):
    id: int
    name: str
    email: str

    model_config = {"from_attributes": True}

@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(body: UserCreate):
    # body уже провалидирован Pydantic
    # body.email — нормализованный lowercase адрес
    user = await db.create_user(body.name, body.email, body.age)
    return user  # SQLAlchemy ORM-объект → Pydantic через from_attributes

Параметры пути и строки запроса

Pydantic валидирует и приводит типы для path/query параметров. FastAPI использует Query(), Path() с теми же Pydantic-ограничениями.

from fastapi import Query, Path

@app.get("/users/{user_id}")
async def get_user(
    user_id: int = Path(ge=1),
    include_deleted: bool = Query(default=False),
    page: int = Query(default=1, ge=1),
    size: int = Query(default=20, ge=1, le=100),
):
    ...

Сериализация ответов через response_model

Параметр response_model указывает FastAPI прогнать возвращаемый объект через Pydantic перед отправкой клиенту. Это фильтрует лишние поля (например, password_hash) и приводит типы к JSON-совместимым.

class UserPublic(BaseModel):
    id: int
    name: str
    # email и age не включены — они не попадут в ответ

@app.get("/users/{user_id}", response_model=UserPublic)
async def get_user(user_id: int):
    return await db.get_user(user_id)  # объект может содержать email, но в ответ не уйдёт

OpenAPI-схема генерируется автоматически

FastAPI читает аннотации Pydantic-моделей и строит OpenAPI-схему (/docs, /openapi.json) без дополнительного кода. Field(description=..., example=...) появляется в Swagger UI.

Обработка ошибок валидации

from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    return JSONResponse(
        status_code=422,
        content={"detail": exc.errors(), "body": exc.body},
    )

Подводные камни

  • from_attributes не включён по умолчанию — если вернуть ORM-объект без model_config = {"from_attributes": True}, Pydantic выбросит ValidationError вместо сериализации атрибутов объекта.
  • Ленивые отношения SQLAlchemy — при from_attributes=True Pydantic попытается обратиться к lazy-loaded relationship уже за пределами сессии, что даёт MissingGreenlet или DetachedInstanceError. Решение: selectinload/joinedload или явный model_validate(obj, from_attributes=True) внутри сессии.
  • response_model_exclude_unset — по умолчанию в ответ включаются все поля модели, даже если они не заданы. Используйте response_model_exclude_unset=True для PATCH-эндпоинтов, чтобы не перезаписывать поля значениями по умолчанию.
  • Вложенные модели и глубокая валидация — FastAPI валидирует рекурсивно, поэтому большие вложенные структуры могут давать длинные трассировки ошибок; нужно явно указывать понятные Field(description=...).
  • Pydantic v1 vs v2 — FastAPI ≥0.100 требует Pydantic v2. Если в проекте есть legacy-код с orm_mode = True, нужно мигрировать на from_attributes = True в model_config.
  • Circular imports — вынос Pydantic-моделей в отдельный модуль schemas.py предотвращает циклические импорты между роутерами и моделями.
  • Производительность при больших спискахresponse_model=list[UserResponse] для 10 000 объектов даёт заметный оверхед валидации. Альтернатива: response_model_include или прямой JSONResponse с model.model_dump().

Common mistakes

  • Описывать fastapi integration только как термин и не показывать механизм на минимальном примере.
  • Игнорировать ошибки, пустые данные, конкурентный доступ или границы транзакции.
  • Не связывать поведение с официальным контрактом Pydantic и реальной эксплуатацией.

What the interviewer is testing

  • Объясняет fastapi integration через последовательность действий, а не через набор ключевых слов.
  • Приводит короткий кодовый пример или production-сценарий с ожидаемым поведением.
  • Называет хотя бы один риск: производительность, безопасность, транзакции, память или сопровождение.
  • Умеет обсудить отказ, наблюдаемость и rollback без изменения публичного контракта.

Sources

Related topics