Валидация разных версий входной схемы в Pydantic решается через поддержку множественных моделей или использование универсального адаптера. Прямая поддержка позволяет явно указывать версию при парсинге, а TypeAdapter дает гибкость для обработки данных без жесткой привязки к одной структуре.

Прямая поддержка множественных моделей

Самый прозрачный способ — создание отдельных классов для каждой версии схемы. Это позволяет явно управлять логикой валидации и миграции данных.

При парсинге JSON вы указываете конкретную модель, соответствующую текущей версии данных. Если версия неизвестна, можно реализовать логику автоопределения или вернуть ошибку с рекомендацией обновить источник.

Пример реализации: создайте классы V1Schema и V2Schema. В точке входа определите версию (например, через заголовок HTTP или поле в теле запроса) и выберите модель динамически.

Использование TypeAdapter для универсальной обработки

TypeAdapter позволяет создать единый адаптер, который может работать с разными структурами данных. Это полезно, когда нужно агрегировать данные из нескольких источников или обрабатывать поток сообщений.

Адаптер не требует явного указания модели при каждом вызове. Вы создаете один экземпляр и используете его для валидации любых входящих данных, которые соответствуют хотя бы одной из поддерживаемых схем.

Это снижает количество кода и упрощает поддержку, но требует внимательности: если схема меняется кардинально, адаптер может начать принимать некорректные данные. Поэтому важно тестировать переходы между версиями.

Переключение версий через параметр version

В модели можно добавить поле `version` с типом строки или числа. В конструкторе BaseModel используйте аргумент `__pydantic_extra_fields__`, чтобы разрешать дополнительные поля, не описанные в схеме.

Используйте кастомный конструктор (например, через `model_validator`), который проверяет значение поля `version` и динамически выбирает нужную под-схему для валидации остальных полей.

Такой подход позволяет хранить все данные в одном объекте, но обрабатывать их как разные версии. Это удобно для бэкенда, где одна модель отвечает за всю структуру ответа API.

Обработка устаревших полей

При переходе на новую версию часто приходится удалять старые поля. Чтобы не ломать работу с legacy-данными, используйте `Field(default_factory=…)` или игнорируйте лишние поля через настройки модели.

Если поле устарело, но его значение нужно сохранить для истории, добавьте его в модель как опциональное поле с дефолтным значением. Это позволит постепенно мигрировать данные без потери информации.

Важно: не удаляйте поля из схемы сразу после запуска новой версии. Держите их до тех пор, пока все клиенты не обновятся.

Совместимость версий Pydantic

Pydantic v2 имеет измененную систему валидации по сравнению с v1. Если нужно поддерживать старые проекты, используйте модуль `pydantic.v1` или оберните код в отдельный пакет.

При переходе на v2 обратите внимание на изменения в работе с `model_validate_json` и `Field`. Некоторые функции требуют явного указания `from_attributes=True` для десериализации из словарей.

Обработка невалидных данных

Если данные не соответствуют ни одной версии, верните понятную ошибку с указанием ожидаемой структуры. Логгируйте такие случаи для анализа причин сбоя.

Реализуйте механизм graceful degradation: если критическое поле отсутствует, используйте дефолтное значение или пропустите валидацию этого поля, но предупредите пользователя.

Не игнорируйте ошибки молча. Записывайте их в лог с контекстом (версия данных, версия модели, содержимое запроса) для последующего анализа.

Тестирование переходов между версиями

Напишите тесты для каждой версии схемы и проверьте обратную совместимость. Используйте фиктивные данные, имитирующие реальные изменения в API.

Проверьте, что миграция данных не приводит к потере информации. Если нужно трансформировать данные из старой версии в новую, сделайте это явно в конструкторе модели.

Интеграция встраиваемых схем

Используйте `model_validate_json` с указанием конкретной модели для парсинга JSON. Это гарантирует, что данные будут проверены именно той схемой, которая нужна.

Для сложных структур создайте иерархию моделей: общая модель-обертка содержит поле `version`, а внутри — вложенные объекты разных типов.

Вопросы и ответы

Как определить версию данных автоматически?

Можно анализировать заголовки HTTP, структуру JSON или использовать сигнатуру полей. Например, если поле `id` имеет формат UUID v4 — это новая версия, а строковый формат — старая.

Что делать, если клиент не сообщает версию?

Реализуйте логику автоопределения по структуре данных или используйте дефолтную модель. Если данные не соответствуют ни одной версии — верните ошибку с рекомендацией обновить клиента.

Можно ли использовать одну модель для всех версий?

Только если разница между версиями минимальна (добавление опциональных полей). При структурных изменениях лучше разделять модели или использовать TypeAdapter с явной логикой выбора.

По теме

from pydantic import BaseModel, Field, TypeAdapter, model_validator
from typing import Union, Literal

class V1Schema(BaseModel):
    id: str
    name: str

class V2Schema(BaseModel):
    id: int
    name: str
    version: Literal["v2"] = "v2"

class UnifiedSchema(BaseModel):
    version: Literal["v1", "v2"]
    data: Union[V1Schema, V2Schema]

    @model_validator(mode='after')
    def validate_version(self):
        if self.version == 'v1':
            return V1Schema(**self.data)
        elif self.version == 'v2':
            return V2Schema(**self.data)

# Использование TypeAdapter
ta = TypeAdapter(Union[V1Schema, V2Schema])
data = ta.validate_json('{"id": 123, "name": "Test", "version": "v2"}')