Pydantic: валидация данных в Python

17 июля 2026
Автор

Антон Ларичев

Что такое Pydantic

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

Pydantic широко используется в:

  • веб-фреймворке FastAPI для валидации запросов и ответов;
  • конфигурации приложений через переменные окружения;
  • десериализации JSON из внешних API;
  • ETL-пайплайнах и обработке данных.

В этой статье рассматривается Pydantic v2, который вышел в 2023 году и принёс значительные изменения в API по сравнению с v1.

Установка

pip install pydantic

Для работы с переменными окружения дополнительно установите:

pip install pydantic-settings

Базовые модели (BaseModel)

Основной строительный блок Pydantic — класс BaseModel. Вы описываете поля модели через аннотации типов, а Pydantic автоматически проверяет типы при создании экземпляра.

from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    email: str
    age: int
    is_active: bool = True

user = User(id=1, name="Иван Петров", email="ivan@example.com", age=30)
print(user)
# id=1 name='Иван Петров' email='ivan@example.com' age=30 is_active=True

Если передать данные неправильного типа, Pydantic попытается их преобразовать. Если преобразование невозможно — выбросит ValidationError:

from pydantic import ValidationError

try:
    user = User(id="не_число", name="Иван", email="ivan@example.com", age=30)
except ValidationError as e:
    print(e)
# 1 validation error for User
# id
#   Input should be a valid integer, unable to parse string as an integer

Сериализация и десериализация

Pydantic умеет создавать модели из словарей и JSON-строк, а также конвертировать их обратно:

data = {"id": 1, "name": "Мария", "email": "maria@example.com", "age": 25}
user = User.model_validate(data)

print(user.model_dump())
# {'id': 1, 'name': 'Мария', 'email': 'maria@example.com', 'age': 25, 'is_active': True}

print(user.model_dump_json())
# {"id":1,"name":"Мария","email":"maria@example.com","age":25,"is_active":true}

json_str = '{"id": 2, "name": "Алексей", "email": "alex@example.com", "age": 35}'
user2 = User.model_validate_json(json_str)

Поле Field

Класс Field позволяет задать дополнительные ограничения и метаданные для поля модели.

from pydantic import BaseModel, Field

class Product(BaseModel):
    name: str = Field(min_length=3, max_length=100, description="Название продукта")
    price: float = Field(gt=0, description="Цена в рублях, должна быть положительной")
    quantity: int = Field(ge=0, le=10000, default=0)
    sku: str = Field(pattern=r'^[A-Z]{3}-\d{4}$', description="Артикул формата ABC-1234")

product = Product(name="Ноутбук", price=75000.0, quantity=5, sku="LPT-0042")
print(product)

Основные параметры Field:

Параметр Описание
default Значение по умолчанию
default_factory Функция для генерации дефолтного значения
min_length / max_length Ограничения длины строки
gt / ge / lt / le Числовые ограничения (больше/не менее/меньше/не более)
pattern Regex-паттерн для строк
description Описание поля (используется в JSON Schema)
alias Альтернативное имя поля при парсинге

Псевдонимы полей (alias)

Когда имена полей во внешнем API отличаются от принятого стиля в Python:

from pydantic import BaseModel, Field

class ApiResponse(BaseModel):
    user_id: int = Field(alias="userId")
    full_name: str = Field(alias="fullName")
    created_at: str = Field(alias="createdAt")

    model_config = {"populate_by_name": True}

data = {"userId": 42, "fullName": "Ольга Смирнова", "createdAt": "2024-01-15"}
response = ApiResponse.model_validate(data)
print(response.user_id)  # 42
print(response.full_name)  # Ольга Смирнова

Встроенные типы данных

Pydantic поддерживает широкий набор типов из стандартной библиотеки и предоставляет свои специализированные типы.

from datetime import datetime, date
from typing import Optional, List, Dict, Tuple
from uuid import UUID
from pydantic import BaseModel, EmailStr, HttpUrl

# pip install pydantic[email] для EmailStr

class Order(BaseModel):
    order_id: UUID
    user_email: EmailStr
    website: HttpUrl
    items: List[str]
    metadata: Dict[str, int]
    coordinates: Tuple[float, float]
    delivery_date: Optional[date] = None
    created_at: datetime = Field(default_factory=datetime.now)

import uuid

order = Order(
    order_id=uuid.uuid4(),
    user_email="customer@example.com",
    website="https://purpleschool.ru",
    items=["Ноутбук", "Мышь"],
    metadata={"weight": 2, "fragile": 1},
    coordinates=(55.7558, 37.6173),
)

Кастомные валидаторы

Для сложной бизнес-логики валидации используются декораторы @field_validator и @model_validator.

Валидатор поля

from pydantic import BaseModel, field_validator, ValidationInfo

class UserRegistration(BaseModel):
    username: str
    password: str
    password_confirm: str
    age: int

    @field_validator("username")
    @classmethod
    def username_must_be_alphanumeric(cls, v: str) -> str:
        if not v.isalnum():
            raise ValueError("Имя пользователя должно содержать только буквы и цифры")
        return v.lower()

    @field_validator("age")
    @classmethod
    def age_must_be_adult(cls, v: int) -> int:
        if v < 18:
            raise ValueError("Регистрация доступна только с 18 лет")
        return v

try:
    user = UserRegistration(
        username="ivan123",
        password="secret",
        password_confirm="secret",
        age=15,
    )
except ValidationError as e:
    print(e)
# 1 validation error for UserRegistration
# age
#   Value error, Регистрация доступна только с 18 лет

Валидатор модели

@model_validator позволяет проверять несколько полей вместе — например, сверить пароль и его подтверждение:

from pydantic import BaseModel, model_validator
from typing import Self

class UserRegistration(BaseModel):
    username: str
    password: str
    password_confirm: str

    @model_validator(mode="after")
    def passwords_match(self) -> Self:
        if self.password != self.password_confirm:
            raise ValueError("Пароли не совпадают")
        return self

try:
    user = UserRegistration(
        username="ivan",
        password="qwerty123",
        password_confirm="qwerty456",
    )
except ValidationError as e:
    print(e)
# 1 validation error for UserRegistration
# Value error, Пароли не совпадают

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

Pydantic поддерживает вложенность моделей любой глубины:

from pydantic import BaseModel
from typing import List, Optional

class Address(BaseModel):
    street: str
    city: str
    zip_code: str
    country: str = "Россия"

class ContactInfo(BaseModel):
    phone: str
    email: str
    address: Optional[Address] = None

class Company(BaseModel):
    name: str
    inn: str
    contact: ContactInfo
    branches: List[Address] = []

company = Company(
    name="ООО Технологии",
    inn="7712345678",
    contact={
        "phone": "+7-999-123-45-67",
        "email": "info@tech.ru",
        "address": {
            "street": "ул. Тверская, 1",
            "city": "Москва",
            "zip_code": "125009",
        },
    },
)

print(company.contact.address.city)  # Москва
print(company.model_dump())

Обратите внимание: вложенные словари Pydantic автоматически конвертирует в соответствующие модели.

Конфигурация модели

model_config позволяет тонко настроить поведение модели:

from pydantic import BaseModel, ConfigDict

class StrictUser(BaseModel):
    model_config = ConfigDict(
        strict=True,               # Запрещает неявное приведение типов
        frozen=True,               # Делает объект иммутабельным
        extra="forbid",            # Запрещает лишние поля
        str_strip_whitespace=True, # Автоматически обрезает пробелы в строках
        validate_default=True,     # Валидирует значения по умолчанию
    )

    name: str
    age: int

try:
    user = StrictUser(name="  Иван  ", age=30, role="admin")
except ValidationError as e:
    print(e)
# 1 validation error for StrictUser
# role
#   Extra inputs are not permitted

user = StrictUser(name="  Мария  ", age=25)
print(user.name)  # "Мария" — пробелы обрезаны

try:
    user.name = "Ольга"  # frozen=True — изменение запрещено
except Exception as e:
    print(e)  # Instance is frozen

Работа с переменными окружения

pydantic-settings позволяет загружать конфигурацию приложения из переменных окружения и .env-файлов:

from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict

class AppSettings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file=".env",
        env_file_encoding="utf-8",
        case_sensitive=False,
    )

    app_name: str = "MyApp"
    debug: bool = False
    database_url: str
    secret_key: str
    max_connections: int = Field(default=10, ge=1, le=100)

settings = AppSettings()
print(settings.database_url)

Пример .env-файла:

DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
SECRET_KEY=super-secret-key-here
DEBUG=true
MAX_CONNECTIONS=20

Дискриминированные объединения (Discriminated Unions)

Когда модель может быть одного из нескольких типов, используют дискриминаторы:

from typing import Annotated, Literal, Union
from pydantic import BaseModel, Field

class Cat(BaseModel):
    pet_type: Literal["cat"]
    name: str
    indoor: bool = True

class Dog(BaseModel):
    pet_type: Literal["dog"]
    name: str
    breed: str

class Parrot(BaseModel):
    pet_type: Literal["parrot"]
    name: str
    can_talk: bool

Pet = Annotated[
    Union[Cat, Dog, Parrot],
    Field(discriminator="pet_type")
]

class Owner(BaseModel):
    owner_name: str
    pet: Pet

owner1 = Owner(owner_name="Андрей", pet={"pet_type": "dog", "name": "Шарик", "breed": "Лабрадор"})
owner2 = Owner(owner_name="Елена", pet={"pet_type": "cat", "name": "Мурка"})

print(type(owner1.pet))  # <class '__main__.Dog'>
print(owner1.pet.breed)  # Лабрадор

JSON Schema

Pydantic автоматически генерирует JSON Schema для ваших моделей — это используется в FastAPI для документации Swagger:

import json
from pydantic import BaseModel, Field

class BlogPost(BaseModel):
    """Статья в блоге"""
    title: str = Field(min_length=5, max_length=200, description="Заголовок статьи")
    content: str = Field(min_length=100, description="Текст статьи")
    tags: list[str] = Field(default=[], max_length=10)
    published: bool = False

schema = BlogPost.model_json_schema()
print(json.dumps(schema, ensure_ascii=False, indent=2))

Вывод:

{
  "title": "BlogPost",
  "description": "Статья в блоге",
  "type": "object",
  "properties": {
    "title": {
      "type": "string",
      "minLength": 5,
      "maxLength": 200,
      "description": "Заголовок статьи"
    },
    ...
  },
  "required": ["title", "content"]
}

Практический пример: парсинг ответа внешнего API

Рассмотрим реальный сценарий — получение и валидация данных от стороннего API:

from pydantic import BaseModel, Field, field_validator
from datetime import datetime
from typing import Optional, List
import json

class GithubUser(BaseModel):
    login: str
    id: int
    avatar_url: str
    name: Optional[str] = None
    company: Optional[str] = None
    public_repos: int = Field(ge=0)
    followers: int = Field(ge=0)
    created_at: datetime

    @field_validator("login")
    @classmethod
    def login_to_lower(cls, v: str) -> str:
        return v.lower()

class GithubRepo(BaseModel):
    id: int
    name: str
    full_name: str
    private: bool
    description: Optional[str] = None
    stargazers_count: int = Field(alias="stargazers_count", ge=0)
    language: Optional[str] = None
    created_at: datetime
    updated_at: datetime

# Симуляция ответа от API
api_response = """
{
    "login": "OctoCat",
    "id": 583231,
    "avatar_url": "https://github.com/images/error/octocat.png",
    "name": "monalisa octocat",
    "company": "GitHub",
    "public_repos": 8,
    "followers": 9000,
    "created_at": "2011-01-25T18:44:36Z"
}
"""

user = GithubUser.model_validate_json(api_response)
print(user.login)       # octocat (приведено к нижнему регистру)
print(user.followers)   # 9000
print(user.created_at)  # 2011-01-25 18:44:36+00:00

Сравнение Pydantic v1 и v2

Если вы работаете с кодом, написанным под Pydantic v1, важно знать ключевые отличия:

Pydantic v1 Pydantic v2
@validator @field_validator
@root_validator @model_validator
.dict() .model_dump()
.json() .model_dump_json()
from_orm(obj) model_validate(obj)
parse_raw(json) model_validate_json(json)
class Config model_config = ConfigDict(...)

Pydantic v2 написан на Rust и работает в 5–50 раз быстрее v1 на реальных сценариях.

Итог

Pydantic — это мощный инструмент, который превращает хрупкую ручную валидацию данных в декларативные, читаемые модели. Ключевые возможности:

  • автоматическое приведение типов и валидация через аннотации;
  • гибкие ограничения через Field;
  • кастомные валидаторы на уровне поля и всей модели;
  • вложенные модели и дискриминированные объединения;
  • сериализация в dict и JSON;
  • автоматическая генерация JSON Schema;
  • конфигурация приложений через переменные окружения.

Освоив Pydantic, вы получаете надёжную основу для работы с FastAPI, внешними API и любыми данными, которые приходят из ненадёжных источников.

Чтобы углубиться в Python и научиться профессионально разрабатывать веб-приложения с FastAPI и Pydantic, приходите на курс PurpleSchool: https://purpleschool.ru/course/fastapi?utm_source=knowledgebase&utm_medium=text&utm_campaign=pydantic-data-validation

Постройте личный план изучения Python до уровня Middle — бесплатно!

Python — часть карты развития Backend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Все гайды по Python

Как отправлять запросы с помощью requests в PythonПочему Python выводит значение без команды printКак работает команда print в PythonВозможности Python для автоматизации задачРабота с JSON в Python на примерахPython get — методы получения данныхКак находить и исправлять ошибки в PythonРабота с данными через API и внешние сервисыСтруктура и оформление кода PythonОсновы Django с PythonПолезные приёмы в Python для повседневной работыИспользование locals в Python для отладкиИнтеграция PHP и PythonКак выполнять HTTPS-запросы в PythonFastAPI Python — быстрый старт: создание REST API с нуляКак работать с API в Python
Ввод целого числа в PythonВедение логов в PythonУдаление данных в Python с помощью removeОбработка исключений с помощью try/except в PythonФункция super() в Python — как вызвать метод родителяСоздание собственных контекстных менеджеров в PythonРабота с символами программирования PythonРабота с переменной X в PythonРабота с классами в PythonКак скачать Python на компьютерПростая программа на Python для начинающихОсновы Python для тех, кто начинаетЧто нового в Python 3Поддерживается ли Python 2 и стоит ли его использоватьPython 1 — с чего начиналась история языкаКоманда python print - полное руководство по выводу данныхПравила именования переменных в PythonПользовательские исключения в PythonОсновы Python coreОписание объектов PythonНаследование классов в Python — основы и примерыМножественное наследование в Python — примеры и MROКонтекстный менеджер with в Python — как работает и зачем нуженКомментарии в Python — однострочные, многострочные и docstringКакой Python выбрать для установкиКак вывести целое число с помощью print в PythonКак установить Python на Windows macOS и LinuxКак пользоваться консолью PythonКак получить последний элемент в PythonКак найти значение в PythonКак настроить PythonКак использовать print для строк в PythonКак работает интерпретатор PythonИнструкция по работе с PythonБлок finally в обработке исключений PythonЦелые числа в PythonАбстрактные классы в Python — ABC и abstractmethod
Pydantic: валидация данных в Python
Загрузка данных PythonУправление проектами на GitHub с PythonСоздание веб-приложений на Flask PythonСоздание бота на PythonСоздание интерфейсов Python QTСоздание игр с PygameСоздание GUI в PythonКак работать со словарями в PythonРабота с библиотеками через Python PackagingРабота со временем в Python при помощи модуля timePython name — особенности переменнойМатематические операции в Python с модулем mathPython listing — что это и как использоватьPytest — тестирование на Python: полное руководствоОбработка изображений с OpenCV PythonNumPy в Python — основы и применение в задачахМашинное обучение с PythonИспользование Anaconda с PythonМодуль contextlib в Python — утилиты для контекстных менеджеровБиблиотеки Python и их применение в проектах
Возврат значений из функции в PythonВложенные функции в PythonСоздание собственных декораторов в PythonРабота с функцией map в PythonЦикл while в Python и примеры использованияОбработка чисел, введённых через input в PythonОсновные операторы в Python с примерамиУсловные выражения if else в Python для начинающихКак выполняется вызов функций call в PythonПродвинутые генераторы в Python — send, throw, close и корутиныПозиционные и именованные аргументы в PythonОбъявление переменных и управление областью видимости в PythonПередача аргументов по ссылке и по значению в PythonПередача аргументов через args и kwargs в PythonОсновные методы Python и примеры их использованияОператор match/case в Python 3.10+ — основы структурного сопоставленияПаттерны match/case в Python — деструктуризация, guard и вложенные шаблоныПрактические примеры match/case в Python — реальные сценарии примененияЛокальные и глобальные переменные в PythonЧасто используемые команды PythonКлючевые слова global и nonlocal в PythonКак создавать функции в PythonКак работает сборщик мусора в PythonКак работает область видимости переменных в PythonКак работает функция callable в PythonКак работает функция any и all в PythonКак проверить тип переменной в PythonКак передать функцию как аргумент в PythonКак использовать функцию isinstance в PythonКак использовать функцию filter в PythonКак использовать функцию filter в PythonКак использовать функцию eval безопасно в PythonКак использовать декораторы в PythonИзменяемые и неизменяемые типы данных в PythonГенераторы и yield в Python — как создавать и использоватьГенераторные выражения в Python — синтаксис и примерыФункции в Python и способы их вызоваФункции как объекты в PythonЧто такое замыкания в PythonЧто делает функция reduce в PythonЧто делает функция id в PythonАргументы по умолчанию в PythonАнонимные функции и lambda в PythonАлгоритмы на Python — примеры и объяснение
Запись данных в PythonУстановка pip в PythonУправление зависимостями requirement в PythonУправление библиотеками с помощью Python PackagingУдаление пробелов с помощью strip в PythonСтруктурирование кода в PythonСоздание исполняемого файла Python в exeРазбор traceback в модуле PythonРазбор site-packages в PythonРазбор Program Files в PythonРабота с Unicode кодировками в PythonРабота с системными функциями Python sysРабота с папкой AppData в PythonРабота с модулем logging в PythonРабота с каталогами в PythonРабота с CSV в PythonВиртуальная среда venv в Python — создание и настройкаКак создать простое приложение на PythonИспользование pip в Python для установки пакетовМодули в Python и организация кода в проектеИмпорт модулей в Python и правила подключенияРабота с файлами в Python пошаговоЧто делает компилятор Python и как он работаетПолучение строки из модуля PythonПодключение файлов в Python с includeПеременные среды в PythonСборка проекта с помощью packaging в PythonНастройка Python сервераИспользование Python на UbuntuИспользование консоли PythonИспользование кодировок в PythonИнициализация пакетов PythonИмпорт модулей PythonИмпорт имен в PythonСреда IDLE Python и базовые возможностиЧтение и запись TXT в PythonЧтение файлов в Python с помощью open file
Удаление элементов из списка PythonТипы данных в Python — обзор и рекомендацииОсновные операции со строками в PythonМетоды str в Python и обработка текстаСписки в Python и их ключевые методыСоздание списков данных в PythonРабота со строками и символами в PythonРабота со столбцами в PythonРабота со списком значений в PythonРабота с таблицами в Python с помощью DataFrameРабота с RFR в PythonРабота с пробелами в PythonРабота с массивами в PythonРабота с кортежами tuple PythonРабота с координатами X и Y в PythonРабота с ключами в PythonРабота с элементами данных PythonРабота с двоичными числами PythonРабота с данными в PythonРабота с данными NumPy PythonРабота с большими числами в PythonРабота с битами в PythonРабота с байтами в PythonЧто такое значение в Python и как его определитьМножества в Python и операции с нимиИспользование range в Python для цикловПроверка на четность в PythonПроверка числа в PythonПреобразование типов в PythonПреобразование списка в строку PythonПреобразование числа в строку в PythonПостроение графиков в PythonОпределение индекса элемента в PythonОкругление чисел в PythonОбъединение списков в Python с помощью zipМножества в PythonМассивы в Python и отличие от списковМассив чисел в PythonКортежи данных в PythonКак вычислить сумму чисел в PythonКак получить остаток от деления в PythonКак найти следующее число в PythonИспользование Unicode в PythonТип int в Python и его особенностиИндекс списка в PythonФункции для работы со строками в PythonЭлементы Python и способы доступа к нимДоступ к элементам массива в PythonДеление чисел в PythonРабота с данными в Python на практикеКак работать с числами в Python
Открыть базу знаний

Лучшие курсы по теме

Иконка молнииНовый
изображение курса

Основы Python

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий