Структура ошибок валидации, извлечение деталей, кастомные сообщения об ошибках.
Понимание структуры ошибок и кастомизация сообщений — ключ к удобному API
from pydantic import BaseModel, Field, ValidationError
class User(BaseModel):
username: str = Field(min_length=3)
email: str
age: int = Field(ge=0, le=150)
try:
User(username="ab", email="invalid", age=200)
except ValidationError as e:
print(e)
# 3 validation errors for User
# username
# String should have at least 3 characters [...]
# email
# value is not a valid email address [...]
# age
# Input should be less than or equal to 150 [...]from pydantic import ValidationError
try:
User(username="ab", email="invalid", age=200)
except ValidationError as e:
# errors() — список ошибок
errors = e.errors()
print(errors)
# [
# {'type': 'string_too_short', 'loc': ('username',),
# 'msg': 'String should have at least 3 characters',
# 'input': 'ab', ...},
# {'type': 'value_error', 'loc': ('email',),
# 'msg': 'value is not a valid email address',
# 'input': 'invalid', ...},
# {'type': 'less_than_equal', 'loc': ('age',),
# 'msg': 'Input should be less than or equal to 150',
# 'input': 200, ...}
# ]
# error_count() — количество ошибок
print(e.error_count()) # 3
# title — название модели
print(e.title) # "User"Каждая ошибка в errors() содержит:
{
'type': 'string_too_short', # тип ошибки
'loc': ('username',), # путь к полю
'msg': 'String should have...', # сообщение
'input': 'ab', # входное значение
'ctx': {'min_length': 3}, # контекст ошибки
'url': '...' # ссылка на документацию
}from pydantic import ValidationError
def format_validation_error(exc: ValidationError) -> str:
"""Форматирование ошибки для пользователя"""
messages = []
for error in exc.errors():
field = '.'.join(str(loc) for loc in error['loc'])
msg = error['msg']
messages.append(f"{field}: {msg}")
return '; '.join(messages)
try:
User(username="ab", email="invalid", age=200)
except ValidationError as e:
user_message = format_validation_error(e)
print(user_message)
# username: String should have at least 3 characters;
# email: value is not a valid email address;
# age: Input should be less than or equal to 150from pydantic import BaseModel, Field
class User(BaseModel):
username: str = Field(
min_length=3,
max_length=20,
description="Имя пользователя"
)
# Кастомные сообщения через json_schema_extra
# (работает для JSON Schema, не для валидации)from pydantic import BaseModel, Field, field_validator
from typing import Union
class User(BaseModel):
username: str = Field(min_length=3)
email: str
age: int
@field_validator('username')
@classmethod
def validate_username(cls, v: str) -> str:
if not v.isalnum():
raise ValueError('Username должен содержать только буквы и цифры')
return v
@field_validator('email')
@classmethod
def validate_email(cls, v: str) -> str:
if '@' not in v:
raise ValueError('Неверный формат email')
if not v.endswith(('.com', '.org', '.net', '.ru')):
raise ValueError('Недопустимый домен email')
return v
@field_validator('age')
@classmethod
def validate_age(cls, v: int) -> int:
if v < 0:
raise ValueError('Возраст не может быть отрицательным')
if v > 150:
raise ValueError('Возраст не может быть больше 150')
return v
try:
User(username="ab!", email="invalid", age=-5)
except ValidationError as e:
for error in e.errors():
print(f"{error['loc'][0]}: {error['msg']}")
# username: Username должен содержать только буквы и цифры
# email: Неверный формат email
# age: Возраст не может быть отрицательнымРаспространённые типы ошибок:
# Типы для строк
'string_too_short' # min_length
'string_too_long' # max_length
'string_pattern_mismatch' # pattern
# Типы для чисел
'greater_than' # gt
'greater_than_equal' # ge
'less_than' # lt
'less_than_equal' # le
'multiple_of' # multiple_of
# Типы для коллекций
'too_short' # min_length для list/set
'too_long' # max_length для list/set
# Типы для моделей
'missing' # обязательное поле отсутствует
'extra_forbidden' # лишнее поле при extra='forbid'
# Типы для Union
'union_tag_invalid' # неверный тег дискриминатора
'union_tag_not_found' # поле дискриминатора отсутствуетfrom pydantic import ValidationError
def handle_validation_error(exc: ValidationError) -> dict:
"""Обработка ошибок по типам"""
result = {'errors': [], 'warnings': []}
for error in exc.errors():
error_type = error['type']
field = error['loc'][0]
msg = error['msg']
if error_type == 'missing':
result['errors'].append(f"{field}: обязательное поле")
elif error_type == 'string_too_short':
result['errors'].append(f"{field}: слишком короткое значение")
elif error_type == 'greater_than_equal':
result['errors'].append(f"{field}: значение слишком мало")
elif error_type == 'less_than_equal':
result['errors'].append(f"{field}: значение слишком велико")
else:
result['errors'].append(f"{field}: {msg}")
return result
try:
User(username="ab", email="invalid", age=200)
except ValidationError as e:
handled = handle_validation_error(e)
print(handed)from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from pydantic import ValidationError
app = FastAPI()
@app.exception_handler(ValidationError)
async def validation_exception_handler(
request: Request,
exc: ValidationError
):
errors = []
for error in exc.errors():
errors.append({
'field': '.'.join(str(loc) for loc in error['loc']),
'message': error['msg'],
'type': error['type']
})
return JSONResponse(
status_code=422,
content={
'success': False,
'error': 'validation_error',
'errors': errors
}
)from pydantic import BaseModel, ValidationError
from typing import Tuple, Optional
def safe_validate(model_class, data: dict) -> Tuple[Optional[BaseModel], Optional[str]]:
"""Безопасная валидация с возвратом ошибки"""
try:
instance = model_class(**data)
return instance, None
except ValidationError as e:
error_msg = '; '.join(
f"{err['loc'][0]}: {err['msg']}"
for err in e.errors()
)
return None, error_msg
# Использование
user, error = safe_validate(User, {'username': 'ab', 'email': 'invalid', 'age': 200})
if error:
print(f"Ошибка: {error}")
else:
print(f"Успешно: {user}")import logging
from pydantic import ValidationError
logger = logging.getLogger(__name__)
def log_validation_error(exc: ValidationError, context: str = ""):
"""Логирование ошибки валидации"""
logger.warning(
f"Validation error in {context}: {exc.error_count()} errors",
extra={
'model': exc.title,
'errors': [
{'field': '.'.join(str(loc) for loc in err['loc']),
'type': err['type'],
'msg': err['msg']}
for err in exc.errors()
]
}
)
try:
User(username="ab", email="invalid", age=200)
except ValidationError as e:
log_validation_error(e, context="user_creation")
raiseСоздайте систему валидации с кастомными сообщениями:
from pydantic import BaseModel, Field, field_validator, ValidationError
from typing import Dict, List, Optional
from enum import Enum
class ErrorCode(str, Enum):
REQUIRED = "required"
TOO_SHORT = "too_short"
TOO_LONG = "too_long"
INVALID_FORMAT = "invalid_format"
OUT_OF_RANGE = "out_of_range"
class ValidationResult(BaseModel):
success: bool
errors: Dict[str, str] = {}
def add_error(self, field: str, message: str):
self.errors[field] = message
@property
def is_valid(self) -> bool:
return len(self.errors) == 0
class ValidatedModel(BaseModel):
"""Базовый класс для моделей с кастомными сообщениями"""
@classmethod
def validate_with_messages(cls, data: dict) -> ValidationResult:
"""Валидация с кастомными сообщениями"""
result = ValidationResult(success=True)
try:
cls(**data)
except ValidationError as e:
result.success = False
for error in e.errors():
field = '.'.join(str(loc) for loc in error['loc'])
# Кастомизация сообщений по типу ошибки
if error['type'] == 'missing':
result.add_error(field, f"Поле '{field}' обязательно")
elif error['type'] == 'string_too_short':
result.add_error(field, f"Поле '{field}' слишком короткое")
elif error['type'] == 'string_too_long':
result.add_error(field, f"Поле '{field}' слишком длинное")
else:
result.add_error(field, error['msg'])
return result
# Пример использования:
class UserForm(ValidatedModel):
username: str = Field(min_length=3, max_length=20)
email: str
age: int = Field(ge=0, le=150)
result = UserForm.validate_with_messages(
{'username': 'ab', 'email': 'invalid', 'age': 200}
)
print(result.is_valid) # False
print(result.errors) # {'username': '...', 'email': '...', 'age': '...'}В следующей теме изучим производительность Pydantic и оптимизацию валидации.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.