1. Архитектура моделей и строгая типизация
Архитектура моделей и строгая типизация
Pydantic решает две задачи одновременно:
В продвинутых проектах основная сложность — не написать ещё один BaseModel, а выстроить архитектуру моделей, где:
> В этой статье мы будем опираться на Pydantic v2 (актуальная ветка). Документация: Pydantic Documentation.
!Поток данных через слои моделей и места, где применяется строгая типизация
Модель как контракт и как граница
В архитектуре модели выполняют роль границ:
Если смешать эти границы в одной модели, появляются типичные проблемы:
Практический вывод: делайте несколько типов моделей под разные слои.
Слои моделей: transport, domain, persistence
Одна из рабочих стратегий — разделять модели по назначению.
Transport-модели (DTO)
Transport-модели описывают то, как данные приходят/уходят.
Пример:
Domain-модели
Domain-модели — то, с чем живёт бизнес-логика. Часто они:
str/int”В некоторых командах доменные сущности делают не на Pydantic (например, dataclasses), а Pydantic используют как “периметр”. Но Pydantic v2 позволяет комфортно строить строгие доменные модели тоже — вопрос архитектурного выбора.
Persistence-модели
Persistence-модели описывают то, как объект хранится в БД/кэше/индексе.
created_at, version, deleted)Управление строгостью: где быть строгим, а где гибким
Строгая типизация в Pydantic — это контроль над тем, допускаете ли вы неявные преобразования. Пример неявного преобразования: строка "123" превращается в int(123).
Ключевая идея: строгость — это архитектурное решение по слоям.
В Pydantic v2 строгость обычно задают через конфигурацию и/или строгие типы.
Документация по конфигурации: Model Config.
Конфигурация модели: единые правила на весь класс
В v2 конфигурация задаётся через model_config = ConfigDict(...).
Наиболее архитектурно значимые параметры:
extra='forbid' | 'ignore' | 'allow'strict=True (сделать модель строгой по умолчанию)populate_by_name=True (разрешить заполнение по имени поля, а не только по алиасу)Пример строгой доменной модели:
Что даёт strict=True:
Строгие типы: точечная строгость вместо глобальной
Иногда не нужна строгость всего, а нужна строгость на ключевых полях. Для этого есть строгие типы.
В Pydantic доступны, например:
StrictInt, StrictStr, StrictBool, StrictFloatДокументация: Strict Types.
Пример:
Архитектурный смысл: вы можете оставить transport-модель достаточно гибкой, но критичные параметры (страницы, лимиты, флаги доступа) сделать строгими.
Annotated и Field: тип — отдельно, ограничения — отдельно
В продвинутой типизации важно не перегружать типы. Рекомендуемый путь — хранить тип в аннотации, а ограничения — в метаданных.
Для этого используется typing.Annotated и Field.
Документация по Annotated: PEP 593 — Flexible function and variable annotations.
Пример:
Плюсы такого подхода:
UserId)Композиция моделей вместо наследования
Наследование удобно, но в больших кодовых базах часто приводит к хрупким иерархиям: изменения в базовой модели влияют на десятки наследников.
Композиция обычно устойчивее:
Пример композиции:
Когда наследование оправдано:
Явная стратегия работы с лишними полями (extra)
Параметр extra — архитектурная “вилка”, влияющая на безопасность и устойчивость интеграций.
extra='forbid': лучший выбор для домена и внутренних контрактов (лишнее — ошибка)extra='ignore': часто полезен на входе, когда клиенты присылают больше полей, чем нужноextra='allow': полезно редко (например, проксирование произвольных payload), требует дисциплиныПример строгого запрета:
Инварианты и валидаторы: минимум логики в валидаторах
Валидаторы — мощный инструмент, но архитектурно опасный, если превращать их в “свалку бизнес-логики”. Рекомендации:
В Pydantic v2 используются @field_validator и @model_validator.
Документация: Validators.
Пример нормализации:
Дискриминированные объединения: строгие полиморфные payload
Когда у вас есть “одно из нескольких” сообщений/команд/событий, используйте discriminated union: поле-дискриминатор определяет конкретную модель.
Документация: Discriminated Unions.
Пример:
Архитектурный эффект:
dict[str, Any]TypeAdapter: единый валидатор для “не-моделей”
Не всё в проекте должно быть BaseModel. Иногда нужно валидировать:
Для этого в v2 есть TypeAdapter.
Документация: TypeAdapter.
Пример:
Архитектурно это помогает:
Конструирование без валидации: осознанный “быстрый путь”
Иногда данные уже гарантированно корректны (например, пришли из вашего же хранилища, и вы контролируете схему). Тогда может быть полезно построить модель без валидации.
В Pydantic v2 для этого используется model_construct.
Документация: BaseModel.model_construct.
Важно понимать архитектурный риск:
Правило: используйте model_construct только в местах, где у вас действительно есть гарантия корректности данных и это зафиксировано в контракте слоя.
Переиспользуемые типы: Value Object подход
Вместо того чтобы везде писать str и int, вводите доменные типы:
UserId, OrderIdEmailCurrencyCodeЭто улучшает читабельность и снижает шанс перепутать поля местами.
Варианты реализации:
Annotated (часто достаточно)Пример алиаса:
Примечание: регулярное выражение — это ограничение формата, но оно не доказывает, что почтовый ящик существует. Для архитектуры это означает, что “валидность формата” и “валидность как бизнес-факт” — разные уровни.
Типизация и статический анализ: Pydantic не заменяет mypy/pyright
Pydantic проверяет данные во время выполнения, а mypy/pyright — во время разработки.
Ссылки:
Практика для продвинутых проектов:
Any, минимум неявных преобразований)Резюме
ConfigDict для единых правил модели и строгие типы для точечной строгости.TypeAdapter помогает валидировать сложные типы без создания лишних моделей.model_construct — оптимизация с риском, применяйте только при гарантированной корректности данных.В следующей логичной теме курса мы будем углубляться в валидацию, нормализацию и пользовательские ограничения так, чтобы валидаторы оставались предсказуемыми и архитектурно “тонкими”.