JavaRush /Курси /Модуль 4: FastAPI /Валідація відповідей API через Pydantic

Валідація відповідей API через Pydantic

Модуль 4: FastAPI
Рівень 3 , Лекція 6
Відкрита

Сьогодні поговоримо про те, як ми можемо використовувати Pydantic не тільки для валідації на вході (в запитах), а й на виході — для того, що ваше API відправляє користувачу у відповіді.

Ця тема не менш важлива, ніж валідація вхідних даних. Якщо API повертає несподівані або некоректні дані, це може призвести до поламаного інтерфейсу, зриву автоматизації, роздратованих коментарів від колег — і, якщо вже на те пішло, до втрати довіри до вашого API. З таким ви, звісно, стикатися не хочете.

Уявіть, що ви побудували складну систему, де ваш API взаємодіє з фронтендом або іншими сервісами. Що станеться, якщо ви випадково повернете об'єкт не в тому форматі, який очікують ваші клієнти? Наприклад:

  1. Фронтенд може перестати відображати дані через неправильний формат.
  2. Партнери, які використовують ваше API, можуть почати бити на сполох (і розривати контракти).
  3. Помилки складніше відлагоджувати (особливо вночі, коли єдина підтримка — ваша кружка кави).
Важливо пам'ятати:

коректний та очікуваний формат відповіді — це обличчя вашого API. Саме тому валідація відповідей така важлива.


Як FastAPI допомагає з валідацією вихідних даних?

FastAPI інтегрований з Pydantic настільки тісно, що готовий автоматично валідовувати дані, які повертаються з вашого енедпоінта. Щоб увімкнути цю магію, достатньо вказати, якою Pydantic-моделлю має бути представлена відповідь.

Створимо перший приклад.

Давайте почнемо з простого API, яке повертає інформацію про користувача:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

# Визначаємо Pydantic-модель
class UserResponse(BaseModel):
    id: int
    name: str
    email: str

@app.get("/user/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
    # Дані з бази даних (або іншого джерела)
    user = {"id": user_id, "name": "Іван Іванов", "email": "ivan@example.com"}
    return user

Що відбувається в цьому коді?

  1. Ми створили модель UserResponse, яка описує, як мають виглядати повернені дані: id (ціле число), name (рядок) і email (рядок).
  2. В ендпоінті /user/{user_id} ми вказали response_model=UserResponse. Це каже FastAPI: "Усі дані, які повертаються з цього ендпоінта, мають відповідати моделі UserResponse".
  3. FastAPI автоматично перевірить дані перед тим, як відправити їх користувачу.

Перевірка валідації

Що буде, якщо ми повернемо дані, які не відповідають моделі? Наприклад, видалимо поле email:

@app.get("/user/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
    user = {"id": user_id, "name": "Іван Іванов"}  # Email відсутній
    return user

FastAPI викине помилку ValueError, тому що поле email є обов'язковим, але його немає в повернених даних.


Перетворення даних через Pydantic

Pydantic не тільки валідовує, але й автоматично перетворює дані. Наприклад, якщо ви повернете рядок для поля id, хоча воно очікує ціле число, Pydantic постарається "виправити" це.

Приклад перетворення

@app.get("/user/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
    user = {"id": str(user_id), "name": "Іван Іванов", "email": "ivan@example.com"}  # id як рядок
    return user

Незважаючи на те, що id тут представлено як рядок, Pydantic перетворить його в ціле число, тому що модель очікує int. Це зручно, коли дані з різних джерел мають різні типи.


Часткова валідація (поле з типом Any)

Іноді структура відповіді може бути складною або динамічною, і ви не хочете описувати всі поля моделі. Для таких випадків є поле з типом Any.

from typing import Any

class FlexibleResponse(BaseModel):
    id: int    # Важливе поле типу int
    data: Any  # А тут може бути що завгодно

Поля типу Any пропустять будь-які дані без додаткової перевірки.


Вкладені моделі у відповідях

Іноді в відповідях можуть бути складні вкладені структури даних. Наприклад, користувач може мати список ролей, кожна з яких описана своєю моделлю.

Приклад вкладеної моделі

from typing import List

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

class UserResponseWithRoles(BaseModel):
    id: int
    name: str
    email: str
    roles: List[Role]  # Список ролей

@app.get("/user/{user_id}/roles", response_model=UserResponseWithRoles)
async def get_user_with_roles(user_id: int):
    user = {
        "id": user_id,
        "name": "Іван Іванов",
        "email": "ivan@example.com",
        "roles": [{"id": 1, "name": "Адміністратор"}, {"id": 2, "name": "Користувач"}],
    }
    return user

FastAPI перевірить не тільки структуру основного об'єкта, а й кожен елемент списку roles. Якщо хоч одна роль виявиться некоректною, вся відповідь буде визнана недійсною.


Додаткові параметри: response_model_exclude і response_model_include

Іноді ви не хочете повертати всі поля моделі (наприклад, виключити конфіденційну інформацію). Для цього є параметри response_model_exclude і response_model_include.

Приклад з виключенням полів

@app.get("/user/{user_id}", response_model=UserResponse, response_model_exclude={"email"})
async def get_user_without_email(user_id: int):
    user = {"id": user_id, "name": "Іван Іванов", "email": "ivan@example.com"}
    return user

У цьому випадку поле email не буде включено у відповідь.


Налагодження помилок валідації (копаємо глибше!)

Якщо дані, які повертає ваш енедпоінт, не проходять валідацію, FastAPI покаже детальне повідомлення про помилку. Наприклад:

Помилка може виглядати так:

{
  "detail": [
    {
      "loc": ["response", "email"],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}

У цьому прикладі FastAPI каже, що в повернених даних відсутнє поле email. Це допоможе швидко знайти й виправити проблему.


Практичне завдання

  1. Створіть ендпоінт /product/{product_id}, який повертає опис продукту.
  2. Використайте вкладені моделі: продукт має містити поля id, name і attributes, де attributes — складний об'єкт з полями color, size і in_stock (булеве значення).
  3. Напишіть API, яке повертає список продуктів (використайте список List).

Тепер ви озброєні знаннями, щоб ваші відповіді були такими ж ідеальними, як архітектура FastAPI! Не забувайте, що коректні відповіді — це запорука довіри, успішної інтеграції і, звісно, вашого спокою. Удачі і менше багів!

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ