Привет! FastAPI использует Pydantic для валидации данных. Это одна из самых мощных фишек фреймворка — данные автоматически проверяются, преобразуются и документируются.
В этой статье мы разберём:
- Базовую валидацию типов
- Валидацию через Field
- Кастомные валидаторы
- Вложенные модели
- Обработку ошибок валидации
- Что нужно знать перед началом
- Основная часть
- Базовые типы
- Валидация через Field
- Валидация с помощью валидаторов
- Валидация списков
- Валидация вложенных моделей
- Опциональные поля
- Кастомные ошибки
- Полный пример
- Задачи для закрепления
- Нюансы и подводные камни
- Частые ошибки и как их избежать
- Ошибка 1: Забыл обработать ошибки валидации
- Ошибка 2: Неправильный порядок валидаторов
- Ошибка 3: Использование Field без импорта
- Шпаргалка
- Заключение
- КВИЗ
- Что дальше?
Что нужно знать перед началом
Для этого урока тебе понадобится:
- Установленный FastAPI
- Базовое понимание Pydantic моделей
Совет: Валидация данных — это гарантия того, что твоё API получает правильные данные.
Основная часть
Базовые типы
FastAPI автоматически валидирует типы данных на основе аннотаций.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
name: str
age: int
is_active: bool = True
height: float
@app.post("/users/")
def create_user(user: User):
return {"user": user}Ошибка при неверном типе:
# Запрос с неверным типом
{
"name": "Анна",
"age": "25", # Должно быть число
"height": 1.75
}
# Ответ: 422 Unprocessable EntityВалидация через Field
Field добавляет дополнительные проверки.
from pydantic import BaseModel, Field
class User(BaseModel):
name: str = Field(..., min_length=2, max_length=50)
age: int = Field(..., ge=0, le=120)
email: str = Field(..., regex=r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
password: str = Field(..., min_length=6)Параметры Field:
| Параметр | Назначение |
|---|---|
min_length | Минимальная длина строки |
max_length | Максимальная длина строки |
ge | Больше или равно (для чисел) |
le | Меньше или равно (для чисел) |
gt | Больше (для чисел) |
lt | Меньше (для чисел) |
regex | Регулярное выражение |
default | Значение по умолчанию |
description | Описание для документации |
Валидация с помощью валидаторов
Для сложной логики используются кастомные валидаторы.
from pydantic import BaseModel, validator
class User(BaseModel):
name: str
password: str
password_confirm: str
@validator('password')
def validate_password(cls, value):
if len(value) < 8:
raise ValueError('Пароль должен содержать минимум 8 символов')
if not any(char.isdigit() for char in value):
raise ValueError('Пароль должен содержать хотя бы одну цифру')
return value
@validator('password_confirm')
def validate_password_confirm(cls, value, values):
if 'password' in values and value != values['password']:
raise ValueError('Пароли не совпадают')
return valueСовет: Валидаторы выполняются в порядке объявления полей.
Валидация списков
from typing import List
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str
price: float = Field(..., ge=0)
class Order(BaseModel):
items: List[Item]
total: float = Field(..., ge=0)Валидация вложенных моделей
class Address(BaseModel):
street: str = Field(..., min_length=1)
city: str = Field(..., min_length=1)
zip_code: str = Field(..., regex=r"^\d{6}$")
class User(BaseModel):
name: str
age: int = Field(..., ge=0, le=120)
address: AddressПример запроса:
{
"name": "Анна",
"age": 25,
"address": {
"street": "ул. Ленина, 1",
"city": "Москва",
"zip_code": "123456"
}
}Опциональные поля
from typing import Optional
class User(BaseModel):
name: str
age: Optional[int] = None
city: Optional[str] = None
phone: Optional[str] = Field(None, min_length=10, max_length=15)Совет:
Optionalи значение по умолчаниюNoneделают поле необязательным.
Кастомные ошибки
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, validator
app = FastAPI()
class User(BaseModel):
name: str
age: int
@validator('age')
def validate_age(cls, value):
if value < 0:
raise ValueError('Возраст не может быть отрицательным')
if value > 150:
raise ValueError('Слишком большой возраст')
return value
@app.post("/users/")
def create_user(user: User):
return {"user": user}Полный пример
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field, validator
from typing import Optional, List
import re
app = FastAPI()
class Address(BaseModel):
street: str = Field(..., min_length=1)
city: str = Field(..., min_length=1)
zip_code: str = Field(..., regex=r"^\d{6}$")
class UserCreate(BaseModel):
name: str = Field(..., min_length=2, max_length=50)
email: str = Field(..., regex=r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
age: int = Field(..., ge=0, le=120)
password: str = Field(..., min_length=8)
password_confirm: str
phone: Optional[str] = Field(None, min_length=10, max_length=15)
address: Optional[Address] = None
tags: List[str] = []
@validator('password')
def validate_password(cls, value):
if not any(char.isdigit() for char in value):
raise ValueError('Пароль должен содержать хотя бы одну цифру')
if not any(char.isupper() for char in value):
raise ValueError('Пароль должен содержать хотя бы одну заглавную букву')
return value
@validator('password_confirm')
def validate_password_confirm(cls, value, values):
if 'password' in values and value != values['password']:
raise ValueError('Пароли не совпадают')
return value
@validator('phone')
def validate_phone(cls, value):
if value is not None:
# Удаляем все нецифровые символы
clean = re.sub(r'\D', '', value)
if len(clean) < 10 or len(clean) > 15:
raise ValueError('Неверный формат телефона')
return value
@app.post("/users/")
def create_user(user: UserCreate):
return {
"message": "Пользователь создан",
"user": user
}Задачи для закрепления
Задача 1. Создай модель Product с валидацией price >= 0.
Задача 2. Добавь валидацию длины строки name (2–50 символов).
Задача 3. Создай валидатор, проверяющий, что end_date > start_date.
Задача 4. Создай модель с опциональным полем description.
Задача 5. Добавь валидацию email с помощью регулярного выражения.
Ответы:
Задача 1.
class Product(BaseModel):
name: str
price: float = Field(..., ge=0)Задача 2.
class Product(BaseModel):
name: str = Field(..., min_length=2, max_length=50)
price: float = Field(..., ge=0)Задача 3.
class Event(BaseModel):
start_date: str
end_date: str
@validator('end_date')
def validate_dates(cls, value, values):
if 'start_date' in values and value <= values['start_date']:
raise ValueError('end_date должен быть позже start_date')
return valueЗадача 4.
class Product(BaseModel):
name: str
price: float
description: Optional[str] = NoneЗадача 5.
class User(BaseModel):
email: str = Field(..., regex=r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")Нюансы и подводные камни
- FastAPI использует Pydantic для валидации.
- Валидация выполняется автоматически при получении запроса.
Fieldдобавляет правила валидации к полям модели.@validatorпозволяет создавать кастомные проверки.- Валидаторы выполняются в порядке объявления полей.
- Если валидация не пройдена — возвращается 422.
- Можно валидировать отдельные поля и комбинации полей.
Частые ошибки и как их избежать
Ошибка 1: Забыл обработать ошибки валидации
Используй try-except для обработки ValidationError.
Ошибка 2: Неправильный порядок валидаторов
Валидаторы выполняются в порядке объявления полей.
Ошибка 3: Использование Field без импорта
from pydantic import Field
Шпаргалка
| Что нужно | Как пишется |
|---|---|
| Минимальная длина | Field(..., min_length=2) |
| Максимальная длина | Field(..., max_length=50) |
| Больше или равно | Field(..., ge=0) |
| Меньше или равно | Field(..., le=120) |
| Регулярное выражение | Field(..., regex=r"pattern") |
| Кастомный валидатор | @validator('field') |
| Опциональное поле | field: Optional[str] = None |
Заключение
Сегодня мы:
- Изучили валидацию данных в FastAPI
- Использовали Field для проверок
- Создавали кастомные валидаторы
- Валидировали вложенные модели








