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

Зачем FastAPI использует Pyndantic?

2.0 Middle🔥 111 комментариев
#FastAPI и Flask#REST API и HTTP

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

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

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

FastAPI и Pydantic

FastAPI использует Pydantic как основной инструмент для валидации данных, сериализации и документирования API. Это не просто выбор, а архитектурное решение, делающее FastAPI мощным инструментом.

Основные причины

1. Валидация входных данных

Pydantic автоматически валидирует данные при получении запроса:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class User(BaseModel):
    name: str
    age: int
    email: str  # Будет валидирован как email

@app.post('/users')
def create_user(user: User):
    return user

# Если клиент отправит:
# { "name": "John", "age": "not a number" }
# Pydantic вернет 422 ошибку с описанием проблемы
# { "detail": [{"loc": ["body", "age"], "msg": "value is not a valid integer"}] }

Без Pydantic нужно было бы вручную проверять каждое поле:

# ❌ Без Pydantic — грязный код
@app.post('/users')
def create_user(request):
    body = request.json()
    
    if 'name' not in body:
        raise ValueError('name required')
    if not isinstance(body['name'], str):
        raise ValueError('name must be string')
    
    if 'age' not in body:
        raise ValueError('age required')
    if not isinstance(body['age'], int):
        raise ValueError('age must be integer')
    # ... и так для каждого поля

2. Автоматическое преобразование типов

Pydantic не только валидирует, но и преобразует типы:

from pydantic import BaseModel
from datetime import datetime

class Event(BaseModel):
    title: str
    created_at: datetime
    price: float

# Клиент отправляет JSON:
# { "title": "Conference", "created_at": "2025-01-15T10:30:00", "price": "99.99" }

# Pydantic автоматически преобразует:
# Event(
#     title='Conference',
#     created_at=datetime(2025, 1, 15, 10, 30),
#     price=99.99  # Строка → float
# )

3. Документирование через OpenAPI

Pydantic модели используются для генерации OpenAPI схемы:

from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()

class Product(BaseModel):
    id: int
    name: str = Field(..., description='Product name', min_length=1)
    price: float = Field(..., gt=0, description='Price must be positive')
    description: str | None = None

@app.get('/products/{product_id}', response_model=Product)
def get_product(product_id: int):
    return {'id': product_id, 'name': 'Widget', 'price': 29.99}

# FastAPI автоматически генерирует:
# - Swagger UI (http://localhost:8000/docs)
# - ReDoc (http://localhost:8000/redoc)
# - OpenAPI JSON (http://localhost:8000/openapi.json)
# Все с полной информацией о типах и ограничениях

4. Сложная валидация

Pydantic поддерживает сложные проверки:

from pydantic import BaseModel, field_validator, model_validator

class RegistrationForm(BaseModel):
    email: str
    password: str
    password_confirm: str
    age: int
    
    @field_validator('email')
    @classmethod
    def email_must_be_valid(cls, v):
        if '@' not in v:
            raise ValueError('Invalid email')
        return v
    
    @field_validator('password')
    @classmethod
    def password_must_be_strong(cls, v):
        if len(v) < 8:
            raise ValueError('Password must be 8+ chars')
        if not any(c.isupper() for c in v):
            raise ValueError('Must contain uppercase')
        return v
    
    @model_validator(mode='after')
    def passwords_match(self):
        if self.password != self.password_confirm:
            raise ValueError('Passwords do not match')
        return self
    
    @field_validator('age')
    @classmethod
    def age_valid(cls, v):
        if v < 18:
            raise ValueError('Must be 18+')
        return v

# Используется в FastAPI
@app.post('/register')
def register(form: RegistrationForm):
    return {'status': 'registered', 'email': form.email}

5. Сериализация (Response models)

Pydantic также используется для сериализации ответов:

from pydantic import BaseModel, ConfigDict
from sqlalchemy.orm import Session

class UserResponse(BaseModel):
    id: int
    name: str
    email: str
    # password НЕ включаем в ответ!
    
    model_config = ConfigDict(from_attributes=True)

@app.get('/users/{user_id}', response_model=UserResponse)
def get_user(user_id: int, db: Session):
    db_user = db.query(User).filter(User.id == user_id).first()
    # SQLAlchemy ORM объект → Pydantic модель → JSON
    # password автоматически не попадет в JSON!
    return db_user

6. Частичные обновления (PATCH)

from pydantic import BaseModel
from typing import Optional

class UserUpdate(BaseModel):
    name: Optional[str] = None
    email: Optional[str] = None
    age: Optional[int] = None

@app.patch('/users/{user_id}')
def update_user(user_id: int, update: UserUpdate):
    # Pydantic автоматически игнорирует None значения
    # Удобно для PATCH: клиент отправляет только те поля, что нужно изменить
    changes = update.model_dump(exclude_unset=True)
    # changes = {'name': 'NewName'}  если передан только name
    return changes

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

from pydantic import BaseModel

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

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

@app.post('/persons')
def create_person(person: Person):
    # Pydantic валидирует и address, и все его поля
    return person

# Клиент отправляет:
# {
#   "name": "John",
#   "age": 30,
#   "address": {
#     "street": "Main St",
#     "city": "NYC",
#     "country": "USA"
#   }
# }

8. Конфигурация сериализации

from pydantic import BaseModel, ConfigDict
from datetime import datetime

class Product(BaseModel):
    id: int
    name: str
    created_at: datetime
    
    model_config = ConfigDict(
        json_schema_extra={
            'example': {
                'id': 1,
                'name': 'Widget',
                'created_at': '2025-01-15T10:30:00'
            }
        },
        # Сериализовать в snake_case если нужно
        by_alias=True,
        # Включить в JSON только заполненные поля
        exclude_unset=False,
    )

# Появляется в Swagger с примерами

Как это работает вместе

from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()

class CreatePostRequest(BaseModel):
    title: str = Field(..., min_length=5, max_length=200)
    content: str = Field(..., min_length=10)
    published: bool = False

class PostResponse(BaseModel):
    id: int
    title: str
    content: str
    published: bool
    views: int

@app.post('/posts', response_model=PostResponse)
def create_post(post: CreatePostRequest):
    # FastAPI в фоне:
    # 1. Парсит JSON из request.body
    # 2. Валидирует через Pydantic (CreatePostRequest)
    # 3. Если ошибка — возвращает 422 с details
    # 4. Иначе передает валидированный объект в функцию
    # 5. Функция возвращает данные
    # 6. FastAPI сериализует через PostResponse
    # 7. Возвращает JSON клиенту
    
    db_post = {
        'id': 1,
        'title': post.title,
        'content': post.content,
        'published': post.published,
        'views': 0
    }
    return db_post

Сравнение: FastAPI с Pydantic vs Flask без валидации

# Flask (нужна ручная валидация)
@app.route('/users', methods=['POST'])
def create_user():
    data = request.json
    
    # Ручная валидация
    if not data.get('name') or not isinstance(data['name'], str):
        return {'error': 'name required'}, 400
    if not data.get('age') or not isinstance(data['age'], int):
        return {'error': 'age required'}, 400
    # ... еще 10 проверок
    
    return {'status': 'ok'}, 201

# FastAPI (Pydantic валидирует автоматически)
class User(BaseModel):
    name: str
    age: int

@app.post('/users')
def create_user(user: User):
    # Уже валидировано, можно использовать
    return {'status': 'ok'}

Итоговые преимущества

  • DRY — описываешь модель один раз
  • Валидация — автоматическая и полная
  • Документация — генерируется из моделей
  • Типизация — полная, работает с IDE
  • Производительность — Pydantic оптимизирован на Rust (v2)
  • Безопасность — контролируешь, что возвращается клиенту
  • DX — разработчик видит все требования к API в одном месте
Зачем FastAPI использует Pyndantic? | PrepBro