Модуль 11

Response models і контроль відповіді API

Ось твій урок у стилі CS50. Вмикаймо уяву, відкриваймо IDE, і поїхали! 🚀


🎓 Тема: Response Models і контроль відповіді API (або «Не показуй зайвого!»)

Привіт, друзі! Ласкаво просимо на черговий етап нашої подорожі у світ бекенду.

1. 🔥 Вступ: проблема та мотивація

Уявіть, що ви — власник елітного ресторану. 🍽️ Клієнт замовляє вашу фірмову страву. На кухні відбувається хаос: брудний посуд, лушпиння від картоплі, чеки від постачальників, секретні нотатки шеф-кухаря з рецептом соусу.

Питання до вас: Коли офіціант виносить страву клієнту, чи виносить він разом з нею сміттєве відро і той самий секретний рецепт? Звісно, ні! Клієнт отримує лише красиву, чисту тарілку з їжею. Нічого зайвого.

А тепер погляньмо на код. Ви створюєте API для користувачів. У вашій базі даних є об'єкт User, який містить: * id * username * email * hashed_password (хеш пароля!) * created_at * secret_internal_rating

Якщо ви просто повернете цей об'єкт "як є" на запит фронтенду... Що станеться? Ви віддасте клієнту (і потенційному хакеру) хеш пароля та внутрішні дані. Це катастрофа безпеки! 🚨

Сьогодні ми навчимося бути тим самим ідеальним офіціантом. Ми розберемо Response Models — механізм, який дозволяє чітко контролювати, що саме виходить з вашого API назовні, незалежно від того, що відбувається "на кухні" (у базі даних).


2. 🧠 Теоретична база (без сухої академічності)

Отже, що таке Response Model?

Response Model (Модель відповіді) — це схема-фільтр, яка визначає форму даних, що будуть відправлені клієнту. Вона бере ваші "сирі" дані, валідує їх і відсікає все зайве.

Найчастіше в сучасному Python (наприклад, у FastAPI) для цього використовується бібліотека Pydantic.

Як це працює «під капотом»? ⚙️

Уявіть це як сито або трафарет. 1. Ваша функція дістає з бази даних "товстий" об'єкт з купою полів. 2. Фреймворк бере вашу Response Model (ваш "трафарет"). 3. Він прикладає трафарет до об'єкта. 4. Все, що не влізає у трафарет — ігнорується. 5. Все, що влізає — перетворюється у JSON і летить до клієнта.

Що треба запам'ятати:

  • Input ≠ Output. Модель для входу (створення юзера) і модель для виходу (показ юзера) — це різні речі. Ніколи не лінуйтеся створювати окремі моделі.
  • Безпека. Це ваш останній рубіж захисту від витоку даних.
  • Документація. Response Model автоматично генерує красиву документацію (Swagger/OpenAPI), щоб фронтендери знали, що їм прийде.

3. 🧪 Приклади (від простого до реального)

Давайте писати код. Уявімо, що ми використовуємо FastAPI та Pydantic, оскільки це золотий стандарт сьогодення.

Приклад 1: "Товста" база даних

Спочатку подивимось на наш умовний об'єкт із бази даних (словник або об'єкт ORM):

# Наша "кухня" - дані з БД
fake_user_db = {
    "id": 1,
    "username": "david_malan",
    "email": "malan@harvard.edu",
    "password_hash": "super_secret_hash_123",  # 😱 ЦЕ НЕ МОЖНА ПОКАЗУВАТИ!
    "is_admin": True
}

Приклад 2: Рішення "В лоб" (Неправильне)

@app.get("/user")
def get_user():
    return fake_user_db

Питання: Що отримає клієнт? Відповідь: Він отримає все, включаючи password_hash. Це провал. ❌

Приклад 3: Використання Response Model (Правильне) ✅

Створимо модель-фільтр. Ми хочемо показати тільки username та email.

from pydantic import BaseModel

# Визначаємо, як має виглядати наша "чиста тарілка"
class UserResponse(BaseModel):
    username: str
    email: str

@app.get("/user", response_model=UserResponse) # <--- Магія тут!
def get_user():
    return fake_user_db

Чому це круто? Ми повернули fake_user_db (де є пароль!), але FastAPI подивився на response_model=UserResponse, побачив, що там немає поля password_hash, і автоматично його видалив перед відправкою.

Клієнт отримає лише:

{
  "username": "david_malan",
  "email": "malan@harvard.edu"
}

Чисто, безпечно, елегантно. 😎


4. 🛠 Практична частина

А тепер ваша черга! Відкрийте редактор (або уявіть його).

🔹 Завдання 1: Базовий фільтр

У вас є товар: product = {"id": 55, "name": "Phone", "cost_price": 500, "selling_price": 1000}. Створіть Pydantic-модель ProductPublic, яка приховає cost_price (собівартість), щоб конкуренти не дізналися вашу маржу.

🔹 Завдання 2: Аліаси (Rename)

У базі даних поле називається selling_price, але фронтендери просять, щоб у JSON приходило просто price. Підказка: Не міняйте базу! Змініть тільки модель. Дослідіть, як у Pydantic просто назвати змінну в моделі price, а значення взяти з selling_price (або просто присвоїти при поверненні).

🔹 Завдання 3: Обчислювані поля

У базі даних є first_name та last_name. Клієнт хоче одне поле full_name. Напишіть модель, яка приймає ім'я та прізвище, а віддає їх об'єднаними. (Тут можна використати Pydantic field_validator або просто сформувати словник перед поверненням).

🔹 Завдання 4: Реальний кейс (User Profile)

Створіть дві моделі для одного й того самого юзера: 1. UserProfilePrivate (бачить сам власник): включає email, телефон, історію замовлень. 2. UserProfilePublic (бачать інші люди): тільки ім'я та аватарка.

Напишіть псевдо-код двох ендпоінтів (/me та /users/{id}), які використовують ці різні моделі для одного джерела даних.

🔹 Завдання 5: Питання "А що, якщо..."

Що станеться, якщо в response_model ви вказали поле age: int, а в базі даних (у словнику, який ви повертаєте) цього ключа взагалі немає? * А) API поверне null? * Б) API впаде з помилкою 500 Internal Server Error? * В) Pydantic придумає число сам? (Поміркуйте і перевірте).


5. 💡 Мислення як у розробника

Як відрізнити новачка від профі в цій темі?

❌ Типова помилка новачка:

Використовувати одну модель User для всього: і для реєстрації (вхідні дані), і для логіну, і для профілю, і для адмінки. Наслідок: Ви хочете додати поле confirm_password для реєстрації, і воно раптово починає "стирчати" у відповіді профілю. Це "спагетті-моделі".

✅ Як думає профі:

"Контракт — понад усе". Спочатку я домовляюся з фронтендом/мобільним додатком, що саме їм потрібно отримати. Я пишу Response Model. І тільки потім я думаю, як дістати ці дані з бази. Це називається API-First Design.

💡 Порада з практики:

Якщо ви змінюєте структуру бази даних (наприклад, перейменували колонку), ваша Response Model захищає API від поломки. Ви просто міняєте логіку всередині функції, а модель залишається старою. Клієнти навіть не помітять, що ви переробили всю базу. Це називається Desoupling (розчеплення).


6. 🧩 Підсумок

Отже, друзі, що ми маємо у сухому залишку?

  1. Ми ніколи не віддаємо об'єкти з бази напряму. Це негігієнічно і небезпечно.
  2. Response Model — це наш офіціант, який перевіряє тарілку перед подачею.
  3. Ми використовуємо різні моделі для різних ситуацій (публічні дані vs приватні).

Тепер ви вмієте: Контролювати дані, захищати секрети та будувати стабільні API, які не ламаються від кожного чиху в базі даних.

👉 Наступного разу: Тепер, коли ми знаємо, як віддавати дані, поговоримо про те, як їх приймати і перевіряти. Готуйтеся, наступна тема: Валідація вхідних даних: чому не можна вірити користувачам.

Це був CS50... тобто, наш урок! 😉 Кодьте з розумом! 👨‍💻👩‍💻