Недавно я обратил внимание, что в русскоязычном интернете очень мало доступной и понятной информации о библиотеке Pydantic, особенно об её обновлённой версии 2. Это кажется странным, потому что Pydantic — это очень простая в освоении библиотека, на изучение которой у вас уйдёт всего несколько часов. Однако, освоив её, вы получите в своё распоряжение мощный инструмент, который можно эффективно использовать в большинстве проектов на Python.
Интересный факт: более 30% всех Python-проектов используют Pydantic, даже если это не всегда заметно на первый взгляд. А такие фреймворки, как FastAPI, вообще строят свою логику на основе Pydantic, делая его неотъемлемой частью своих решений.
Как вы уже поняли из названия, сегодня мы подробно разберём, как применять Pydantic 2 в своих проектах, а главное — зачем это нужно. Мы рассмотрим ключевые концепции, возможности и изменения, появившиеся в новой версии библиотеки.
Что вы узнаете к концу этой статьи:
Что такое Pydantic и его основное назначение.
Понятие модели в Pydantic.
Детально изучим, что такое поля и как встроенные механизмы Pydantic помогают в валидации данных.
Разберем кастомную валидацию полей (через
field_validator
) и глобальную валидацию на уровне модели (model_validator
).Разберемся с вопросом автогенерируемых поле в Pydantic
Погрузимся в настройки моделей с использованием
ConfigDict
, чтобы понять, зачем они нужны и как их эффективно использовать.Рассмотрим механизм наследования моделей, который может существенно оптимизировать и упростить ваш код.
Узнаем, как интегрировать Pydantic с ORM-моделями (на примере SQLAlchemy, хотя это применимо и к другим ORM).
Научимся преобразовывать данные в удобные форматы — словари и JSON-строки.
В итоге: вы познакомитесь с Pydantic версии 2 и освоите основные его методы и подходы.
Краткий теоретический блок
Перед тем, как перейти к практике, давайте познакомимся с основными понятиями и возможностями Pydantic 2. Этот блок будет теоретическим, но крайне важным — все его аспекты мы затем закрепим на практике.
Я постараюсь объяснить всё максимально подробно и "дотошно", чтобы не оставалось вопросов.
Что такое Pydantic?
Pydantic 2 — это библиотека для Python, предназначенная для валидации и трансформации данных. Она помогает разработчикам гарантировать, что входные данные соответствуют установленным правилам и типам, а также обеспечивает их автоматическое преобразование в нужные форматы.
Основные функции Pydantic:
Валидация данных: проверка входных данных на соответствие ожидаемым типам и ограничениям.
Трансформация данных: автоматическое приведение данных к нужным типам и форматам.
Модели в Pydantic
Модели в Pydantic наследуются от класса BaseModel
. Каждая модель описывает набор полей, которые представляют собой структуру данных и условия для их валидации.
Описание полей:
Типизация: Поля в модели описываются с указанием типов, например,
name: str
. Это обеспечивает базовую типовую валидацию.Использование
Field()
: Позволяет аннотировать поля с дополнительными параметрами, такими как значения по умолчанию, ограничения и другие настройки.
Пример базовой модели:
from pydantic import BaseModel, Field
class User(BaseModel):
name: str
email: str = Field(..., alias='email_address')
Валидация полей
Минимальная валидация типов: Используя встроенные типы Python (например,
str
,int
), можно проводить базовую проверку полей.Использование валидаторов: В Pydantic доступны валидаторы, такие как
EmailStr
для проверки email-адресов. Для использования расширенных валидаторов требуется установка дополнительных зависимостей:pydantic[email]
илиpydantic[all]
.
Пример с валидатором:
from pydantic import BaseModel, EmailStr
class User(BaseModel):
name: str
email: EmailStr
Декораторы в Pydantic
В версии Pydantic 2 добавлены новые возможности для валидации и вычисления полей с помощью декораторов.
@field_validator
— заменяет старый@validator
и позволяет добавлять кастомную логику валидации поля. Вызывается при создании или изменении модели.
Пример использования @field_validator
:
from pydantic import BaseModel, field_validator
class User(BaseModel):
age: int
@field_validator('age')
def check_age(cls, value):
if value < 18:
raise ValueError('Возраст должен быть больше 18 лет')
return value
@computed_field
— поле, которое вычисляется на основе других данных в модели. Его можно использовать для автоматической генерации значений, а также для валидации.
Пример использования @computed_field
:
from pydantic import BaseModel, computed_field
class User(BaseModel):
name: str
surname: str
@computed_field
def full_name(self) -> str:
return f"{self.name} {self.surname}"
Работа с ORM
Pydantic поддерживает интеграцию с ORM (например, SQLAlchemy) для валидации и трансформации данных, полученных из базы данных.
Чтобы настроить модель для работы с ORM, используйте параметр
ConfigDict
с флагомfrom_attributes=True
.
Пример:
from datetime import date
from pydantic import BaseModel, ConfigDict
class User(BaseModel):
id: int
name: str = 'John Doe'
birthday_date: date
config = ConfigDict(from_attributes=True)
Для создания модели Pydantic из объекта ORM используется метод
from_orm
.
Пример:
user = User.from_orm(orm_instance)
Методы для работы с данными
dict()
/model_dump()
— преобразуют модель в словарь Python. В версии 2 методmodel_dump()
стал аналогомdict()
.
Пример:
data = user.model_dump()
json()
/model_dump_json()
— преобразуют модель в JSON-строку. В новой версии методmodel_dump_json()
заменяет старыйjson()
.
Пример:
json_data = user.model_dump_json()
Передача данных в модель
Именованные аргументы: Поля модели могут задаваться напрямую при создании экземпляра.
Пример:
user = User(name="Oleg", age=30)
Распакованные словари: Можно передать значения полей с помощью распаковки словарей
**
.
Пример:
user_data = {"name": "Oleg", "age": 30} user = User(**user_data)
К этому моменту вы должны были усвоить, что Pydantic 2 — это мощный инструмент для работы с данными, который поддерживает валидацию и трансформацию различных типов данных, а также интеграцию с ORM и кастомные валидаторы.
Теперь закрепим все на практике.
Приступаем к практике Pydantic 2
Для начала создайте новый проект в вашей любимой IDE, например, PyCharm. Если у вас ещё нет установленного Pydantic, выполните установку, используя следующую команду:
pip install -U pydantic[all]
Что делает эта команда:
-U
— обновляет Pydantic до последней версии, если он уже установлен, или просто установит последнюю доступную версию, если Pydantic отсутствует.[all]
— этот флаг добавляет всевозможные дополнительные модули и валидаторы, которые могут быть полезны в проекте, такие как валидатор email-адресов и другие расширенные функции.
Теперь, когда установка завершена, мы готовы приступить к практике по Pydantic 2.
Опишем первую модель Pydantic
from datetime import date
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
birthday_date: date
В этой модели Pydantic мы описали три обязательных поля: id
, name
и birthday_date
(день рождения). Эти поля будут валидироваться автоматически.
Давайте теперь создадим объект класса User, передав необходимые параметры.
oleg = User(id=1,
name='Oleg',
birthday_date=date(year=1993, month=2, day=19))
Тут все понятно и мы пока никаких отличий от работы с обычным классом не видим. Сейчас получим данные в виде словаря и JSON-строки, а после я покажу вам несколько трюков.
Для трансформации модели в Python-словарь (dict) можно использовать метод dict() или его полный аналог model_dump().
Для трансформации в JSON-строку можно использовать метод json() или model_dump_json().
Выполним трансформацию, используя каждый из методов.
to_dict = oleg.model_dump()
to_json = oleg.model_dump_json()
print(to_dict, type(to_dict))
print(to_json, type(to_json))
А что будет если мы создадим объект другого пользователя и передадим данные следующим образом?
alex = User(id="2",
name='Алексей',
birthday_date="1990-11-22")
Первое что приходит в голову: «Конечно ошибка!». Что логично, ведь мы передали ID-пользователя строкой, когда ожидается целое число, а в поле birthday_date мы передали строку вместо объекта даты, но мы проверим.
alex = User(id="2",
name='Алексей',
birthday_date="1990-11-22")
to_dict = alex.model_dump()
to_json = alex.model_dump_json()
print(to_dict, type(to_dict))
print(to_json, type(to_json))
Этим простым примером я продемонстрировал, как Pydantic трансформирует данные самостоятельно в нужный нам формат. Эта особенность данной библиотеки бывает мега-полезной в самых разных случаях и я думаю, что сейчас вы самостоятельно сможете представить в каких.
Теперь создадим ещё одного пользователя и опишем его следующим образом:
dima = User(id="3",
name=156,
birthday_date="1990-11-22")
to_dict = dima.model_dump()
to_json = dima.model_dump_json()
print(to_dict, type(to_dict))
print(to_json, type(to_json))
По логике вещей Сейчас всё должно работать правильно, не так ли? Мы уже проверили, что строка преобразуется в целое число, а дата, переданная в виде строки, — в формат даты. Давайте убедимся в этом.
Неожиданно, но мы получили ошибку. Давайте разберемся почему.
Почему произошла ошибка?
Pydantic выполняет валидацию данных, а также может автоматически преобразовывать типы в некоторых случаях. Однако такие преобразования происходят только для данных, которые можно безопасно и однозначно привести к ожидаемому типу. Например:
Если модель ожидает int, а мы передаём строку «123», Pydantic может преобразовать её в int.
Если модель ожидает str, но вы передаете int, как в нашем случае, Pydantic не пытается выполнить автоматическое приведение типов, так как не всегда очевидно, что целое число можно корректно интерпретировать как строку.
Выходов из этой ситуации две:
Стараться передавать корректные данные для неоднозначных случаев
Использовать валидаторы полей, которые будут выполнять трансформацию данных внутри модели до основной проверки типов и преобразовать в строку.
Валидатор полей (field_validator) в Pydantic
В предыдущих версиях Pydantic этот декоратор назывался validate
. Теперь, в новых версиях, его название изменилось на field_validator
, что лучше отражает его предназначение.
Назначение декоратора
Декоратор field_validator
служит для проверки корректности заполнения полей модели Pydantic. Помимо валидации, его можно использовать для преобразования данных перед их сохранением в модели.
Импорт декоратора
Для начала импортируем необходимые элементы из Pydantic:
from pydantic import BaseModel, field_validator
from datetime import date
Пример использования
Создадим модель User
, добавив валидатор для поля name
:
class User(BaseModel):
id: int
name: str
birthday_date: date
@field_validator('name', mode='before')
def validate_name(cls, v):
return str(v)
Декоратор field_validator всегда принимает один обязательный аргумент - название поля, которое необходимо валидировать. Второй аргумент, который предпочтительно указывать, mode.
Здесь важно отметить использование параметра mode='before'
. Это указывает Pydantic на необходимость выполнения валидации и преобразования данных до создания экземпляра модели, а не после. Другой вариант - mode='after'.
Сам метод приничает само поле (v) и далее начинает его валидировать. Выше рассмотрен простой пример, но совсем скоро мы его усложним.
Теперь наш валидатор для поля name
будет автоматически преобразовывать любые переданные значения в строку.
Важно. Скорее всего ваш IDE будет ругаться на описание cls в методе валидатора. Это не ошибка, но, для того чтоб избежать назойливого warning можно описывать этот тип декораторов следующим образом:
@field_validator('name', mode='before')
@classmethod
def validate_name(cls, v):
return str(v)
Проверим модель
Попробуем создать экземпляр модели с такими данными:
user_data = {'id': 3, 'name': '156', 'birthday_date': '1990-11-22'}
user = User(**user_data)
print(user.dict())
Результат:
{'id': 3, 'name': '156', 'birthday_date': datetime.date(1990, 11, 22)}
Ошибок нет, данные успешно преобразовались и сериализовались в JSON. Однако здесь есть одна проблема — наш валидатор использован только для преобразования данных, а не для их проверки.
Неожиданный результат
Допустим, мы передадим такие данные:
dima = User(
id="3",
name=("Коля", True, False, 0, 19933),
birthday_date="1990-11-22"
)
Результат:
{'id': 3, 'name': "('Коля', True, False, 0, 19933)", 'birthday_date': datetime.date(1990, 11, 22)}
Ошибки нет, но явно что-то не так — вместо строки в поле name
мы получили кортеж, который был просто преобразован в строку. Очевидно, что такая ситуация недопустима.
Исправление валидации
Для того чтобы избежать подобной ситуации, мы можем добавить строгую проверку типов данных в валидаторе:
@field_validator('name', mode='before')
def validate_name(cls, v):
if isinstance(v, int):
return str(v)
elif isinstance(v, str):
return v
else:
raise ValueError("Имя должно быть строкой или числом")
Теперь наш валидатор проверяет, является ли переданное значение строкой или числом. Если значение не подходит, вызывается исключение ValueError
.
Проверка исправления
Попробуем снова передать некорректные данные:
dima = User(
id="3",
name=("Коля", True, False, 0, 19933),
birthday_date="1990-11-22"
)
Результат:
Произошла ошибка: 1 validation error for User
name
Value error, Имя должно быть строкой или числом [type=value_error, input_value=('Коля', True, False, 0, 19933), input_type=tuple]
For further information visit https://errors.pydantic.dev/2.9/v/value_error
Теперь валидатор сработал правильно — он остановил создание объекта и вернул ошибку, указав, что переданное значение некорректно.
Таким образом, field_validator
может быть использован не только для преобразования данных, но и для строгой проверки их корректности. В данном примере мы реализовали проверку, которая позволяет избежать некорректных данных в модели, и вернули соответствующую ошибку, если условия не соблюдены.
Валидатор модели (model_validator) в Pydantic
В старых версиях Pydantic этот декоратор назывался root_validator
. Его основное назначение — валидация модели в целом, после того как все поля уже прошли индивидуальную проверку. Это позволяет выполнять комплексные проверки, зависящие от нескольких полей модели одновременно.
Основные особенности декоратора @model_validator:
Выполняется после валидации отдельных полей.
Имеет доступ ко всем полям модели одновременно.
Может изменять значения полей или всю модель целиком.
Используется для сложных проверок, связанных с несколькими полями.
Пример использования model_validator
Расширим наш класс User
, добавив валидатор модели для проверки возраста пользователя и установления имени по умолчанию:
from pydantic import BaseModel, field_validator, model_validator
from datetime import date
class User(BaseModel):
id: int
name: str
birthday_date: date
@field_validator('name', mode='before')
def validate_name(cls, v):
if isinstance(v, int):
return str(v)
elif isinstance(v, str):
return v
else:
raise ValueError("Имя должно быть строкой или числом")
@model_validator(mode='after')
def check_age(self):
today = date.today()
age = today.year - self.birthday_date.year - (
(today.month, today.day) < (self.birthday_date.month, self.birthday_date.day))
if age < 18:
raise ValueError("Пользователь должен быть старше 18 лет")
if age > 120:
raise ValueError("Возраст не может превышать 120 лет")
return self
@model_validator(mode='after')
def set_default_name(self):
if self.name.strip() == '':
self.name = f"User_{self.id}"
return self
В этом примере:
Метод
check_age
проверяет, что возраст пользователя больше 18 лет, но меньше 120 лет. Эта проверка требует доступа к полюbirthday_date
и текущей дате, поэтому она реализована как валидатор модели.Метод
set_default_name
устанавливает имя по умолчанию, если полеname
пустое. Этот валидатор использует несколько полей (имя и идентификатор), поэтому также реализован на уровне модели.
Оба валидатора используют режим after
, что означает, что они выполняются после валидации отдельных полей.
Пример использования:
try:
user = User(id=1, name="John", birthday_date=date(2000, 1, 1))
print(user)
except ValueError as e:
print(f"Ошибка: {e}")
try:
user = User(id=2, name="", birthday_date=date(2010, 1, 1))
print(user)
except ValueError as e:
print(f"Ошибка: {e}")
try:
user = User(id=3, name="Alice", birthday_date=date(1900, 1, 1))
print(user)
except ValueError as e:
print(f"Ошибка: {e}")
Этот пример демонстрирует, как @model_validator
помогает выполнять сложные проверки и изменять модель после валидации отдельных полей.
Вычисляемые поля (computed_field)
Декоратор @computed_field
позволяет создавать поля, которые вычисляются "на лету" при доступе к ним. Это полезно, когда нужно автоматически получать значения, основанные на других полях модели.
Пример с вычисляемыми полями:
Добавим вычисляемые поля full_name
и age
в наш класс User
:
from pydantic import BaseModel, computed_field
from datetime import date
from dateutil.relativedelta import relativedelta
class User(BaseModel):
id: int
name: str
surname: str
birthday_date: date
@computed_field
def full_name(self) -> str:
return f"{self.name} {self.surname}"
@computed_field
def age(self) -> str:
today = date.today()
delta = relativedelta(today, self.birthday_date)
return f"{delta.years} лет, {delta.months} месяцев и {delta.days} дней"
Поле
full_name
вычисляется через объединение имени и фамилии.Поле
age
рассчитывает возраст пользователя в годах, месяцах и днях с использованием библиотеки модуляrelativedelta
библиотекиdateutil
.
Пример использования вычисляемых полей:
alex = User(id=1, name="Алексей", surname="Яковенко", birthday_date="1993-02-19")
print(alex.dict())
Результат:
{'id': 1, 'name': 'Алексей', 'surname': 'Яковенко', 'birthday_date': datetime.date(1993, 2, 19), 'full_name': 'Алексей Яковенко', 'age': '31 лет, 7 месяцев и 28 дней'}
Как видите, вычисляемые поля работают автоматически, обеспечивая удобное получение производных значений.
Декораторы @model_validator
, @field_validator
и @computed_field
позволяют гибко и эффективно управлять валидацией данных в моделях Pydantic, а также добавлять вычисляемые поля. Валидация на уровне модели полезна для комплексных проверок, в то время как вычисляемые поля облегчают работу с производными значениями, не требуя дополнительной логики в коде.
Вычисляемые поля можно комбинировать с model_validate. В таком формате. Мы создаем новое поле а после, при помощи model_validate можно проверить все ли у нас корректно. Кроме того, проверку можно сделать и на этапе создания самого вычисляемого поля.
Для закрепления этого блока потренеруйтесь самостоятельно в использовании декораторов: model_validate, field_validate и computed_field.
Аннотируемые поля в Pydantic 2: использование функции Field
Pydantic 2 предлагает разработчикам удобный и мощный способ работы с полями моделей данных, и одним из ключевых инструментов для этого является функция Field
. Она позволяет детализировать поведение полей: задавать значения по умолчанию, добавлять метаданные, настраивать валидацию и даже документировать модель. В этой главе я расскажу о том, как использовать Field
для настройки моделей в Pydantic 2.
Импортируем необходимые модули:
from pydantic import BaseModel, Field
Что такое Field?
Функция Field
позволяет добавлять к полям моделей метаданные и настройки, которые Pydantic использует для валидации, сериализации и документирования. Вот основные параметры, которые можно передавать в Field
:
default
: устанавливает значение по умолчанию для поля.default_factory
: функция, возвращающая значение по умолчанию.alias
: альтернативное имя поля для сериализации и десериализации.title
: заголовок поля для документации.description
: описание поля для документации.exclude
: исключает поле из сериализации.repr
: определяет, будет ли поле включено в строковое представление модели.
Пример простого использования Field
Допустим, у нас есть модель пользователя, где нужно задать значения по умолчанию и добавить немного метаданных:
class User(BaseModel):
id: int = Field(default=1, description="Уникальный идентификатор пользователя")
name: str = Field(default="John Doe", title="Имя пользователя", description="Полное имя")
role: str = Field(default="user", alias="user_role", description="Роль пользователя в системе")
В этом примере:
Поле
id
имеет значение по умолчанию1
и описание для документации.Поле
name
описывает имя пользователя с указанием заголовка для документации.Поле
role
имеет алиасuser_role
, который будет использоваться при сериализации и десериализации данных.
Валидация полей
Функция Field
также позволяет задавать различные ограничения для значений. Например, можно определить минимальные и максимальные значения для числовых полей, или ограничить длину строк.
Пример валидации через Field
class Product(BaseModel):
price: float = Field(gt=0, description="Цена должна быть больше нуля")
name: str = Field(min_length=2, max_length=50, description="Название продукта должно быть от 2 до 50 символов")
Здесь:
Поле
price
должно быть больше нуля.Поле
name
имеет ограничения по длине строки: минимум 2 символа, максимум 50.
Вот подробное описание часто используемых встроенных валидаторов:
1. gt, ge, lt, le — для числовых ограничений
Эти валидаторы применяются для ограничения числовых значений (целые числа, числа с плавающей точкой и другие типы, поддерживающие арифметику).
gt
(greater than): Проверяет, что значение больше указанного числа.
Пример:Field(gt=0)
— значение должно быть больше нуля.ge
(greater than or equal): Проверяет, что значение больше или равно указанному числу.
Пример:Field(ge=1)
— значение должно быть не меньше единицы.lt
(less than): Проверяет, что значение меньше указанного числа.
Пример:Field(lt=100)
— значение должно быть меньше ста.le
(less than or equal): Проверяет, что значение меньше или равно указанному числу.
Пример:Field(le=10)
— значение должно быть не больше десяти.
Эти валидаторы полезны для задания диапазонов значений, например, проверка возраста, цены, рейтинга и любых других числовых данных, которые имеют верхние и нижние границы.
Пример с числовыми ограничениями:
class Product(BaseModel):
price: float = Field(gt=0, le=10000, description="Цена должна быть положительной и не превышать 10 000")
rating: int = Field(ge=1, le=5, description="Рейтинг должен быть от 1 до 5")
Здесь:
Поле
price
должно быть больше 0 и не больше 10 000.Поле
rating
должно находиться в диапазоне от 1 до 5.
2. max_length, min_length — для строковых полей
Эти валидаторы используются для ограничения длины строк, что важно для валидации текстовых данных, таких как имена пользователей, описания и другие поля.
min_length
: Задаёт минимальное количество символов, которое должно быть в строке.
Пример:Field(min_length=3)
— строка должна быть не короче 3 символов.max_length
: Задаёт максимальное количество символов, которое может содержать строка.
Пример:Field(max_length=100)
— строка должна содержать не более 100 символов.
Пример использования валидаторов длины строк:
class User(BaseModel):
username: str = Field(min_length=3, max_length=20, description="Имя пользователя должно содержать от 3 до 20 символов")
bio: str = Field(max_length=300, description="Описание профиля не должно превышать 300 символов")
Здесь:
Поле
username
должно содержать от 3 до 20 символов.Поле
bio
ограничено 300 символами.
3. regex — для проверки по регулярному выражению
Этот валидатор позволяет проверять строковые значения на соответствие регулярному выражению. Регулярные выражения (regex) дают возможность гибко описывать допустимые форматы строк — например, для проверки адресов электронной почты, телефонных номеров, форматов дат и т. д.
regex
: Задаёт регулярное выражение, которому должна соответствовать строка.Пример:Field(regex=r"[^@]+@[^@]+\.[^@]+")
— проверка, что строка является корректным email-адресом.
Пример использования regex:
class User(BaseModel):
email: str = Field(regex=r"[^@]+@[^@]+\.[^@]+", description="Электронная почта должна быть в корректном формате")
phone_number: str = Field(regex=r"^\+\d{1,3}\s?\d{4,14}$", description="Номер телефона должен быть в формате +123456789")
Здесь:
Поле
email
должно соответствовать шаблону email-адреса (набор символов до@
, доменное имя, точка и домен).Поле
phone_number
должно соответствовать формату международного телефонного номера.
Динамические значения по умолчанию
Иногда требуется генерировать значения для полей динамически. Для этого используется параметр default_factory
, который принимает функцию для генерации значения.
Пример с default_factory
from uuid import uuid4
class Item(BaseModel):
id: str = Field(default_factory=lambda: uuid4().hex)
Здесь каждое поле id
будет автоматически получать уникальный идентификатор при создании нового объекта модели Item
.
Использование алиасов
Для совместимости с внешними API или для удобства чтения кода можно задавать алиасы полей. Алиас — это альтернативное имя для поля, которое будет использоваться при сериализации или десериализации.
Пример с алиасами
class User(BaseModel):
username: str = Field(alias="user_name")
Когда вы задаёте алиас для поля, это поле будет иметь одно имя в вашем Python-коде, но при отправке данных в формате JSON или чтении из него оно будет использовать другое имя, указанное в качестве алиаса.
В этом примере поле username
будет сериализоваться и десериализоваться под именем user_name
.
Исключение полей из сериализации
Иногда необходимо скрывать определённые поля при сериализации данных — например, чтобы не отправлять конфиденциальную информацию, такую как пароли.
Пример исключения полей
class User(BaseModel):
password: str = Field(exclude=True)
В данном примере поле password будет исключено из сериализованного представления объекта модели.
Это значит, что при сериализации объекта модели (например, при преобразовании его в JSON или словарь), поле password
не будет включено в результат. Таким образом, вы можете скрыть конфиденциальную информацию, такую как пароли, от внешних систем или клиентов.
Настройка строкового представления модели
Можно контролировать, какие поля будут отображаться в строковом представлении модели, используя параметр repr
.
Пример настройки представления
class Config(BaseModel):
debug_mode: bool = Field(repr=False)
Строковое представление объекта модели — это то, что возвращается при вызове repr()
или str()
. Например, когда вы хотите увидеть состояние объекта или отладить код, вы можете вывести его строковое представление.
Параметр repr=False
позволяет исключить определённые поля из этого представления, чтобы они не отображались при выводе объекта, даже если они присутствуют в модели.
В нашем примере поле debug_mode
не будет отображаться в строковом представлении модели, что может быть полезно для скрытия технической информации.
Расширенные возможности через Annotated
Теперь, когда мы разобрались с базовыми возможностями Field
, можно перейти к использованию аннотаций через Annotated
. Этот способ позволяет добавить метаданные и валидацию более гибко и точно.
Пример использования Annotated
from typing_extensions import Annotated
class User(BaseModel):
id: Annotated[int, Field(gt=0)]
name: Annotated[str, Field(min_length=2, max_length=50)]
email: Annotated[str, Field(regex=r"[^@]+@[^@]+\.[^@]+")]
role: Annotated[str, Field(default="user")]
Здесь:
Поле
id
должно быть больше нуля.Поле
name
ограничено длиной от 2 до 50 символов.Поле
email
должно соответствовать регулярному выражению, проверяющему формат электронной почты.Поле
role
имеет значение по умолчанию —"user"
.
В настоящее время наиболее предпочтительным подходом считается использование Annotated в водробном описании полей.
Функция Field
в Pydantic 2 предоставляет разработчикам гибкие инструменты для настройки полей моделей. С её помощью можно точно настраивать валидацию, задавать значения по умолчанию, использовать алиасы и добавлять метаданные для документации. А использование Annotated
позволяет сделать этот процесс ещё более мощным и удобным.
Эти возможности помогают создавать структурированные и безопасные модели данных, обеспечивая лёгкую интеграцию с внешними API и чёткую валидацию входящих данных.
Конфигурация моделей в Pydantic 2
В Pydantic 2 конфигурация моделей теперь задаётся через ConfigDict, а не через старый формат с классом Config
. Это важное изменение, которое упрощает настройку и делает её более гибкой.
Как это выглядит теперь:
Вместо того чтобы писать:
class MyModel(BaseModel):
class Config:
from_attributes = True
теперь используем ConfigDict:
from pydantic import BaseModel, ConfigDict
class MyModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
Основные опции ConfigDict
from_attributes=True
— позволяет создавать объект модели напрямую из атрибутов Python-объектов (например, когда поля модели совпадают с атрибутами другого объекта). Чаще всего опция применяется для преобразования моделей ORM к моделям Pydantic.str_to_lower, str_to_upper
- преобразование всех строк модели в нижний или верхний регистрstr_strip_whitespace
- cледует ли удалять начальные и конечные пробелы для типов str (аналог strip)str_min_length, str_max_length
- задает максимальную и минимальную длину строки для всех строковых полейuse_enum_values
- cледует ли заполнять модели значениями, выбранными из перечислений, вместо того чтобы использовать необработанные значения? Это часто требуется при работе с моделями ORM, в которых колонки определены как перечисления (ENUM).
Почему это важно?
Потому что оно делает конфигурацию моделей более явной, удобной и гибкой. Использование ConfigDict позволяет определять параметры моделей напрямую через словарь, избегая необходимости создания вложенных классов, что упрощает процесс настройки и делает его более интуитивно понятным. Это особенно полезно при работе с большими и сложными моделями данных, где требуется высокая степень кастомизации.
Теперь вы можете легко определить и изменять параметры моделей, не погружаясь в громоздкие конструкции иерархий. Это способствует лучшей читаемости и поддерживаемости кода, ускоряя процесс разработки.
Для более подробного изучения всех параметров и методов ConfigDict, рекомендуем обратиться к официальной документации где вы найдете полное описание возможностей и примеры использования.
Наследование в Pydantic 2
Наследование в Pydantic позволяет создавать модели, которые могут переопределять или расширять атрибуты и методы своих родительских моделей. Это делает код более гибким и позволяет избежать дублирования.
Пример работы наследования в Pydantic:
from pydantic import BaseModel
class ParentModel(BaseModel):
name: str
age: int
class ChildModel(ParentModel):
school: str
parent = ParentModel(name="Alex", age=40)
child = ChildModel(name="Bob", age=12, school="Greenwood High")
В этом примере ChildModel
наследует поля name
и age
от ParentModel
и добавляет свое поле school
.
Преимущества:
Повторное использование кода: общие поля и методы можно определить в базовой модели.
Расширяемость: дочерние модели могут добавлять новые поля или методы.
В Pydantic, когда вы создаете базовый класс с настройками, эти настройки и атрибуты могут наследоваться дочерними классами. Это позволяет избежать дублирования кода и облегчает поддержание консистентности.
Например, можно определить общие параметры в базовой модели и затем использовать наследование для создания специализированных моделей, которые автоматически получают все свойства и настройки базового класса, но могут добавлять свои уникальные атрибуты. Такой подход делает код более чистым и управляемым.
Связка Pydantic 2 с ORM на примере SQLAlchemy
Если вы следите за моими публикациями на Хабре, то знаете о серии статей, посвященных работе с Python-фреймворком SQLAlchemy. В предыдущей статье "Асинхронный SQLAlchemy 2: пошаговый гайд по управлению сессиями, добавлению и извлечению данных с Pydantic" мы подробно рассмотрели вопрос интеграции SQLAlchemy с Pydantic
Сегодня я кратко расскажу о ключевых концепциях, объясняющих, почему SQLAlchemy нуждается в Pydantic, и приведу практический пример.
Зачем SQLAlchemy нужен Pydantic?
При работе с SQLAlchemy в ORM-стиле мы сталкиваемся с одним существенным неудобством: данные возвращаются в виде объектов моделей таблиц, что не всегда удобно для дальнейшей обработки. Разработчики обычно предпочитают работать с данными в формате JSON или Python-словарей. Именно здесь на помощь приходит Pydantic.
Принцип интеграции SQLAlchemy и Pydantic
Процесс связывания SQLAlchemy и Pydantic можно описать следующими шагами:
Описание модели таблицы в SQLAlchemy
Создание Pydantic-модели для работы с полученными данными
Запрос данных из таблицы через SQLAlchemy
Преобразование объекта SQLAlchemy в объект Pydantic
Использование методов
model_dump
илиmodel_dump_json
для получения данных в нужном формате
Практический пример
Рассмотрим пример интеграции SQLAlchemy и Pydantic:
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
from pydantic import BaseModel, ConfigDict
# Шаг 1: Описание модели SQLAlchemy
Base = declarative_base()
class UserORM(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
email = Column(String)
# Шаг 2: Создание Pydantic-модели
class UserPydantic(BaseModel):
id: int
name: str
email: str
model_config = ConfigDict(from_attributes=True)
# Настройка базы данных и сессии
engine = create_engine("sqlite:///example.db")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
# Шаг 3: Запрос данных
def get_user(user_id: int):
with Session() as session:
user = session.select(UserORM).filter_by(id = user_id).scalar_one_or_none()
# Шаг 4: Преобразование объекта SQLAlchemy в Pydantic
if user:
user_pydantic = UserPydantic.from_orm(user)
# Шаг 5: Получение данных в нужном формате
return user_pydantic.dict()
return None
# Пример использования
user_data = get_user(1)
print(user_data)
Здесь мы видим использование методов from_orm()
для создания Pydantic-модели из ORM-объекта и dict()
для преобразования Pydantic-модели в словарь.
Современный подход
С выходом новых версий Pydantic (особенно 2.0+) рекомендуется использовать следующий стиль:
def get_user(user_id: int):
with Session() as session:
user = session.select(UserORM).filter_by(id = user_id).scalar_one_or_none()
# Шаг 4: Преобразование объекта SQLAlchemy в Pydantic
if user:
user_pydantic = UserPydantic.model_validate(user)
# Шаг 5: Получение данных в нужном формате
return user_pydantic.model_dump()
return None
Ключевые изменения и их обоснование
-
from_orm()
→model_validate()
from_orm()
теперь является алиасом дляmodel_validate()
.model_validate()
более универсален и может использоваться не только с ORM-объектами.Рекомендуется использовать
model_validate()
для большей гибкости и соответствия современным стандартам Pydantic.
-
dict()
→model_dump()
dict()
стал алиасом дляmodel_dump()
.model_dump()
предоставляет более широкие возможности настройки вывода.Использование
model_dump()
делает код более явным и соответствующим новому API Pydantic.
Хотя оба варианта кода функциональны, переход на новые методы (model_validate()
и model_dump()
) рекомендуется по следующим причинам:
Соответствие современным стандартам и рекомендациям Pydantic.
Улучшенная читаемость кода и явное указание намерений.
Возможность использования дополнительных опций, доступных в новых методах.
Подготовка кода к будущим обновлениям библиотеки.
Использование from_attributes в model_validate
В Pydantic 2.0+ метод model_validate
стал более гибким и удобным. Вы можете напрямую указать from_attributes=True
при вызове метода:
user_pydantic = UserPydantic.model_validate(user, from_attributes=True)
Это позволяет динамически контролировать, будут ли атрибуты объекта использоваться для создания модели, без необходимости изменения конфигурации самой модели.
Другие полезные атрибуты model_validate
Помимо from_attributes
, метод model_validate
имеет несколько других полезных атрибутов:
-
strict
: bool | NoneКогда установлен в
True
, применяет строгую валидацию типов.Пример:
model_validate(data, strict=True)
-
context
: Any | NoneПозволяет передать дополнительный контекст валидаторам.
Пример:
model_validate(data, context={'user_id': 123})
-
from_attributes
: bool | NoneКак мы уже обсудили, позволяет извлекать данные из атрибутов объекта.
Пример использования
class User:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
class UserModel(BaseModel):
name: str
age: int
user = User("Alice", 30)
# Использование from_attributes
user_model = UserModel.model_validate(user, from_attributes=True)
# Использование strict и context
data = {"name": "Bob", "age": "25"}
user_model = UserModel.model_validate(
data,
strict=True,
context={"source": "external_api"},
from_attributes=False
)
Эти атрибуты предоставляют гибкость при валидации данных, позволяя адаптировать процесс к конкретным требованиям вашего приложения.
Использование from_attributes
непосредственно в model_validate
особенно удобно, когда вам нужно динамически переключаться между разными источниками данных без изменения конфигурации модели.
Концепция обратной валидации
Идея заключается в использовании Pydantic моделей не только для валидации выходных данных, но и для структурирования входных параметров запросов к базе данных. Это обеспечивает типобезопасность и удобство использования при формировании фильтров для запросов.
Пример реализации
1. Определение Pydantic модели для фильтров
from pydantic import BaseModel, ConfigDict
class TelegramIDModel(BaseModel):
telegram_id: int
model_config = ConfigDict(from_attributes=True)
2. Метод для поиска в базе данных
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from sqlalchemy.exc import SQLAlchemyError
class BaseRepository:
@classmethod
async def find_one_or_none(cls, session: AsyncSession, filters: BaseModel):
filter_dict = filters.model_dump(exclude_unset=True)
try:
query = select(cls.model).filter_by(**filter_dict)
result = await session.execute(query)
return result.scalar_one_or_none()
except SQLAlchemyError as e:
# Обработка ошибок
raise
3. Использование
async def get_user_by_telegram_id(session: AsyncSession, telegram_id: int):
filters = TelegramIDModel(telegram_id=telegram_id)
user = await UserRepository.find_one_or_none(session, filters)
return user
Кстати, в своей универсальной заготовке для создания Telegram-ботов на основе Aiogram 3 и SQLAlchemy я использовал более сложный пример. Если вас интересует исходный код этой заготовки и другой эксклюзивный контент, который я не публикую на Хабре, вы можете получить его бесплатно в моём телеграм-канале «Легкий путь в Python».
Преимущества этого подхода
Типобезопасность: Pydantic обеспечивает валидацию типов входных данных.
Гибкость: Легко создавать различные модели фильтров для разных запросов.
Читаемость: Код становится более понятным и структурированным.
Переиспользование: Модели фильтров можно использовать в разных частях приложения.
Этот подход демонстрирует, как можно эффективно использовать Pydantic не только для валидации выходных данных, но и для структурирования входных параметров запросов. Это создает дополнительный уровень абстракции между бизнес-логикой и слоем доступа к данным, что улучшает общую архитектуру приложения.
Такой метод особенно полезен в крупных проектах, где необходимо обеспечить согласованность и типобезопасность при работе с базой данных.
Практическое применение Pydantic в различных областях
Pydantic — это мощный инструмент, который находит широкое применение в различных сферах разработки. Вот несколько ключевых областей, где Pydantic может быть особенно полезен:
1. Веб-разработка
Pydantic часто используется в веб-фреймворках, таких как FastAPI и Flask, для валидации входящих данных от пользователей. Это позволяет разработчикам легко обрабатывать JSON-запросы и гарантировать, что данные соответствуют ожидаемым типам.
На Хабре я опубликовал более десяти крупных статей о разработке собственного API с использованием FastApi. Если вас интересует эта тема, рекомендую заглянуть в мой профиль. Там вы найдёте множество примеров того, как использовать FastApi вместе с Pydantic.
2. API и микросервисы
При создании RESTful API Pydantic помогает определять схемы данных и автоматически генерировать документацию. Это упрощает интеграцию между различными сервисами и обеспечивает согласованность данных.
3. Обработка конфигураций
Pydantic можно использовать для работы с конфигурационными файлами (например, JSON или YAML). Это позволяет легко загружать и валидировать настройки приложения, минимизируя вероятность ошибок.
4. Обработка данных
В проектах, связанных с анализом данных или машинным обучением, Pydantic помогает валидировать входные данные и гарантировать, что они соответствуют необходимым требованиям перед их обработкой.
5. Тестирование
Pydantic может быть полезен при написании тестов, позволяя создавать фиктивные данные с гарантией их корректности. Это упрощает процесс тестирования и повышает его надежность.
Заключение
Друзья, сегодня мы совершили увлекательное путешествие в мир Pydantic 2, и я надеюсь, что эта статья стала для вас полезным и информативным руководством. Давайте кратко подведем итоги:
Мы разобрали основы Pydantic и его важность в экосистеме Python.
Изучили ключевые концепции Pydantic 2: модели, поля, валидацию, ConfigDict и наследование.
Рассмотрели интеграцию Pydantic с ORM, в частности с SQLAlchemy.
Обсудили практическое применение Pydantic в различных областях разработки.
Отметили улучшения в производительности и функциональности Pydantic 2.
Помните, что для полного освоения Pydantic 2 необходима практика. Экспериментируйте с кодом и интегрируйте Pydantic в ваши проекты.
Если вам понравилась статья, не забудьте поставить лайк и оставить комментарий. Ваше мнение очень важно для меня.
Для получения ещё большего количества эксклюзивного контента, присоединяйтесь к моему Telegram-каналу "Легкий путь в Python".
Спасибо за ваше время! Удачи в ваших проектах, и пусть Pydantic 2 станет вашим надежным помощником в мире Python-разработки!