Опирается на правила: R-VLD-STD-1R-VLD-STD-5 и R-VLD-STD-X1R-VLD-STD-X3 из Validation Style Guide → раздел 2. Стандартные constraints.

Важно знать

  • Required vs optional — через тип, не через Field(default=...): name: str (required), note: str | None = None (optional). Pydantic 2 строго различает.
  • Пустая строкаField(min_length=1). Тип str не запрещает "".
  • ЧислаField(ge=, le=, gt=, lt=). Деньги — Decimal, не float.
  • Формат emailEmailStr из pydantic[email], не Field(pattern=...) с самописным regex.
  • UUID, AnyUrl, PostgresDsn — специальные типы Pydantic; никакого regex вручную.
  • Времяdatetime/date из stdlib; ограничения «не в прошлом» — через @field_validator.
  • @field_validator «не None» на поле без | None — бессмысленно: Pydantic уже гарантирует.

Pydantic v2 покрывает 80% типичных валидаций входных DTO. Стандартные средства — контракт, который одинаково читают разработчик, ревьюер и codegen. Самописный велосипед вместо EmailStr ломает эту общую базу. Раскрытие раздела 2 гайда.

Required vs optional

R-VLD-STD-1: разграничение через систему типов, не через флаги.

from pydantic import BaseModel, Field
from uuid import UUID
from datetime import date

class CreateCustomerRequest(BaseModel):
    first_name: str = Field(min_length=1, max_length=100)
    last_name: str = Field(min_length=1, max_length=100)
    email: EmailStr
    phone: str | None = None
    birth_date: date | None = None
  • first_name: str — required. Pydantic откажет, если поле отсутствует в теле.
  • phone: str | None = None — optional. None — валидное значение.
  • Field(min_length=1) — защита от пустой строки. Тип str разрешает "".

Аналог @NotBlank из Jakarta: str + Field(min_length=1). Аналог @NotNull для объектов: тип без | None.

Размеры — Field с числовыми параметрами

R-VLD-STD-2: строки — min_length/max_length, числа — ge/le/gt/lt.

from decimal import Decimal
from pydantic import BaseModel, Field

class OrderItemRequest(BaseModel):
    sku: str = Field(min_length=1, max_length=64)
    quantity: int = Field(ge=1, le=9999)
    unit_price: Decimal = Field(gt=Decimal("0"), decimal_places=2)
ПараметрЗначение
gegreater or equal (≥)
leless or equal (≤)
gtgreater than (>)
ltless than (<)
min_lengthдля str и list
max_lengthдля str и list
min_length на listаналог @NotEmpty + @Size(min=1)

Формат — специальные типы, не regex

R-VLD-STD-3: стандартные форматы — стандартными средствами.

from pydantic import BaseModel, EmailStr, AnyUrl
from uuid import UUID

class CreateCustomerRequest(BaseModel):
    customer_id: UUID
    email: EmailStr
    site: AnyUrl | None = None

Типы Pydantic, охватывающие стандартные форматы:

ТипЧто валидирует
EmailStremail по RFC 5321
AnyUrlURL (scheme + host)
AnyHttpUrlHTTP/HTTPS URL
PostgresDsnPostgreSQL DSN
UUIDUUID v1-v5
IPvAnyAddressIPv4 или IPv6

R-VLD-STD-X2: Field(pattern=r"^[^@]+@[^@]+\.[^@]+$") вместо EmailStr — антипаттерн. Самописный regex устаревает, не учитывает corner cases RFC, требует поддержки.

Деньги — Decimal, не float

R-VLD-STD-5: финансовые суммы — Decimal с явным decimal_places.

from decimal import Decimal
from pydantic import BaseModel, Field

class CreateOrderRequest(BaseModel):
    total_amount: Decimal = Field(gt=0, decimal_places=2)
    discount: Decimal = Field(ge=0, le=Decimal("100"), decimal_places=2)

float для денег — антипаттерн: потеря точности при арифметике (0.1 + 0.2 != 0.3). Decimal с decimal_places=2 гарантирует, что клиент не передаст 99.999.

Для конфига и внутренних числовых параметров float допустим — там потеря точности не имеет финансовых последствий.

Время — datetime и @field_validator

R-VLD-STD-4: Pydantic принимает ISO 8601 строки и парсит их в datetime/date автоматически. Ограничения «не в прошлом» — через @field_validator.

from datetime import date
from pydantic import BaseModel, field_validator
import datetime as dt

class CreateContractRequest(BaseModel):
    start_date: date
    end_date: date

    @field_validator("start_date")
    @classmethod
    def start_not_in_past(cls, v: date) -> date:
        if v < dt.date.today():
            raise ValueError("Дата начала не может быть в прошлом")
        return v

Строку "2026-07-01" Pydantic разберёт сам. Некорректный формат "01/07/2026"ValidationError без дополнительного кода.

Что запрещено

АнтипаттернПравилоЧто взамен
@field_validator «не None» на поле без \| NoneR-VLD-STD-X1Тип str уже гарантирует; убрать валидатор
Field(pattern=email_regex) вместо EmailStrR-VLD-STD-X2EmailStr из pydantic[email]
float для денежных суммR-VLD-STD-5Decimal = Field(..., decimal_places=2)
Один «всё-в-одном» @field_validator с 5 проверкамиR-VLD-STD-X3Отдельные Field(...) параметры + специальные типы
str = Field(default=None) для optionalR-VLD-STD-1str \| None = None

Куда дальше

  • Validation → раздел 2. Стандартные constraints — нормативные формулировки R-VLD-STD-*.
  • python/custom-constraints — Annotated[T, AfterValidator(...)] для частых доменных форматов.
  • python/cross-field-validation — @model_validator для правил между полями.
  • python/messages-and-i18n — тексты ошибок на русском без технических терминов.