Кастомные сериализаторы через @field_serializer, режим wrap, передача контекста при сериализации.
Кастомные сериализаторы позволяют полностью контролировать вывод данных
@field_serializer позволяет изменить формат вывода конкретного поля:
from pydantic import BaseModel, field_serializer
from datetime import datetime
class User(BaseModel):
name: str
created_at: datetime
@field_serializer('created_at')
def serialize_created_at(self, value: datetime) -> str:
return value.strftime('%Y-%m-%d %H:%M')
user = User(name="Alice", created_at=datetime(2026, 3, 18, 10, 30))
print(user.model_dump())
# {'name': 'Alice', 'created_at': '2026-03-18 10:30'}from pydantic import BaseModel, field_serializer
from datetime import datetime
class Event(BaseModel):
name: str
start: datetime
end: datetime
@field_serializer('start', return_type=str)
def serialize_start(self, value: datetime) -> str:
return value.isoformat()
@field_serializer('end', mode='wrap')
def serialize_end(self, value: datetime, handler):
# handler — стандартный сериализатор
serialized = handler(value)
return f"Ends at: {serialized}"
event = Event(
name="Conference",
start=datetime(2026, 3, 18, 9, 0),
end=datetime(2026, 3, 18, 18, 0)
)
print(event.model_dump())
# {'name': 'Conference', 'start': '2026-03-18T09:00:00',
# 'end': 'Ends at: 2026-03-18T18:00:00'}from pydantic import BaseModel, field_serializer
from decimal import Decimal
class Product(BaseModel):
name: str
price: Decimal
@field_serializer('price', mode='wrap')
def serialize_price(self, value: Decimal, handler) -> str:
# Стандартная сериализация
serialized = handler(value)
# Форматирование как валюта
return f"${float(value):.2f}"
product = Product(name="Widget", price=Decimal("19.99"))
print(product.model_dump())
# {'name': 'Widget', 'price': '$19.99'}from pydantic import BaseModel, field_serializer
from datetime import datetime
class Event(BaseModel):
name: str
created_at: datetime
updated_at: datetime
@field_serializer('created_at', 'updated_at')
def serialize_dates(self, value: datetime) -> str:
return value.strftime('%Y-%m-%d')
event = Event(
name="Meeting",
created_at=datetime(2026, 3, 18, 10, 0),
updated_at=datetime(2026, 3, 18, 11, 0)
)
print(event.model_dump())
# {'name': 'Meeting', 'created_at': '2026-03-18', 'updated_at': '2026-03-18'}@model_serializer позволяет переопределить сериализацию всей модели:
from pydantic import BaseModel, model_serializer
from datetime import datetime
class User(BaseModel):
name: str
email: str
password_hash: str
created_at: datetime
@model_serializer
def serialize_user(self) -> dict:
return {
'name': self.name,
'email': self.email,
'created_at': self.created_at.strftime('%Y-%m-%d')
# password_hash не включается
}
user = User(
name="Alice",
email="alice@example.com",
password_hash="hashed_value",
created_at=datetime.now()
)
print(user.model_dump())
# {'name': 'Alice', 'email': 'alice@example.com', 'created_at': '2026-03-18'}from pydantic import BaseModel, model_serializer
class User(BaseModel):
name: str
email: str
role: str = "user"
@model_serializer(mode='wrap')
def serialize_user(self, handler):
# Стандартная сериализация
data = handler(self)
# Добавление метаданных
data['_serialized_at'] = datetime.now().isoformat()
data['_version'] = '1.0'
return data
user = User(name="Alice", email="alice@example.com")
print(user.model_dump())
# {'name': 'Alice', 'email': 'alice@example.com', 'role': 'user',
# '_serialized_at': '2026-03-18T10:30:00', '_version': '1.0'}from pydantic import BaseModel, field_serializer, SerializationInfo
from datetime import datetime
class User(BaseModel):
name: str
email: str
is_admin: bool
last_login: datetime | None = None
@field_serializer('last_login')
def serialize_last_login(
self,
value: datetime | None,
info: SerializationInfo
) -> str | None:
# Доступ к контексту
context = info.context
if context and context.get('show_last_login'):
return value.isoformat() if value else None
# Скрываем last_login по умолчанию
return None
user = User(
name="Alice",
email="alice@example.com",
is_admin=True,
last_login=datetime(2026, 3, 18, 10, 0)
)
# Без контекста
print(user.model_dump())
# {'name': 'Alice', 'email': 'alice@example.com', 'is_admin': True}
# С контекстом
print(user.model_dump(context={'show_last_login': True}))
# {'name': 'Alice', 'email': 'alice@example.com', 'is_admin': True,
# 'last_login': '2026-03-18T10:00:00'}from pydantic import BaseModel, field_serializer, field_validator
from typing import Any
class SecureModel(BaseModel):
data: dict[str, Any]
sensitivity: str # 'public', 'internal', 'confidential'
@field_serializer('data')
def serialize_data(self, value: dict) -> dict:
if self.sensitivity == 'confidential':
# Маскирование чувствительных данных
return {k: '***' for k in value}
elif self.sensitivity == 'internal':
# Удаление некоторых полей
return {k: v for k, v in value.items() if not k.startswith('_')}
return value
public = SecureModel(data={'key': 'value'}, sensitivity='public')
internal = SecureModel(data={'key': 'value', '_secret': 'hidden'}, sensitivity='internal')
confidential = SecureModel(data={'key': 'value'}, sensitivity='confidential')
print(public.model_dump())
# {'data': {'key': 'value'}, 'sensitivity': 'public'}
print(internal.model_dump())
# {'data': {'key': 'value'}, 'sensitivity': 'internal'}
print(confidential.model_dump())
# {'data': {'key': '***'}, 'sensitivity': 'confidential'}from pydantic import BaseModel, field_serializer
from datetime import datetime
class Address(BaseModel):
street: str
city: str
zip_code: str
class User(BaseModel):
name: str
address: Address
updated_at: datetime
@field_serializer('address')
def serialize_address(self, value: Address) -> str:
return f"{value.street}, {value.city}"
@field_serializer('updated_at')
def serialize_updated_at(self, value: datetime) -> str:
return value.strftime('%Y-%m-%d')
user = User(
name="Alice",
address=Address(street="123 Main St", city="NYC", zip_code="10001"),
updated_at=datetime.now()
)
print(user.model_dump())
# {'name': 'Alice', 'address': '123 Main St, NYC', 'updated_at': '2026-03-18'}from pydantic import BaseModel, field_serializer
from datetime import datetime
class Event(BaseModel):
name: str
date: datetime
class Calendar(BaseModel):
name: str
events: list[Event]
@field_serializer('events')
def serialize_events(self, value: list[Event]) -> list[dict]:
return [
{'name': e.name, 'date': e.date.strftime('%Y-%m-%d')}
for e in value
]
calendar = Calendar(
name="Work",
events=[
Event(name="Meeting", date=datetime(2026, 3, 18, 10, 0)),
Event(name="Deadline", date=datetime(2026, 3, 20, 18, 0))
]
)
print(calendar.model_dump())
# {'name': 'Work', 'events': [
# {'name': 'Meeting', 'date': '2026-03-18'},
# {'name': 'Deadline', 'date': '2026-03-20'}
# ]}Создайте систему ответов API с кастомной сериализацией:
from pydantic import BaseModel, field_serializer, model_serializer, Field
from datetime import datetime
from typing import Any, Optional, Generic, TypeVar
T = TypeVar('T')
class APIResponse(BaseModel, Generic[T]):
"""Универсальный ответ API с кастомной сериализацией"""
success: bool
data: T
message: Optional[str] = None
timestamp: datetime = Field(default_factory=datetime.now)
request_id: str
# Добавьте @field_serializer для:
# - timestamp: формат ISO 8601
# - data: кастомная сериализация в зависимости от типа
# Добавьте @model_serializer(mode='wrap') для:
# - Добавления версии API в ответ
# - Условного включения message (только если не None)
class UserData(BaseModel):
id: int
username: str
email: str
password_hash: str # не должен сериализоваться
created_at: datetime
# Добавьте @field_serializer для:
# - created_at: формат 'YYYY-MM-DD'
# - password_hash: возвращать '***' вместо значения
# Пример использования:
# response = APIResponse[UserData](
# success=True,
# data=UserData(id=1, username="alice", email="alice@example.com",
# password_hash="hashed", created_at=datetime.now()),
# request_id="req-123"
# )
# print(response.model_dump())В следующей теме изучим обработку ошибок валидации и кастомизацию сообщений.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.