Тема 3 из 6

Модели Pydantic

Данные для создания и изменения записей клиент присылает в теле запроса, в JSON. Чтобы проверить такой JSON, в FastAPI описывают модели на библиотеке Pydantic.

Модель

from pydantic import BaseModel, Field


class TodoIn(BaseModel):
    title: str = Field(min_length=1, max_length=100)
    priority: int = Field(3, ge=1, le=5)    # 3 — значение по умолчанию
    done: bool = False

Модель — класс с полями и типами, похожий на dataclass. Pydantic проверяет каждое поле: тип, ограничения Field(...), обязательность (поле без значения по умолчанию обязательно).

Тело запроса

@app.post("/todos", status_code=201)
def create(todo: TodoIn):
    item = {"id": next_id(), **todo.model_dump()}
    TODOS[item["id"]] = item
    return item
  • параметр-модель FastAPI читает из JSON в теле запроса;
  • если JSON не подходит (нет title, priority = 9, строка вместо числа), клиент получит 422 с описанием ошибок — ваша функция даже не вызовется;
  • todo.title, todo.priority — поля уже нужного типа; todo.model_dump() — словарь;
  • status_code=201 в декораторе — код успешного ответа (201 Created для создания).

Модель ответа

class UserOut(BaseModel):
    id: int
    name: str


@app.get("/users/{uid}", response_model=UserOut)
def get_user(uid: int):
    return USERS[uid]           # в словаре есть и password_hash

response_model проверяет ответ и оставляет только поля модели. Так в ответ не попадут внутренние данные: хеш пароля, служебные поля. Её же FastAPI показывает в документации /docs как схему ответа.

Часто делают несколько моделей одной сущности: TodoIn — что присылает клиент, TodoOut — что он получает (с id), TodoPatch — для изменения.

Частичное обновление

class TodoPatch(BaseModel):
    title: str | None = Field(None, min_length=1, max_length=100)
    done: bool | None = None


@app.patch("/todos/{tid}")
def update(tid: int, patch: TodoPatch):
    TODOS[tid].update(patch.model_dump(exclude_unset=True))
    return TODOS[tid]

Метод PATCH меняет только присланные поля. model_dump() вернул бы все поля, и неприсланные стали бы None. exclude_unset=True оставляет только поля, которые клиент действительно прислал.

Итог

  • BaseModel, типы и Field(min_length, max_length, ge, le);
  • параметр-модель — тело запроса, неверные данные — 422 автоматически;
  • status_code=201, response_model прячет лишние поля;
  • PATCH и model_dump(exclude_unset=True).