В одной из предыдущих статей (англ) я рассматривал dataclasses как способ написания классов python, которые выступают в качестве контейнеров данных.
Проект dataclasses показался мне потрясающим по нескольким причинам:
- он реализует множество удобных методов, чтобы избавить вас от написания шаблонного кода;
- обеспечивает лаконичный синтаксис;
- задействует современные практики программирования.
Однако в этом модуле не хватает важной функции — валидации данных, то есть процесса, с помощью которого вы накладываете ограничения схемы на данные во время выполнения.
Вот тут нам и пригодится проект Pydantic. Мы рассмотрим 8 ее полезных функций и увидим, как можно быстро внедрить их в приложения с помощью нескольких строк кода.
Что такое валидация данных?
Валидация данных — это процесс, который приводит ваши данные в соответствие с набором правил, схем или ограничений, которые вы определили для каждого атрибута. Это заставляет код принимать и возвращать данные именно так, как вы того ожидаете.
Валидация данных предотвращает непредвиденные ошибки, возникающие из-за таких проблем, как неправильный ввод данных пользователем. В этом смысле она также выполняет роль функции очистки.
Приведем пример. Представьте, что вы создаете API кредитного скоринга для оценки кредитоспособности человека.
Для того чтобы этот API заработал, нужно отправить POST-запрос на конкретный URL и предоставить некоторые данные полезной нагрузки.
Эти данные определяют личность: некоторые поля являются обязательными. Но этим дело не ограничивается.
По какой-то причине это физическое лицо должно иметь французский налоговый идентификатор, указанный в одном из обязательных полей. Этот идентификатор составлен по определенной схеме: скажем, он должен состоять из 13 цифр и заканчиваться двумя заглавными буквами.
Человек должен указать свой полный адрес: название улицы, ее номер и почтовый индекс. Почтовый индекс должен состоять из 5 цифр.
Перечень можно продолжать и дальше, но суть вам понятна.
👉 Валидация данных гарантирует, что данные, которые вы отправляете в API, соответствуют данным ограничениям.
Вы можете привести свои данные в соответствие с этими ограничениям, загрузив их и применив ряд условий к каждому полю. Это может сработать, но вам придется написать большое количество кода, который со временем станет невозможно поддерживать.
Что если бы мы могли инкапсулировать данные в класс, создать типизированный атрибут для каждого поля и проверять ограничения полей во время выполнения, когда данные загружаются в класс?
Решить эту задачу нам поможет Pydantic.
Что такое Pydantic?
“Pydantic — это библиотека, которая обеспечивает проведение валидации данных и управление настройками с помощью аннотаций типов” (из официальной документации Pydantic).
Мне нравится воспринимать Pydantic как щепотку соли, которой вы посыпаете еду (или, в данном случае, вашу кодовую базу), чтобы сделать ее вкуснее. Pydantic все равно, как вы выполняете то или иное действие. Он скорее слой абстракции, который вы добавляете в свой код, не меняя его логики.
Этот слой, как вы, возможно, и ожидаете, будет в основном заниматься парсингом и валидацией данных, а также другими важными функциями.
Давайте рассмотрим 8 возможностей Pydantic, чтобы понять, чем он может быть нам полезен.
1. Простой синтаксис для определения моделей данных
Вы можете определить свои данные внутри класса, который наследует от класса BaseModel
.
Модели Pydantic — это структуры, которые принимают данные, парсят их и проверяют, чтобы они соответствовали ограничениям полей, определенных в них.
Начнем с простого примера. Определим класс Person
, который имеет два поля без каких-либо ограничений: first_name
и last_name
.
from pydantic import BaseModel
class Person(BaseModel):
first_name: str
last_name: str
- Как и в случае с dataclasses, мы используем аннотации типов.
- В отличие от dataclasses, мы не используем декоратор @dataclass. Вместо этого мы наследуем от класса
BaseModel
.
Вызвав модуль typing
, мы можем добавлять поля с более сложными типами:
from pydantic import BaseModel
from typing import Optional, List
class Person(BaseModel):
first_name: str
last_name: str
interest: Optional[List[str]]
data = {"first_name": "Ahmed", "last_name": "Besbes"}
person = Person(**data)
print(person)
# first_name='Ahmed' last_name='Besbes' address=None interests=None
Вы даже можете создавать типы, которые сами являются классом BaseModel:
from pydantic import BaseModel
from typing import Optional, List
class Address(BaseModel):
street: str
number: int
zipcode: str
class Person(BaseModel):
first_name: str
last_name: str
interest: Optional[List[str]]
address_data = {"street": "Main street", "number": 1, "zipcode": 12345}
address = Address(**address_data)
data = {"first_name": "Ahmed", "last_name": "Besbes", "address": address}
person = Person(**data)
print(person)
# first_name='Ahmed' last_name='Besbes' address=Address(street='Main street', number=1, zipcode='12345') interests=None
2. Удобные для пользователя сообщения об ошибках
Что произойдет, если вы определите модель Pydantic и передадите ей данные, которые не соответствуют заданной схеме?
Чтобы понять, как поведет себя Pydantic в этом конкретном случае, давайте рассмотрим простую модель:
from pydantic import BaseModel
from typing import Optional
class Address(BaseModel):
street: str
number: int
zipcode: str
class Person(BaseModel):
first_name: str
last_name: str
age: int
address: Optional[Address]
Давайте теперь передадим ей некоторые несовместимые данные и посмотрим, какие сообщения об ошибках будут возвращены.
❌ Первый случай: отсутствует обязательное поле:
Давайте опустим поле age
(возраст). Это немедленно вызывает ошибку ValidationError
, которая указывает на конкретное отсутствующее поле.
❌ Второй случай: несовместимый тип поля
Вместо целого числа в поле age
передадим строку: например, "30 years"
(“30 лет”) вместо 30
. Аналогично, это вызовет ошибку ValidationError
, а в сообщении будет явно указано, что поле ожидает целочисленный тип.
👉 Стоит отметить, что Pydantic всегда пытается в принудительном порядке обработать тип, который вы аннотировали. Например, если попытаться передать “30” в поле age
, несмотря на то, что это поле ожидает целочисленное значение, ошибки не будет. Pydantic справляется с этой ситуацией без проблем.
👉 Вы можете сделать сообщения об ошибках Pydantic более явными, импортировав класс ValidationError
и вызвав его внутри оператора try
/ except
.
3. Хорошая интеграция с IDE и линтерами
Pydantic хорошо интегрируется с современными IDE, такими как VSCode и PyCharm, что помогает быстро отлаживать код и избегать глупых ошибок. Например, когда вы инстанцируете объект из модели Pydantic, то сразу можете воспользоваться автозаполнением:
Доступен также линтинг, если вы используете статическую проверку типов, например mypy.
В следующем примере, если вы попытаетесь применить функцию len
к атрибуту age
, VSCode сообщит об ошибке через mypy.
4. Настройка полей
Pydantic снабжен нативным механизмом по добавлению валидаций для каждого поля с помощью обертывания внутри класса Field
.
Например:
- вы можете добавить ограничения на длину строковых полей с помощью аргументов Field
max_length
иmin_length
; - вы можете установить границы для числовых полей, используя аргументы Field
ge
иle
( ge — больше или равно, le — меньше или равно).
Рассмотрим следующий пример. Добавим ограничения в отношении полей first_name
(длина от 2 до 20) и age
(значение меньше 150).
from pydantic import BaseModel, Field
from typing import Optional
class Address(BaseModel):
street: str
number: int
zipcode: str
class Person(BaseModel):
first_name: str = Field(min_length=2, max_length=20)
last_name: str
age: int = Field(le=150)
address: Optional[Address]
Давайте посмотрим, какие ошибки вернутся, если мы попытаемся передать классу Person следующие данные:
data = {"first_name": "a", "last_name": "Besbes", "age": 200}
Вы можете выполнить более сложную настройку полей. Вот некоторые аргументы, которые используются внутри класса Field
:
regex
: добавляет валидатор регулярных выражений. Функция пригодится, когда вам понадобится соответствие некоторых строковых значений определенному шаблону.multiple_of
: применяется к полям int. Добавляет валидатор “multiple of”.max_items
иmin_items
: применяются к спискам и накладывают ограничение на количество содержащихся в них элементов.allow_mutation
: применяется к любому типу полей. По умолчанию установлено значение False. При установке значения True поле становится неизменяемым (или защищенным).
Чтобы узнать больше о широких возможностях настройки Pydantic Field, пройдите по этой ссылке из документации.
5. Множество вспомогательных методов
Вам не придется изобретать колесо. Pydantic предоставляет пользователю большое количество вспомогательных функций и методов. Приведу несколько примеров. Взгляните на код ниже:
from pydantic import BaseModel, Field
from typing import Optional
class Address(BaseModel):
street: str
number: int
zipcode: str
class Person(BaseModel):
first_name: str = Field(min_length=2, max_length=20)
last_name: str
age: int = Field(le=150)
address: Optional[Address]
Давайте создадим объект Person
:
data = {"first_name": "Ahmed", "last_name": "Besbes", "age": 30}
person = Person(**data)
У этого объекта есть доступ ко многим полезным методам:
dict()
: возвращает словарь из объекта;json()
: возвращает словарь в формате JSON;copy()
: возвращает копию объекта;schema()
: выводит схему в формате JSON.
Чтобы узнать больше о вспомогательных функциях, перейдите по этой ссылке.
6. Типы Pydantic
str
, int
, float
, List
— это все обычные типы, с которыми мы работаем.
Но в некоторых случаях мы можем работать со значениями, которые требуют специальной валидации, например, пути, адреса электронной почты, IP-адреса и т.п.
К счастью, Pydatnic предоставляет список встроенных типов для многих из этих случаев использования:
- FilePath: для парсинга путей к файлам;
- DirectoryPath: для парсинга путей к каталогам;
- EmailStr: для парсинга адресов электронной почты;
- Color: для парсинга цветов HTML (см. тип цвета);
- HttpUrl: для парсинга строгих HTTP URL-адресов;
- IPvAnyAddress: для парсинга адресов IPv4 и IPv6.
7. Пользовательские валидаторы
Pydantic позволяет вам написать свой собственный валидатор. Допустим, мы хотим добавить поле PhoneNumber
в предыдущий пример. Нам нужно, чтобы это поле соответствовало двум требованиям:
- это строка из 10 цифр;
- она должна начинаться с 0.
Чтобы выполнить пользовательскую валидацию, необходимо импортировать функцию validator
из Pydantic и использовать ее в качестве декоратора перед функцией, проверяющей значение поля.
import re
from pydantic import BaseModel, validator
from typing import Optional
class Address(BaseModel):
street: str
number: int
zipcode: str
class Person(BaseModel):
first_name: str
last_name: str
phone_number: str
age: int
address: Optional[Address]
@validator("phone_number")
def phone_number_must_have_10_digits(cls, v):
match = re.match(r"0\d{9}", v)
if (match is None) or (len(v) != 10):
raise ValueError("Phone number must have 10 digits")
return v
Конечно, вы можете проводить более сложные валидации. Перейдите по этой ссылке, чтобы узнать больше.
8. Парсинг значений переменных среды
Pydantic позволяет читать переменные среды из файлов .env и парсить их непосредственно внутри класса BaseSettings
. Для этого вам сначала нужно установить python-dotenv.
Предположим, что у вас есть некоторые переменные среды внутри этого файла .env:
LOGIN=Ahmed
API_KEY=SeCR€t!
SEED=42
Чтобы Pydantic загрузил эти переменные, нам сначала нужно определить класс Settings
, который наследует от класса BaseSettings
.
Внутри класса Settings
мы определим переменные, которые перечислены в .env-файле, добавив при этом типы и валидаторы. И, наконец, мы укажем, что переменные среды должны быть прочитаны из файла .env.
from pydantic import BaseSettings
class Settings(BaseSettings):
api_key: str
login: str
seed: int
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
settings = Settings()
print(settings)
Если мы запустим код, настройки будут выведены на терминал:
Если мы заменим числовое обозначение “42” на буквенное “сорок два” в файле .env, вот что мы получим:
Читайте также:
- Классы данных в Python и их ключевые особенности
- Python 3: 3 функции, которые следует помнить
- Улучшение Python кода: замените if-elif условие на словарь!
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Ahmed Besbes: 8 Reasons to Start Using Pydantic to Improve Data Parsing and Validation