← Назад к вопросам

Для чего нужен Pydantic?

2.0 Middle🔥 241 комментариев
#FastAPI и Flask#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Для чего нужен Pydantic

Pydantic — это мощная библиотека для валидации данных и парсинга конфигураций в Python. Она работает с типами данных, проверяет их корректность и преобразует данные из разных источников (JSON, словари, внешние API).

Основная задача Pydantic

Pydantic решает три основных проблемы:

  1. Валидация данных — проверка корректности
  2. Парсинг данных — преобразование из внешних форматов (JSON, БД)
  3. Типизация — явная типизация с runtime проверками

Проблема без Pydantic

# ❌ Без Pydantic — беспорядок
def create_user(data):
    # Нужно вручную проверять каждое поле
    if not isinstance(data.get('name'), str):
        raise ValueError('Name must be string')
    if not isinstance(data.get('age'), int):
        raise ValueError('Age must be integer')
    if data.get('age') < 0 or data.get('age') > 150:
        raise ValueError('Age must be 0-150')
    if '@' not in data.get('email', ''):
        raise ValueError('Invalid email')
    
    # Нет гарантии, что все поля присутствуют
    user = User(
        name=data['name'],
        age=data['age'],
        email=data.get('email', ''),
        phone=data.get('phone')  # Может быть None
    )
    return user

Решение с Pydantic

from pydantic import BaseModel, Field, EmailStr, validator
from typing import Optional

class User(BaseModel):
    name: str  # Обязательное поле, тип string
    age: int  # Обязательное, тип int
    email: EmailStr  # Автоматическая валидация email
    phone: Optional[str] = None  # Опциональное поле
    
    # Пользовательская валидация
    @validator('age')
    def age_must_be_valid(cls, v):
        if v < 0 or v > 150:
            raise ValueError('Age must be 0-150')
        return v

# ✅ Валидация автоматическая
user = User(name="Alice", age=30, email="alice@example.com")
print(user)  # name='Alice' age=30 email='alice@example.com' phone=None

# ❌ Неправильные данные будут отклонены
try:
    bad_user = User(name="Bob", age="invalid", email="not-an-email")
except ValueError as e:
    print(e)  # 1 validation error for User

Основные возможности Pydantic

1. Валидация типов

from pydantic import BaseModel

class Product(BaseModel):
    name: str
    price: float
    quantity: int

# ✅ Правильные данные
product = Product(name="Laptop", price=999.99, quantity=5)

# ❌ Неправильный тип
try:
    bad = Product(name="Phone", price="expensive", quantity=2)  # price должно быть float
except Exception as e:
    print(e)  # Input should be a valid number

2. Значения по умолчанию и опциональные поля

from pydantic import BaseModel, Field
from datetime import datetime
from typing import Optional

class Blog(BaseModel):
    title: str
    content: str
    author: str
    views: int = 0  # Значение по умолчанию
    created_at: datetime = Field(default_factory=datetime.now)  # Функция для значения
    tags: Optional[list[str]] = None  # Опциональное

blog = Blog(
    title="Python Tips",
    content="Learn Python best practices",
    author="Alice"
)
print(blog.views)  # 0
print(blog.tags)  # None

3. Сложные типы данных

from pydantic import BaseModel
from typing import List, Dict
from datetime import date

class Company(BaseModel):
    name: str
    employees: List[str]  # Список строк
    budget: Dict[str, float]  # Словарь
    founded: date  # Дата автоматически парсится

company = Company(
    name="TechCorp",
    employees=["Alice", "Bob", "Charlie"],
    budget={"R&D": 500000, "Marketing": 200000},
    founded="2020-01-15"  # Строка преобразуется в date
)
print(company.founded)  # 2020-01-15

4. Вложенные модели

from pydantic import BaseModel, EmailStr
from typing import List

class Address(BaseModel):
    street: str
    city: str
    zipcode: str

class Person(BaseModel):
    name: str
    email: EmailStr
    address: Address  # Вложенная модель

# Парсинг из словаря
person = Person(
    name="Alice",
    email="alice@example.com",
    address={  # Автоматически преобразуется в Address
        "street": "123 Main St",
        "city": "New York",
        "zipcode": "10001"
    }
)

print(person.address.city)  # New York

5. Валидаторы для кастомной логики

from pydantic import BaseModel, field_validator, ValidationInfo

class Registration(BaseModel):
    username: str
    password: str
    password_confirm: str
    age: int
    
    @field_validator('username')
    @classmethod
    def username_valid(cls, v):
        if len(v) < 3:
            raise ValueError('Username must be at least 3 characters')
        if not v.isalnum():
            raise ValueError('Username must be alphanumeric')
        return v
    
    @field_validator('password')
    @classmethod
    def password_strong(cls, v):
        if len(v) < 8:
            raise ValueError('Password must be at least 8 characters')
        return v
    
    @field_validator('password_confirm')
    @classmethod
    def passwords_match(cls, v, info: ValidationInfo):
        password = info.data.get('password')
        if v != password:
            raise ValueError('Passwords do not match')
        return v
    
    @field_validator('age')
    @classmethod
    def age_valid(cls, v):
        if v < 18:
            raise ValueError('Must be at least 18')
        return v

# Тестирование
try:
    reg = Registration(
        username="al",  # Слишком короткий
        password="short",  # Слишком слабый
        password_confirm="short",
        age=16  # Слишком молодой
    )
except Exception as e:
    print(e)  # 4 validation errors

6. Парсинг JSON и dict

from pydantic import BaseModel
import json

class Config(BaseModel):
    host: str
    port: int
    debug: bool

# Из словаря
config_dict = {"host": "localhost", "port": 5432, "debug": True}
config = Config(**config_dict)

# Из JSON
json_str = '{"host": "127.0.0.1", "port": 8000, "debug": false}'
config2 = Config.model_validate_json(json_str)

# В JSON
json_output = config2.model_dump_json()
print(json_output)  # {"host":"127.0.0.1","port":8000,"debug":false}

7. Сериализация с контролем

from pydantic import BaseModel, Field, ConfigDict
from typing import Optional

class User(BaseModel):
    model_config = ConfigDict(validate_default=True)
    
    name: str
    password: str = Field(exclude=True)  # Не включать в сериализацию
    email: str = Field(alias='user_email')  # Альтернативное имя
    age: Optional[int] = Field(default=None, gt=0)  # Больше 0

user = User(name="Alice", password="secret", user_email="alice@example.com", age=25)

# Сериализация без пароля
print(user.model_dump())  # {'name': 'Alice', 'email': 'alice@example.com', 'age': 25}

# С указанием алиаса в JSON
print(user.model_dump(by_alias=True))

Применение в FastAPI

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
from typing import Optional

app = FastAPI()

class UserCreate(BaseModel):
    name: str
    email: EmailStr
    age: int

class UserResponse(BaseModel):
    id: int
    name: str
    email: str

@app.post("/users/", response_model=UserResponse)
async def create_user(user: UserCreate):  # Автоматическая валидация
    # user уже валидирован!
    # email проверен
    # age — число
    return UserResponse(id=1, name=user.name, email=user.email)

# Пример запроса с плохими данными
# POST /users/
# {"name": "Bob", "email": "not-an-email", "age": "twenty"}
# Ответ: 422 Unprocessable Entity с описанием ошибок валидации

Pydantic vs dataclass

# Pydantic
from pydantic import BaseModel, EmailStr

class PydanticUser(BaseModel):
    name: str
    email: EmailStr  # Валидация email
    age: int

# dataclass
from dataclasses import dataclass

@dataclass
class DataclassUser:
    name: str
    email: str  # Нет валидации!
    age: int

# Pydantic валидирует автоматически
user1 = PydanticUser(name="Alice", email="alice@example.com", age=30)  # OK
try:
    bad_user = PydanticUser(name="Bob", email="invalid", age="old")  # Ошибка
except Exception:
    pass

# dataclass не валидирует
user2 = DataclassUser(name="Bob", email="invalid", age="old")  # OK! Но неправильно
print(user2.age)  # "old" — строка вместо числа

Best Practices с Pydantic

from pydantic import BaseModel, Field, field_validator, ConfigDict
from typing import Optional, List
from enum import Enum

class StatusEnum(str, Enum):
    active = "active"
    inactive = "inactive"
    pending = "pending"

class Product(BaseModel):
    model_config = ConfigDict(
        validate_default=True,
        str_strip_whitespace=True,  # Убирать пробелы
        use_enum_values=True  # Использовать значения enum
    )
    
    id: int
    name: str = Field(..., min_length=1, max_length=100)
    price: float = Field(..., gt=0)  # Больше 0
    status: StatusEnum = StatusEnum.active
    tags: List[str] = Field(default_factory=list)
    description: Optional[str] = None
    
    @field_validator('name')
    @classmethod
    def name_cannot_be_empty(cls, v):
        if not v.strip():
            raise ValueError('Name cannot be empty')
        return v

# Использование
product = Product(
    id=1,
    name=" Laptop ",  # Будет преобразовано в "Laptop"
    price=999.99,
    status="active",
    tags=["electronics", "computers"]
)

Заключение

Pydantic — это必须 инструмент для:

  • Валидации входных данных (API, формы, конфиги)
  • Парсинга JSON и внешних данных
  • Обеспечения типобезопасности
  • Автоматической документации (OpenAPI)
  • Работы с FastAPI и другими фреймворками

Она экономит часы на написание валидационного кода и делает ваше приложение более надежным и безопасным.