lenec ru

← все посты

Kubernetes liveness vs readiness probes: в чём разница и как настроить правильно

13K

Pydantic v2 — это не косметический апдейт, а переписывание ядра на Rust. Валидация стала в 5–50 раз быстрее, API почистили, а старые паттерны из v1 местами сломались. Разберём, что изменилось, как мигрировать и где новые грабли.

Главные изменения в Pydantic v2

Ядро валидации переехало в отдельный крейт pydantic-core, написанный на Rust. Python-слой теперь тонкая обёртка. Что это даёт на практике:

  • Скорость: парсинг JSON и валидация моделей — в 5–50x быстрее в зависимости от сложности схемы.
  • ConfigDict вместо class Config: вложенный класс Config заменён на model_config = ConfigDict(...).
  • model_validator вместо root_validator: новый декоратор с явным режимом mode="before" / mode="wrap" / mode="after".
  • field_validator вместо validator: декоратор @validator удалён, теперь @field_validator с явным указанием полей.
from pydantic import BaseModel, ConfigDict, field_validator, model_validator

class User(BaseModel):
    model_config = ConfigDict(strict=True, frozen=True)

    name: str
    email: str
    age: int

    @field_validator("email")
    @classmethod
    def validate_email(cls, v: str) -> str:
        if "@" not in v:
            raise ValueError("Invalid email")
        return v.lower()

    @model_validator(mode="after")
    def check_age_name(self) -> "User":
        if self.age < 18 and "admin" in self.name.lower():
            raise ValueError("Admins must be 18+")
        return self

Бенчмарк: v1 vs v2 на реальных моделях

Тестируем на типичной модели из продакшена — вложенные объекты, списки, Optional-поля, кастомные валидаторы. Окружение: Python 3.11, 1 млн итераций:

# Модель: Order с вложенными Item (5 полей), список из 10 элементов
# Pydantic v1.10.13
# model(**data):          4.2 сек / 1M
# .json() сериализация:  3.8 сек / 1M

# Pydantic v2.6
# model_validate(data):  0.31 сек / 1M  (x13.5 быстрее)
# .model_dump_json():    0.19 сек / 1M  (x20 быстрее)

Самый большой прирост — на сериализации в JSON. Rust-ядро сериализует напрямую, минуя промежуточный dict. На простых flat-моделях разница скромнее (x5–x7), на глубоко вложенных — до x50.

Пошаговая миграция с v1 на v2

Миграция большого проекта — не «поменять импорты». Вот проверенный план:

  1. Запустите bump-pydantic: автоматический кодмод, который переименовывает 80% изменений.
pip install bump-pydantic
bump-pydantic --diff .  # сначала смотрим diff
bump-pydantic .
  1. Ручные правки после bump-pydantic:
  • @validator@field_validator (bump-pydantic делает это, но проверьте pre=Truemode="before").
  • @root_validator@model_validator(mode="before") или mode="after".
  • class Configmodel_config = ConfigDict(...).
  • .dict().model_dump(), .json().model_dump_json().
  • parse_obj()model_validate(), parse_raw()model_validate_json().
  1. Включите deprecation warnings:
import warnings
warnings.filterwarnings("error", category=DeprecationWarning, module="pydantic")
  1. Прогоните тесты с strict=True на критичных моделях — это выявит неявные coercion (строка → int), которые v2 по умолчанию всё ещё делает в lax mode.

Новые фичи: computed_field, JSON Schema, strict mode

computed_field — вычисляемое поле, которое попадает в сериализацию и JSON Schema, но не принимается на вход:

from pydantic import BaseModel, computed_field

class Rectangle(BaseModel):
    width: float
    height: float

    @computed_field
    @property
    def area(self) -> float:
        return self.width * self.height

r = Rectangle(width=3, height=4)
print(r.model_dump())
# {'width': 3.0, 'height': 4.0, 'area': 12.0}

JSON Schema — генерация стала точнее и поддерживает OpenAPI 3.1:

print(Rectangle.model_json_schema())
# Включает 'area' как readOnly property

Strict mode — запрещает неявное приведение типов:

from pydantic import BaseModel, ConfigDict

class StrictUser(BaseModel):
    model_config = ConfigDict(strict=True)
    age: int

StrictUser(age="25")  # ValidationError! str не приводится к int
StrictUser(age=25)    # OK

Strict mode можно включить глобально через ConfigDict(strict=True) или точечно через Field(strict=True) на отдельных полях.

Подводные камни: breaking changes в validators

  • values → info: в v1 валидатор получал values: dict с уже провалидированными полями. В v2 @field_validator получает только значение поля. Для доступа к другим полям используйте @model_validator(mode="after").
  • each_item=True удалён: валидация элементов списка теперь через аннотацию Annotated[list[str], BeforeValidator(fn)].
  • orm_mode → from_attributes: Config.orm_mode = True стал ConfigDict(from_attributes=True).
  • __get_validators__ удалён: кастомные типы теперь через __get_pydantic_core_schema__.
  • Порядок валидации: в v1 поля валидировались в порядке объявления, в v2 порядок не гарантирован (параллелизм в Rust-ядре). Не полагайтесь на порядок в field_validator.
# v1 — работало, потому что 'name' валидировался раньше 'greeting'
@validator("greeting", pre=False)
def set_greeting(cls, v, values):
    return f"Hello, {values['name']}"

# v2 — правильный способ
@model_validator(mode="after")
def set_greeting(self) -> "MyModel":
    self.greeting = f"Hello, {self.name}"
    return self

Выводы

Pydantic v2 — объективно лучше: быстрее, строже, с более чистым API. Но миграция требует внимания:

  1. Начните с bump-pydantic — он закроет 80% механических изменений.
  2. Переведите root_validatormodel_validator вручную, проверяя логику доступа к полям.
  3. Включите strict=True на критичных моделях — найдёте скрытые баги с coercion.
  4. Используйте computed_field вместо хаков с __init__ и property.
  5. Не полагайтесь на порядок валидации полей — это главный источник багов при миграции.

Если проект на FastAPI — обновляйтесь до FastAPI 0.100+, он нативно поддерживает Pydantic v2 и автоматически генерирует OpenAPI 3.1 схемы.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.