Модуль 12 · Урок 45 із 58

Pydantic у FastAPI: request і response models

Type hint стає API boundary лише тоді, коли команда усвідомлює coercion, constraints, unknown fields і різницю між input, domain та public output. Один ORM object не повинен автоматично бути і request body, і database model, і response.

Pydantic v2ValidationSchemasOutput

Безпечна FastAPI-практика модуля 12

Пакет містить synthetic SprintBoard API, Pydantic v2 models, dependencies, Problem Details, middleware, TestClient і OpenAPI contract tests. Він не відкриває зовнішню мережу під час tests, не містить authentication substitutes, персональних чи production-даних, credentials або production endpoints.

Завантажити практичний пакет →

Model гарантує форму результату validation

Pydantic BaseModel читає недовірені input values, перетворює або відхиляє їх і створює model instance з оголошеними types та constraints. Він гарантує output після validation, а не те, що raw input уже мав потрібний Python type.

from pydantic import BaseModel, ConfigDict, Field

class TaskCreate(BaseModel):
    model_config = ConfigDict(extra="forbid")

    title: str = Field(min_length=2, max_length=100)
    priority: int = Field(ge=1, le=5)
    labels: list[str] = Field(default_factory=list, max_length=10)

default_factory створює окрему collection для кожного instance. extra="forbid" робить typo або неочікуване field явним failure замість тихого ігнорування.

Coercion має бути свідомою

За замовчуванням Pydantic може перетворити сумісні values, наприклад numeric string на integer. Це зручно для query parameters, але ризиковано для identifiers, money або security-sensitive flags. Для таких fields застосовують strict types або strict=True і тестують JSON та Python validation modes.

RequiredНемає default — field обов’язкове.
OptionalType дозволяє None, але default визначає requiredness.
ConstraintLength, range, pattern або domain validator.
ExtraIgnore, allow чи forbid — explicit policy.

Input і output потребують різних models

TaskCreate описує те, що client може надіслати. TaskPublic описує те, що client може отримати. Internal fields на кшталт password hash, owner note, cost або moderation flag не входять до public output.

class TaskPublic(BaseModel):
    id: int
    title: str
    priority: int
    status: str

@app.post(
    "/tasks",
    response_model=TaskPublic,
    status_code=201,
)
async def create_task(payload: TaskCreate):
    return repository.create(payload.model_dump())

FastAPI використовує response model для OpenAPI, serialization, validation та filtering. Якщо application повертає missing required field, це server bug; якщо повертає зайве internal field, response filtering не випустить його за заявлений contract.

Nested contract і serialization перевіряються окремо

Nested models дозволяють виразити structured data, але payload depth, list size та string length мають bounded policy. model_dump() повертає Python structures; model_dump_json() серіалізує JSON. Alias, exclude і computed field змінюють wire representation, тому покриваються tests.

Validation не є authorization

Валідний owner_id не доводить, що caller має право діяти від імені owner. Authentication та authorization — окрема trust boundary наступного модуля.

Error response не повинен віддзеркалювати secrets

Default validation error корисний, але custom handler не має повертати весь raw body, headers або exception string без review. Public issues можуть містити safe location і stable error type; raw payload лишається поза response та проходить redaction у захищеній telemetry.

  1. Розділіть create, update, public і internal models.
  2. Визначте coercion та extra-field policy.
  3. Bounded constraints додайте на transport boundary.
  4. Перевірте positive, missing, wrong type, boundary й extra cases.
  5. Окремо доведіть, що output не містить internal fields.

Методичні джерела

Урок, сценарії, пояснення й вправи створені SEOWORK. Посилання ведуть на офіційну документацію framework та стандартів.

Практична перевірка · урок 45 з 47

Закріпіть матеріал уроку

Три сценарні питання. Для зарахування уроку потрібно дати щонайменше дві правильні відповіді.

1. Pydantic model отримує id='123' і після validation має integer 123. Що саме гарантує Pydantic?
2. Client помилився і надіслав priorty замість priority. Яка model configuration робить typo явним failure?
3. Field status має type str | None, але не має default. Чи є воно optional у сенсі присутності request field?