Модуль 10

Валідація даних з Pydantic

Ось готовий урок, створений за твоїм майстер-промптом.


🎓 УРОК: Валідація даних з Pydantic

Привіт, друзі! Радий бачити вас на цьому уроці. Сьогодні ми поговоримо про те, що відокремлює надійний, професійний код від "скрипта, який я написав за 5 хвилин і який падає щоразу, коли користувач натискає не ту кнопку".

Ми розберемо Pydantic.


1. 🔥 Вступ: Фейс-контроль для вашої програми

Уявіть, що ви власник елітного нічного клубу (це ваша програма). Всередині — вечірка: працює складна логіка, бази даних записують транзакції, процесори обробляють запити.

А тепер уявіть, що на вході немає охорону.

Що станеться? Хтось зайде з брудом на взутті (невірний формат даних). Хтось принесе зброю (SQL-ін'єкції або шкідливі скрипти). А хтось взагалі не людина, а ведмідь на велосипеді (об'єкт зовсім іншого типу).

Ваша вечірка закінчиться катастрофою. Програма впаде з помилкою TypeError або KeyError десь глибоко в надрах коду, і ви будете годинами шукати, де саме це сталося.

Риторичне питання: Ви б хотіли перевіряти паспорт і дрес-код на вході, чи розбиратися з проблемами вже на танцполі?

Звісно, на вході! Ось тут і вступає в гру Pydantic.

Це ваш ідеальний охоронець. Ви даєте йому інструкцію (схему), і він каже кожному шматочку даних: "Ти проходиш, ти — ні, а ти — вдягни краватку (конвертуйся в правильний тип), і тоді заходь".


2. 🧠 Теоретична база: Як це працює?

Pydantic — це бібліотека для Python, яка використовує Type Hints (підказки типів), щоб перевіряти дані.

Але тут є важливий нюанс. Запам’ятайте це:

💡 Pydantic — це не просто валідатор, це парсер.

Що це означає? Якщо ваша функція очікує ціле число (int), а ви передаєте рядок "42", звичайний валідатор скаже: "Помилка! Це рядок!". Pydantic скаже: "Хм, це схоже на число. Я перетворю це на int (42) і пропущу далі". Він намагається зрозуміти дані і привести їх до потрібного вигляду.

Як це виглядає "під капотом"?

  1. Ви створюєте клас, який успадковується від BaseModel.
  2. Описуєте поля, використовуючи стандартні типи Python (str, int, list, bool).
  3. Коли ви створюєте об'єкт цього класу, Pydantic біжить по всіх полях і перевіряє їх.

Якщо все ок — у вас є об'єкт з гарантовано правильними типами. Якщо ні — Pydantic викидає детальну помилку (ValidationError), де чітко написано, що і де пішло не так.


3. 🧪 Приклади

Приклад 1: "Мінімальний"

Уявіть, що ми реєструємо користувача. Нам потрібні його id та name.

Питання до вас: Що буде, якщо ми передамо id як рядок "123"?

Дивимось код:

from pydantic import BaseModel

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

# Спроба 1: Ідеальні дані
user1 = User(id=1, name="David")
print(user1)
# id=1 name='David'

# Спроба 2: id як рядок "123"
user2 = User(id="123", name="Alice") 
print(user2)
# id=123 name='Alice'  <-- ДИВІТЬСЯ! Воно стало числом (int)!

Пояснення: Pydantic зробив приведення типів (coercion). Це неймовірно зручно при роботі з API, де всі дані часто приходять як рядки (наприклад, з URL-параметрів).


Приклад 2: "Реальний світ"

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

from typing import List, Optional
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float
    tags: List[str] = []  # Значення за замовчуванням - порожній список
    is_active: bool = True

# Приходять "брудні" JSON-дані
raw_data = {
    "name": "Super Laptop",
    "price": "999.50",       # Рядок!
    "tags": ["tech", 123],   # Один тег - число?!
    "is_active": "yes"       # Рядок замість булевого значення!
}

item = Item(**raw_data) # Розпаковка словника
print(item)

Питання: Чи впаде код? * price: рядок "999.50" -> float 999.5. ✅ * tags: число 123 -> рядок "123". ✅ * is_active: "yes" -> Pydantic достатньо розумний, щоб зрозуміти, що "yes", "on", "1", "true" — це True. ✅

Результат: name='Super Laptop' price=999.5 tags=['tech', '123'] is_active=True

Ми отримали чисті, типізовані дані, з якими безпечно працювати далі.


Приклад 3: "Коли все погано"

А тепер спробуємо зламати систему.

try:
    User(id="abc", name="Hacker") # "abc" не можна перетворити на int
except Exception as e:
    print(e)

Ви побачите ValidationError. Це не просто "Error". Там буде написано: * loc=('id',) — помилка в полі id. * msg='Input should be a valid integer...' — що саме не так.

Це дозволяє вам автоматично віддавати клієнту API красиву відповідь: "Друже, ти помилився в полі id, виправ, будь ласка".


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

Час братися за клавіатуру! Виконайте ці завдання по черзі.

🔹 Завдання 1: Основи Створіть модель Book (Книга) з полями: * title (рядок) * pages (ціле число) * rating (дробове число, float) Створіть об'єкт цієї книги, передавши правильні дані. Виведіть його model_dump() (це перетворить об'єкт назад у словник).

🔹 Завдання 2: "Зламай мене" Спробуйте створити об'єкт Book, передавши в pages значення "багато". Огорніть це в блок try...except ValidationError і виведіть помилку в консоль. Прочитайте її.

🔹 Завдання 3: Вкладеність Уявіть, що у Книги є Автор. 1. Створіть модель Author (name: str, age: int). 2. Додайте поле author типу Author у модель Book. 3. Спробуйте створити книгу з вкладеним словником автора. Приклад даних: {"title": "Dune", "pages": 800, "rating": 4.9, "author": {"name": "Frank", "age": 65}}

🔹 Завдання 4: Міні-кейс (Валідатор) Реальна задача: ціна не може бути від'ємною. Використовуйте декоратор @field_validator. (Підказка: погугліть pydantic field_validator або спробуйте інтуїтивно).

from pydantic import field_validator

class Product(BaseModel):
    price: float

    @field_validator('price')
    @classmethod
    def check_positive(cls, v: float):
        if v < 0:
            raise ValueError('Ціна має бути більше 0')
        return v

Реалізуйте це і перевірте на Product(price=-5).

🔹 Питання "А що, якщо..." Що, якщо в JSON прийде поле, якого немає в нашій моделі (зайве сміття)? Pydantic за замовчуванням його проігнорує. Але чи можна налаштувати так, щоб він забороняв зайві поля? (Ключове слово для пошуку: model_config = {'extra': 'forbid'}).


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

Як думає новачок? "Я просто напишу if data['age'] > 0 в середині функції". Це призводить до "спагеті-коду", де бізнес-логіка змішана з перевірками типів.

Як думає Senior Developer? 1. Fail Fast (Падай швидко): Якщо дані криві, я хочу знати про це на першій мілісекунді, а не після того, як почав запис у базу даних. 2. Single Source of Truth: Моя модель Pydantic — це документація. Якщо я дивлюсь на клас User, я точно знаю, як виглядає користувач. Мені не треба читати код функції. 3. Не довіряй нікому: Фронтенд може помилитися. Інший мікросервіс може надіслати нісенітницю. Pydantic — це мій бронежилет.

Типова помилка: Забувати про Optional. Якщо поле може бути None, обов'язково пишіть Optional[str] (або str | None у Python 3.10+). Інакше, якщо прийде null, буде помилка.


6. 🧩 Підсумок

Отже, що ми сьогодні отримали?

  1. Ви зрозуміли, що валідація — це безпека і стабільність.
  2. Ви навчилися створювати Pydantic-моделі.
  3. Ви побачили, як Pydantic автоматично конвертує типи (магія!).
  4. Ви знаєте, як обробляти помилки.

Тепер ви вмієте будувати надійний "фільтр" на вході у ваші програми. Ваші функції більше не повинні перевіряти, чи є число числом. Вони просто роблять свою роботу.

🚀 Тизер наступного уроку: Тепер, коли ми вміємо описувати дані, як нам створити повноцінний веб-сервер, який приймає ці дані, обробляє їх і віддає відповідь? У наступному уроці ми познайомимось із найкращим другом Pydantic — фреймворком FastAPI. Готуйтесь, буде швидко!

А зараз — вперед до практики! Чекаю на ваші рішення! 👋