Работа с Union-типами, дискриминаторы для точного выбора модели, валидация полиморфных данных.
Дискриминаторы позволяют Pydantic точно выбирать модель из Union по значению поля
Без дискриминатора Pydantic пробует модели по порядку:
from typing import Union
from pydantic import BaseModel
class Cat(BaseModel):
pet_type: str # "cat"
meows: int
class Dog(BaseModel):
pet_type: str # "dog"
barks: float
Pet = Union[Cat, Dog]
# Pydantic пробует Cat первым
# Если данные подходят Cat, но должны быть Dog — ошибка
pet = Pet(pet_type="dog", barks=5.5) # Может выдать ошибкуИспользуем Field с discriminator:
from typing import Union, Literal
from pydantic import BaseModel, Field
from typing import Annotated
class Cat(BaseModel):
pet_type: Literal["cat"]
meows: int
class Dog(BaseModel):
pet_type: Literal["dog"]
barks: float
# Дискриминированный Union
Pet = Annotated[
Union[Cat, Dog],
Field(discriminator="pet_type")
]
# Pydantic использует pet_type для выбора модели
cat = Pet(pet_type="cat", meows=5) # Cat
dog = Pet(pet_type="dog", barks=3.5) # Dog
print(type(cat)) # <class '__main__.Cat'>
print(type(dog)) # <class '__main__.Dog'>Pydantic смотрит на поле-дискриминатор и выбирает модель:
from typing import Union, Literal
from pydantic import BaseModel, Field
from typing import Annotated
class Cat(BaseModel):
pet_type: Literal["cat"]
meows: int
class Dog(BaseModel):
pet_type: Literal["dog"]
barks: float
Pet = Annotated[Union[Cat, Dog], Field(discriminator="pet_type")]
# Валидация из dict
data = {"pet_type": "cat", "meows": 10}
pet = Pet(**data) # Cat
# Ошибка: pet_type не соответствует ни одной модели
try:
Pet(pet_type="bird", sings=True)
except Exception as e:
print(e)
# ValidationError: Input tag 'bird' doesn't match any tag# Без дискриминатора
class Cat(BaseModel):
pet_type: str
meows: int
class Dog(BaseModel):
pet_type: str
barks: float
Pet = Union[Cat, Dog]
# Непонятная ошибка
try:
Pet(pet_type="dog", meows=5)
except Exception as e:
print(e)
# 2 validation errors for Union[Cat, Dog]
# ... много текста ...
# С дискриминатором
class Cat(BaseModel):
pet_type: Literal["cat"]
meows: int
class Dog(BaseModel):
pet_type: Literal["dog"]
barks: float
Pet = Annotated[Union[Cat, Dog], Field(discriminator="pet_type")]
# Точная ошибка
try:
Pet(pet_type="cat", barks=5) # barks нет в Cat
except Exception as e:
print(e)
# 1 validation error for Cat
# barks: Extra inputs are not permittedДискриминатор позволяет Pydantic сразу выбрать модель, не перебирая все варианты.
Код явно указывает, как выбирается модель.
from typing import Union, Literal
from pydantic import BaseModel, Field
from typing import Annotated
class Cat(BaseModel):
pet_type: Literal["cat"]
meows: int
class Dog(BaseModel):
pet_type: Literal["dog"]
barks: float
class Bird(BaseModel):
pet_type: Literal["bird"]
sings: bool
Pet = Annotated[Union[Cat, Dog, Bird], Field(discriminator="pet_type")]
class Owner(BaseModel):
name: str
pet: Pet
owner = Owner(
name="Alice",
pet={"pet_type": "dog", "barks": 5.5}
)
print(owner.pet) # Dog(pet_type='dog', barks=5.5)Можно использовать Tag для явного указания тега модели:
from typing import Union, Literal
from pydantic import BaseModel, Field, Tag
from typing import Annotated
class Cat(BaseModel):
pet_type: Literal["cat"]
meows: int
class Dog(BaseModel):
pet_type: Literal["dog"]
barks: float
# Явные теги
Pet = Annotated[
Union[
Annotated[Cat, Tag("cat")],
Annotated[Dog, Tag("dog")]
],
Field(discriminator="pet_type")
]from typing import Union, Literal
from pydantic import BaseModel, Field
from typing import Annotated
class ErrorDetails(BaseModel):
code: str
message: str
class ValidationErrorDetail(BaseModel):
type: Literal["validation"]
field: str
error: str
class SystemErrorDetail(BaseModel):
type: Literal["system"]
component: str
traceback: str
ErrorDetail = Annotated[
Union[ValidationErrorDetail, SystemErrorDetail],
Field(discriminator="type")
]
class APIError(BaseModel):
status: int
detail: ErrorDetail
# Валидация
error1 = APIError(
status=400,
detail={"type": "validation", "field": "email", "error": "invalid"}
)
error2 = APIError(
status=500,
detail={"type": "system", "component": "database", "traceback": "..."}
)
print(error1.detail.field) # "email"
print(error2.detail.component) # "database"from typing import Union, Literal
from pydantic import BaseModel, Field
from typing import Annotated
class Cat(BaseModel):
pet_type: Literal["cat"]
meows: int
class Dog(BaseModel):
pet_type: Literal["dog"]
barks: float
Pet = Annotated[Union[Cat, Dog], Field(discriminator="pet_type")]
cat = Cat(pet_type="cat", meows=5)
dog = Dog(pet_type="dog", barks=3.5)
# Сериализация
print(cat.model_dump()) # {'pet_type': 'cat', 'meows': 5}
print(dog.model_dump()) # {'pet_type': 'dog', 'barks': 3.5}
# В Union
pets = [cat, dog]
for pet in pets:
print(pet.model_dump())Создайте систему уведомлений с дискриминированными Union:
from typing import Union, Literal, List
from pydantic import BaseModel, Field
from typing import Annotated
from datetime import datetime
# EmailNotification
# - type: Literal["email"]
# - to: str (email)
# - subject: str
# - body: str
# SMSNotification
# - type: Literal["sms"]
# - to: str (phone)
# - message: str
# PushNotification
# - type: Literal["push"]
# - device_id: str
# - title: str
# - body: str
# Notification = Annotated[Union[...], Field(discriminator="type")]
# NotificationBatch
# - notifications: list[Notification]
# - scheduled_at: datetime
# - priority: Literal["low", "normal", "high"]
# Пример:
# batch = NotificationBatch(
# notifications=[
# {"type": "email", "to": "alice@example.com", "subject": "Hello", "body": "Hi!"},
# {"type": "sms", "to": "+1234567890", "message": "Alert!"},
# {"type": "push", "device_id": "dev-123", "title": "Update", "body": "New version"}
# ],
# scheduled_at=datetime.now(),
# priority="high"
# )В следующей теме изучим Pydantic Settings для управления конфигурацией приложения.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.