Pydantic для валидации данных на собеседовании Data Engineer

Проверь себя · 1/3разбор после ответа
Нужно получить уникальный список идентификаторов пользователей из двух каналов: campaign_a(user_id) и campaign_b(user_id). Как корректнее объединить списки, чтобы убрать дубликаты?

Зачем Pydantic в ETL

ETL-пайплайны принимают сырые данные из API, файлов и очередей — то есть из источников, которым нельзя доверять. Стоит поставщику поменять тип поля, прислать NULL вместо числа или переименовать ключ в JSON, и без проверки этот мусор молча поедет дальше по пайплайну, а всплывёт уже в дашборде или в упавшей ML-модели. Отлаживать такое потом — боль: ошибка проявляется далеко от места, где данные испортились.

Pydantic решает эту проблему на входе. Это библиотека для валидации данных на основе аннотаций типов Python: вы описываете ожидаемую схему как обычный класс, а Pydantic проверяет входные данные, приводит типы там, где это разумно, и падает с понятной ошибкой, если данные не соответствуют контракту. На собесе Data Engineer это любимый инструмент для темы «валидация и контракты данных», потому что он ловит проблемы рано — на границе пайплайна, а не в его конце.

Модели

Модель — это класс, унаследованный от BaseModel, где поля описаны аннотациями типов. Валидация происходит автоматически при создании объекта:

from datetime import datetime
from pydantic import BaseModel

class Order(BaseModel):
    id: int
    amount: float
    status: str
    created_at: datetime

# Валидация запускается автоматически
order = Order(**raw_dict)  # бросит ValidationError, если данные не подходят

Что Pydantic делает при создании объекта:

  • Проверяет типы. Поле amount должно быть числом, created_at — датой.
  • Требует обязательные поля. Если в raw_dict нет id, будет ошибка — поле без значения по умолчанию считается обязательным.
  • Приводит типы, где это разумно. Строку "42" в поле int превратит в число 42.

Если данные не проходят проверку, поднимается ValidationError со списком всех проблем сразу — это удобнее, чем ловить ошибки по одной.

Приведение типов

По умолчанию Pydantic работает в «мягком» режиме и приводит совместимые типы автоматически. Это ровно то, что нужно в ETL, где API часто отдаёт числа и даты строками:

from datetime import datetime
from decimal import Decimal
from pydantic import BaseModel

class Event(BaseModel):
    timestamp: datetime
    user_id: int
    amount: Decimal

raw = {"timestamp": "2026-05-07", "user_id": "42", "amount": "100.50"}
event = Event(**raw)  # всё корректно приведётся к нужным типам

Здесь строка "2026-05-07" станет datetime, "42" — числом, "100.50"Decimal. Но иногда приведение вредит: если поле критично и вы хотите отклонять строку там, где ждёте только число, включается строгий режим (strict=True в конфигурации модели или поля). В нём приведение отключается, и "42" в поле int уже вызовет ошибку. На собесе стоит проговорить этот компромисс: мягкий режим удобнее для грязных источников, строгий — надёжнее для критичных контрактов.

Валидаторы

Когда встроенных проверок типов мало и нужна своя бизнес-логика, пишут кастомные валидаторы. Метод помечается декоратором field_validator, принимает значение поля и либо возвращает его (возможно, изменённым), либо бросает ошибку:

from pydantic import BaseModel, field_validator

class User(BaseModel):
    email: str
    age: int

    @field_validator('age')
    def age_positive(cls, v):
        if v < 0:
            raise ValueError('возраст не может быть отрицательным')
        return v

    @field_validator('email')
    def email_valid(cls, v):
        if '@' not in v:
            raise ValueError('некорректный email')
        return v.lower()

Валидатор может не только проверять, но и нормализовать данные — здесь email заодно приводится к нижнему регистру. Это удобное место для правил, которые не выразить одним типом: «дата отгрузки не раньше даты заказа», «сумма положительная», «код страны из списка ISO». Для проверок, которые затрагивают несколько полей сразу, есть model_validator.

Готовишься к собесу Data Engineer?
Spark, Airflow, ClickHouse, SQL для DE — вопросы с разборами в Telegram
Тренировать DE в Telegram

Pydantic v1 vs v2

Pydantic v2 (вышел в 2023) — это крупная переработка библиотеки, и на собесе про отличия спрашивают часто, потому что в продакшене встречаются оба поколения:

  • Скорость. Ядро v2 переписано на Rust (pydantic-core), что дало ускорение в 5–50 раз на реальных нагрузках.
  • Сообщения об ошибках. Стали подробнее и структурированнее — проще понять, какое поле и почему не прошло.
  • API поменялся. Часть изменений ломающая: переименованы декораторы и методы конфигурации.
# v1
class Model(BaseModel):
    @validator('field')
    ...

# v2
class Model(BaseModel):
    @field_validator('field')
    ...

В новых проектах берут v2 — он быстрее и активно развивается. Legacy-кодовые базы мигрируют постепенно, для этого есть инструмент bump-pydantic, который автоматически переписывает часть устаревшего синтаксиса.

Как это спрашивают на собесе

Реальные формулировки и что за ними проверяют:

  • «Зачем валидировать данные на входе ETL, а не в конце?» Чтобы поймать битые данные до того, как они разъедутся по пайплайну и всплывут в дашборде. Отладка у источника кратно дешевле.
  • «Чем Pydantic лучше ручных проверок через if?» Схема описывается декларативно через типы, ошибки собираются все сразу в ValidationError, а не по одной, плюс бесплатное приведение типов.
  • «Как отклонить строку там, где ждёте int?» Включить strict-режим — в нём приведение отключается и несовпадение типа даёт ошибку.
  • «Как проверить правило, зависящее от двух полей?» Через model_validator, который валидирует всю модель целиком, а не отдельное поле.
  • «Чем v2 отличается от v1?» Rust-ядро и ускорение в разы, новые декораторы (field_validator вместо validator), более детальные ошибки.

Частые ошибки

  • Полагаться на мягкое приведение для критичных полей. Pydantic по умолчанию с радостью превратит "0" в 0 или "true" в True. Для полей, где важна строгость, включайте strict-режим.
  • Валидировать в конце пайплайна. Тогда битые данные уже успели повлиять на промежуточные шаги. Проверять надо на границе — сразу после чтения из источника.
  • Игнорировать содержимое ValidationError. Ошибка содержит список всех проблем с указанием полей — её надо логировать и мониторить, а не просто ловить и глотать.
  • Тяжёлые валидаторы на горячем пути. Валидатор с обращением к БД или внешнему API на каждую строку убьёт производительность батча. Тяжёлые проверки выносят отдельно.
  • Путать синтаксис v1 и v2. @validator из v1 и @field_validator из v2 несовместимы; смешивание в одном проекте — источник неочевидных багов.

Связанные темы

FAQ

Чем Pydantic лучше ручной проверки данных?

Схема описывается декларативно через аннотации типов, а не десятком проверок if. Pydantic сам приводит совместимые типы, требует обязательные поля и собирает все ошибки в один ValidationError вместо того, чтобы падать на первой. Код становится короче, а проверки — единообразными и переиспользуемыми.

Когда включать strict-режим?

Когда автоматическое приведение типов вредит: для критичных полей, где строка вместо числа или "true" вместо булева — это сигнал о битом источнике, а не то, что нужно молча исправить. В мягком режиме такие значения приведутся, в strict — дадут ошибку. Для грязных внешних API обычно оставляют мягкий режим, для внутренних контрактов — строгий.

Как проверить правило, которое зависит от нескольких полей?

Через model_validator — он валидирует модель целиком, когда все поля уже разобраны. Подходит для правил вроде «дата окончания позже даты начала» или «если тип = premium, поле план обязательно». field_validator для этого не годится, он видит только одно поле.

Стоит ли использовать v1 или v2?

В новых проектах — v2: ядро на Rust даёт ускорение в 5–50 раз, ошибки подробнее, библиотека активно развивается. v1 остаётся в legacy-проектах; миграцию упрощает инструмент bump-pydantic. Синтаксис у поколений разный (validator против field_validator), смешивать их не стоит.

Это официальная информация?

Нет. Статья основана на документации Pydantic (v2) и опыте кандидатов. Конкретный стек и требования зависят от команды и проекта.


Тренируйте Data Engineering — откройте тренажёр с 1500+ вопросами для собесов.