Pydantic — это мощная библиотека проверки данных и управления настройками для Python, созданная для повышения прочности и надежности вашей кодовой базы. Pydantic может справиться практически с любым сценарием проверки данных с минимальным количеством кода: от проверки, является ли переменная целым числом, до обеспечения правильных типов данных для ключей и значений вложенных словарей.
Друзья, подписывайтесь на наш телеграм канал Pythonist. Там еще больше туториалов, задач и книг по Python.
Содержание
Библиотека Pydantic
Одной из главных особенностей Python является то, что это динамически типизированный язык. Динамическая типизация означает, что типы переменных определяются во время выполнения программы. Для сравнения — в статически типизированных языках типы явно объявляются во время компиляции.
Динамическая типизация отлично подходит для быстрой разработки и простоты использования, но для реальных приложений часто требуется более надежная проверка типов и валидация данных. В этом вам поможет библиотека Python Pydantic. Она быстро завоевала популярность, и сейчас это самая распространенная библиотека проверки данных для Python.
Знакомство с Pydantic
Pydantic — это мощная библиотека Python, которая использует подсказки типов, чтобы помочь вам легко проверять и сериализовать схемы данных. Это делает ваш код более надежным, читабельным, лаконичным и легким для отладки.
Pydantic также хорошо интегрируется со многими популярными инструментами статической типизации и IDE, что позволяет выявлять проблемы со схемами до запуска кода.
Отличительные особенности Pydantic:
- Кастомизация. С помощью Pydantic можно проверять данные практически любого типа. Эта библиотека позволяет валидировать и сериализовать практически любой объект Python, от примитивных типов до высоко вложенных структур данных.
- Гибкость. Pydantic дает вам контроль над строгостью проверки данных. В некоторых случаях вы можете захотеть принудительно привести входящие данные к правильному типу. Например, вы можете принять данные, которые должны иметь тип float, но получены как целое число. В других случаях вы можете захотеть строго придерживаться типов получаемых данных. Pydantic позволяет вам поступать и так, и эдак.
- Сериализация. Вы можете сериализовать и десериализовать объекты Pydantic в виде словарей и строк JSON. Это означает, что можно легко преобразовывать объекты Pydantic в JSON и обратно. Эта возможность привела к созданию самодокументированных API и интеграции практически с любым инструментом, поддерживающим JSON-схемы.
- Производительность. Благодаря основной логике валидации, написанной на языке Rust, Pydantic работает исключительно быстро. Это преимущество в производительности обеспечивает быструю и надежную обработку данных, особенно в высокопроизводительных приложениях, таких как REST API, которые должны масштабироваться до большого количества запросов.
- Экосистема и промышленное внедрение. Pydantic является зависимостью многих популярных библиотек Python, таких как FastAPI, LangChain и Polars. Эту библиотеку также используют большинство крупнейших технологических компаний. Это свидетельствует о поддержке сообщества, надежности и устойчивости Pydantic.
Эти особенности делают Pydantic привлекательной библиотекой проверки данных. В нашем руководстве мы покажем их в действии.
Установка Pydantic
Pydantic доступна на PyPI, и вы можете установить ее с помощью pip. Откройте терминал или командную строку, создайте новое виртуальное окружение, а затем выполните следующую команду для установки Pydantic:
(venv) $ python -m pip install pydantic
Эта команда установит последнюю версию Pydantic из PyPI на вашу машину. Чтобы убедиться, что установка прошла успешно, запустите Python REPL и импортируйте Pydantic:
>>> import pydantic
Если импорт пройдет без ошибок, значит, библиотека установлена успешно, и теперь в вашей системе есть ядро Pydantic.
Добавление дополнительных зависимостей
Вместе с Pydantic можно установить и дополнительные зависимости. Например, в этой статье мы будем работать с проверкой электронной почты, поэтому вы можете включить в свою установку эти зависимости:
(venv) $ python -m pip install "pydantic[email]"
В Pydantic есть отдельный пакет для управления настройками, который мы тоже рассмотрим. Чтобы установить его, выполните следующую команду:
(venv) $ python -m pip install pydantic-settings
Итак, вы установили все зависимости, необходимые для этого урока, и готовы приступить к изучению Pydantic. Начнем с моделей — основного способа определения схем данных в Pydantic.
Использование моделей
Основной способ определения схем данных в Pydantic — это модели. Модель Pydantic — это объект, похожий на dataclass Python, который определяет и хранит данные о сущности с аннотированными полями. В отличие от классов данных, Pydantic сосредоточен на автоматических парсинге, валидации и сериализации данных.
Лучший способ разобраться во всем этом — создать свои собственные модели.
Работа с Pydantic BaseModels
Предположим, вы создаете приложение, используемое отделом кадров для управления информацией о сотрудниках. Вам нужен способ проверить, что информация о новом сотруднике представлена в правильной форме. Например, каждый сотрудник должен иметь ID, имя, email, дату рождения, зарплату, отдел и выбор льгот. Это идеальный вариант использования модели Pydantic!
Чтобы определить модель сотрудника, вы создаете класс, который наследуется от BaseModel
Pydantic:
from datetime import date from uuid import UUID, uuid4 from enum import Enum from pydantic import BaseModel, EmailStr class Department(Enum): HR = "HR" SALES = "SALES" IT = "IT" ENGINEERING = "ENGINEERING" class Employee(BaseModel): employee_id: UUID = uuid4() name: str email: EmailStr date_of_birth: date salary: float department: Department elected_benefits: bool
Сначала вы импортируете зависимости, необходимые для определения модели сотрудника. Затем вы создаете enum для представления различных отделов вашей компании и используете его для аннотирования поля department в модели сотрудника.
После этого вы определяете свою модель Employee
, которая наследует от BaseModel
и определяет имена и ожидаемые типы полей с помощью аннотаций. Вот описание каждого поля, которое вы определили в Employee
, и того, как Pydantic проверяет его при создании объекта Employee
:
- employee_id. Это UUID сотрудника, информацию о котором вы хотите сохранить. Используя аннотацию UUID, Pydantic гарантирует, что это поле всегда будет валидным UUID. Каждому экземпляру Employee по умолчанию будет присвоен UUID, который вы указали, вызвав
uuid4()
. - name. Имя сотрудника, которое Pydantic ожидает как строку.
- email. Pydantic убедится, что email каждого сотрудника является валидным, используя под капотом библиотеку Python email-validator.
- date_of_birth. Дата рождения каждого сотрудника должна быть валидной датой, поскольку аннотирована как date из модуля Python datetime. Если вы передадите в
date_of_birth
строку, Pydantic попытается разобрать ее и преобразовать в объектdate
. - salary. Это зарплата сотрудника, и ожидается, что это будет число с плавающей запятой.
- department. Отдел каждого сотрудника должен быть или HR, или SALES, или IT, или ENGINEERING, как определено в
Department
enum. - elected_benefits: Это поле хранит информацию о том, есть ли у сотрудника льготы, и Pydantic ожидает, что это будет булево значение.
Самый простой способ создать объект Employee
— это инстанцировать его, как и любой другой объект Python. Для этого откройте Python REPL и запустите следующий код:
>>> from pydantic_models import Employee >>> Employee( ... name="Chris DeTuma", ... email="cdetuma@example.com", ... date_of_birth="1998-04-02", ... salary=123_000.00, ... department="IT", ... elected_benefits=True, ... ) Employee( employee_id=UUID('73636d47-373b-40cd-a005-4819a84d9ea7'), name='Chris DeTuma', email='cdetuma@example.com', date_of_birth=datetime.date(1998, 4, 2), salary=123000.0, department=<Department.IT: 'IT'>, elected_benefits=True )
В этом блоке вы импортируете Employee и создаете объект со всеми необходимыми полями сотрудника. Pydantic успешно проверяет и принудительно обрабатывает переданные вами поля и создает корректный объект Employee
. Обратите внимание, как Pydantic автоматически преобразует строку даты в объект date
, а строку IT
— в соответствующее перечисление Department
.
Теперь давайте посмотрим, как Pydantic отреагирует, если попытаться передать экземпляру Employee
недопустимые данные:
>>> Employee( ... employee_id="123", ... name=False, ... email="cdetumaexamplecom", ... date_of_birth="1939804-02", ... salary="high paying", ... department="PRODUCT", ... elected_benefits=300, ... ) Traceback (most recent call last): pydantic_core._pydantic_core.ValidationError: 7 validation errors for Employee employee_id Input should be a valid UUID, invalid length: expected length 32 for simple format, found 3 [type=uuid_parsing, input_value='123', input_type=str] For further information visit https://errors.pydantic.dev/2.6/v/uuid_parsing name Input should be a valid string [type=string_type, input_value=False, input_type=bool] For further information visit https://errors.pydantic.dev/2.6/v/string_type email value is not a valid email address: The email address is not valid. It must have exactly one @-sign. [type=value_error, input_value='cdetumaexamplecom', input_type=str] date_of_birth Input should be a valid date or datetime, invalid date separator, expected `-` [type=date_from_datetime_parsing, input_value='1939804-02', input_type=str] For further information visit https://errors.pydantic.dev/2.6/v/date_from_datetime_parsing salary Input should be a valid number, unable to parse string as a number [type=float_parsing, input_value='high paying', input_type=str] For further information visit https://errors.pydantic.dev/2.6/v/float_parsing department Input should be 'HR', 'SALES', 'IT' or 'ENGINEERING' [type=enum, input_value='PRODUCT', input_type=str] elected_benefits Input should be a valid boolean, unable to interpret input [type=bool_parsing, input_value=300, input_type=int] For further information visit https://errors.pydantic.dev/2.6/v/bool_parsing
В этом примере вы создали объект Employee
с недопустимыми полями данных. Pydantic выдает подробное сообщение об ошибке для каждого поля, сообщая, что ожидалось, что было получено и куда можно обратиться, чтобы узнать больше об ошибке.
Такая подробная валидация очень важна, поскольку она предотвращает сохранение недействительных данных в Employee
. Это также дает вам уверенность в том, что объекты Employee
, которые вы создаете без ошибок, содержат ожидаемые данные, и вы можете доверять этим данным в дальнейшем в своем коде или в других приложениях.
BaseModel
оснащена набором методов, которые позволяют легко создавать модели из других объектов, таких как словари и JSON. Например, если вы хотите создать объект Employee
из словаря, вы можете использовать метод класса .model_validate()
:
>>> new_employee_dict = { ... "name": "Chris DeTuma", ... "email": "cdetuma@example.com", ... "date_of_birth": "1998-04-02", ... "salary": 123_000.00, ... "department": "IT", ... "elected_benefits": True, ... } >>> Employee.model_validate(new_employee_dict) Employee( employee_id=UUID('73636d47-373b-40cd-a005-4819a84d9ea7'), name='Chris DeTuma', email='cdetuma@example.com', date_of_birth=datetime.date(1998, 4, 2), salary=123000.0, department=<Department.IT: 'IT'>, elected_benefits=True )
Здесь мы создаем new_employee_dict
, словарь с полями сотрудников, и передаем его в .model_validate()
для создания экземпляра Employee
. Под капотом Pydantic проверяет каждую запись словаря, чтобы убедиться, что она соответствует ожидаемым данным. Если какие-либо данные окажутся невалидными, Pydantic выдаст ошибку тем же способом, который вы видели ранее. Вы также получите уведомление, если в словаре отсутствуют какие-либо поля.
То же самое можно сделать с объектами JSON, используя .model_validate_json()
:
>>> new_employee_json = """ ... {"employee_id":"d2e7b773-926b-49df-939a-5e98cbb9c9eb", ... "name":"Eric Slogrenta", ... "email":"eslogrenta@example.com", ... "date_of_birth":"1990-01-02", ... "salary":125000.0, ... "department":"HR", ... "elected_benefits":false} ... """ >>> new_employee = Employee.model_validate_json(new_employee_json) >>> new_employee Employee( employee_id=UUID('d2e7b773-926b-49df-939a-5e98cbb9c9eb'), name='Eric Slogrenta', email='eslogrenta@example.com', date_of_birth=datetime.date(1990, 1, 2), salary=125000.0, department=<Department.HR: 'HR'>, elected_benefits=False )
В этом примере new_employee_json
— это корректная JSON-строка, в которой хранятся поля сотрудников, и вы используете .model_validate_json()
для валидации и создания объекта Employee
из new_employee_json
.
Хотя это может показаться мелочью, возможность создавать и проверять модели Pydantic из JSON очень важна, потому что JSON — один из самых популярных способов передачи данных через Интернет. Это одна из причин, по которой FastAPI полагается на Pydantic при создании REST API.
Вы также можете сериализовать модели Pydantic в виде словарей и JSON:
>>> new_employee.model_dump() { 'employee_id': UUID('d2e7b773-926b-49df-939a-5e98cbb9c9eb'), 'name': 'Eric Slogrenta', 'email': 'eslogrenta@example.com', 'date_of_birth': datetime.date(1990, 1, 2), 'salary': 125000.0, 'department': <Department.HR: 'HR'>, 'elected_benefits': False } >>> new_employee.model_dump_json() '{"employee_id":"d2e7b773-926b-49df-939a-5e98cbb9c9eb", ⮑"name":"Eric Slogrenta", ⮑"email":"eslogrenta@example.com", ⮑"date_of_birth":"1990-01-02", ⮑"salary":125000.0, ⮑"department":"HR", ⮑"elected_benefits":false}'
Здесь вы используете .model_dump()
и .model_dump_json()
для преобразования модели new_employee
в словарь и строку JSON соответственно. Обратите внимание, что .model_dump_json()
возвращает JSON-объект с date_of_birth
и department
, сохраненными в виде строк.
Хотя Pydantic уже проверил эти поля и преобразовал вашу модель в JSON, тот, кто будет использовать этот JSON, не будет знать, что date_of_birth
должна быть валидной датой, а department
должен быть категорией в вашем Department
enum. Чтобы решить эту проблему, вы можете создать схему JSON на основе модели Employee
.
JSON-схемы указывают, какие поля ожидаются и какие значения будут представлены в JSON-объекте. Можно считать, что это JSON-версия определения класса Employee
. Вот как создается JSON-схема для Employee
:
>>> Employee.model_json_schema() { '$defs': { 'Department': { 'enum': ['HR', 'SALES', 'IT', 'ENGINEERING'], 'title': 'Department', 'type': 'string' } }, 'properties': { 'employee_id': { 'default': '73636d47-373b-40cd-a005-4819a84d9ea7', 'format': 'uuid', 'title': 'Employee Id', 'type': 'string' }, 'name': {'title': 'Name', 'type': 'string'}, 'email': { 'format': 'email', 'title': 'Email', 'type': 'string' }, 'date_of_birth': { 'format': 'date', 'title': 'Date Of Birth', 'type': 'string' }, 'salary': {'title': 'Salary', 'type': 'number'}, 'department': {'$ref': '#/$defs/Department'}, 'elected_benefits': {'title': 'Elected Benefits', 'type': 'boolean'} }, 'required': [ 'name', 'email', 'date_of_birth', 'salary', 'department', 'elected_benefits' ], 'title': 'Employee', 'type': 'object' }
Когда вы вызываете .model_json_schema()
, вы получаете словарь, представляющий JSON-схему вашей модели. Первая запись, которую вы видите, показывает значения, которые может принимать department
. Вы также видите информацию о том, как должны быть отформатированы ваши поля. Например, согласно этой JSON-схеме, employee_id
должен быть UUID, а date_of_birth
— датой.
Вы можете преобразовать свою JSON-схему в JSON-строку с помощью json.dumps()
, что позволит практически любому языку программирования проверить JSON-объекты, созданные вашей моделью Employee
. Другими словами, Pydantic не только может проверять входящие данные и сериализовать их в виде JSON, но и предоставляет другим языкам программирования информацию, необходимую для проверки данных вашей модели с помощью JSON-схем.
Итак, теперь вы понимаете, как использовать BaseModel
в Pydantic для проверки и сериализации данных. Далее вы узнаете, как использовать поля для дальнейшей настройки валидации.
Использование полей для персонализации и метаданных
Пока что ваша модель Employee
проверяет тип данных каждого поля и гарантирует, что некоторые поля, такие как email
, date_of_birth
и department
, имеют допустимые форматы. Но допустим, вы также хотите убедиться, что salary
— это положительное число, name
— не пустая строка, а email
содержит доменное имя вашей компании. Для этого можно использовать класс Field из Pydantic.
Класс Field
позволяет настраивать и добавлять метаданные к полям вашей модели. Чтобы увидеть, как это работает, посмотрите на этот пример:
from datetime import date from uuid import UUID, uuid4 from enum import Enum from pydantic import BaseModel, EmailStr, Field class Department(Enum): HR = "HR" SALES = "SALES" IT = "IT" ENGINEERING = "ENGINEERING" class Employee(BaseModel): employee_id: UUID = Field(default_factory=uuid4, frozen=True) name: str = Field(min_length=1, frozen=True) email: EmailStr = Field(pattern=r".+@example\.com$") date_of_birth: date = Field(alias="birth_date", repr=False, frozen=True) salary: float = Field(alias="compensation", gt=0, repr=False) department: Department elected_benefits: bool
Здесь мы импортируем Field
вместе с другими зависимостями, которые мы использовали ранее, и присваиваем значения по умолчанию некоторым полям Employee
. Вот разбивка параметров Field
, которые мы использовали для добавления дополнительной валидации и метаданных к полям:
- default_factory. Этот параметр используется для определения вызываемого объекта, который генерирует значения по умолчанию. В примере выше мы задали
default_factory
равнымuuid4
. Это вызовет функциюuuid4()
для генерации случайного UUID дляemployee_id
, когда это необходимо. Вы также можете использовать лямбда-функцию для большей гибкости. - frozen. Это булевский параметр, который вы можете установить, чтобы сделать ваши поля неизменяемыми. Это означает, что если значение
frozen
равно True, соответствующее поле не может быть изменено после инстанцирования модели. В этом примере с помощью параметраfrozen
сделаны неизменяемыми поляemployee_id
,name
иdate_of_birth
. - min_length. С помощью
min_length
иmax_length
можно контролировать длину строковых полей. В приведенном выше примере имя должно содержать не менее одного символа. - pattern. Для строковых полей можно задать шаблон в виде regex-выражения. Например, когда вы используете regex-выражение в примере выше для email, Pydantic следит за тем, чтобы каждый email-адрес заканчивался на @example.com.
- alias. Этот параметр можно использовать, когда вы хотите назначить псевдоним для своих полей. Например, псевдоним
birth_date
дляdate_of_birth
иcompensation
дляsalary
. Вы можете использовать эти псевдонимы при инстанцировании или сериализации модели. - gt. Этот параметр используется для установки минимального значения для числовых полей. Буквосочетание «gt» — это сокращение от «greater than», что переводится как «больше, чем». В данном примере значение
gt=0
гарантирует, что зарплата всегда будет положительным числом. В Pydantic есть и другие числовые ограничения, напримерlt
, что означает «less than» («меньше, чем»). - repr. Этот булевский параметр определяет, будет ли поле отображаться в представлении полей модели. В данном примере при печати экземпляра
Employee
вы не увидитеdate_of_birth
илиsalary
.
Чтобы увидеть эту дополнительную валидацию в действии, обратите внимание на то, что происходит при попытке создать модель Employee
с неверными данными:
>>> from pydantic_models import Employee >>> incorrect_employee_data = { ... "name": "", ... "email": "cdetuma@fakedomain.com", ... "birth_date": "1998-04-02", ... "salary": -10, ... "department": "IT", ... "elected_benefits": True, ... } >>> Employee.model_validate(incorrect_employee_data) Traceback (most recent call last): pydantic_core._pydantic_core.ValidationError: 3 validation errors for Employee name String should have at least 1 character [type=string_too_short, input_value='', input_type=str] For further information visit https://errors.pydantic.dev/2.6/v/string_too_short email String should match pattern '.+@example\.com$' [type=string_pattern_mismatch, input_value='cdetuma@fakedomain.com', input_type=str] For further information visit https://errors.pydantic.dev/2.6/v/string_pattern_mismatch salary Input should be greater than 0 [type=greater_than, input_value=-10, input_type=int] For further information visit https://errors.pydantic.dev/2.6/v/greater_than
Здесь вы импортируете обновленную модель Employee
и пытаетесь валидировать словарь с неверными данными. В ответ Pydantic выдает три ошибки валидации: данные в поле name
должны состоять хотя бы из одного символа, email
должен соответствовать доменному имени вашей компании, а значение salary
должно быть больше нуля.
Теперь обратите внимание на дополнительные возможности, которые вы получаете при проверке корректных данных Employee
:
>>> employee_data = { ... "name": "Clyde Harwell", ... "email": "charwell@example.com", ... "birth_date": "2000-06-12", ... "compensation": 100_000, ... "department": "ENGINEERING", ... "elected_benefits": True, ... } >>> employee = Employee.model_validate(employee_data) >>> employee Employee( employee_id=UUID('614c6f75-8528-4272-9cfc-365ddfafebd9'), name='Clyde Harwell', email='charwell@example.com', department=<Department.ENGINEERING: 'ENGINEERING'>, elected_benefits=True) >>> employee.salary 100000.0 >>> employee.date_of_birth datetime.date(2000, 6, 12)
В этом блоке мы создаем словарь и модель Employee
с помощью .model_validate()
. В поле employee_data
обратите внимание на то, что мы использовали birth_date
вместо date_of_birth
и compensation
вместо salary
. Pydantic распознает эти псевдонимы и внутренне присваивает их значения правильным именам полей.
Поскольку мы задали repr=False
, salary
и date_of_birth
не отображаются в представлении Employee
. Чтобы увидеть их значения, необходимо явно обратиться к ним как к атрибутам. Наконец, обратите внимание на то, что происходит при попытке изменить замороженное поле:
>>> employee.department = "HR" >>> employee.name = "Andrew TuGrendele" Traceback (most recent call last): pydantic_core._pydantic_core.ValidationError: 1 validation error for Employee name Field is frozen [type=frozen_field, input_value='Andrew TuGrendele', input_type=str] For further information visit https://errors.pydantic.dev/2.6/v/frozen_field
Здесь мы сначала меняем значение department
с IT на HR. Это вполне допустимо, поскольку department
не является замороженным полем. Однако, когда вы пытаетесь изменить name
, Pydantic выдает ошибку, говоря, что name
— это замороженное поле.
Итак, вы познакомились с классами BaseModel
и Field
в Pydantic. С их помощью вы можете определить множество различных правил валидации и метаданных для своих схем данных, но иногда этого недостаточно. Далее вы расширите возможности валидации полей с помощью валидаторов Pydantic.
Работа с валидаторами
До этого момента мы использовали BaseModel
Pydantic для валидации полей модели с предопределенными типами, а для дальнейшей настройки валидации включили Field
. И хотя даже на BaseModel
и Field
можно далеко продвинуться, для более сложных сценариев валидации, требующих пользовательской логики, вам понадобится использовать валидаторы Pydantic.
С помощью валидаторов можно выполнить практически любую логику проверки, которую можно выразить в функции. Далее вы увидите, как это сделать.
Валидация моделей и полей
Продолжим использовать пример с сотрудниками. Предположим, что ваша компания придерживается политики, согласно которой на работу принимаются сотрудники не моложе восемнадцати лет. Каждый раз, когда вы создаете новый объект Employee
, вам нужно убедиться, что сотрудник старше восемнадцати лет. Для этого можно добавить поле «age» и использовать класс Field
, чтобы убедиться, что сотруднику не менее восемнадцати лет. Однако это кажется излишним, поскольку вы уже храните дату рождения сотрудника.
Лучшее решение — использовать валидатор полей Pydantic. Валидаторы полей позволяют применять пользовательскую логику проверки к полям BaseModel
путем добавления методов класса в модель. Чтобы убедиться, что всем сотрудникам не меньше восемнадцати лет, вы можете добавить в модель Employee
следующий валидатор поля:
from datetime import date from uuid import UUID, uuid4 from enum import Enum from pydantic import BaseModel, EmailStr, Field, field_validator class Department(Enum): HR = "HR" SALES = "SALES" IT = "IT" ENGINEERING = "ENGINEERING" class Employee(BaseModel): employee_id: UUID = Field(default_factory=uuid4, frozen=True) name: str = Field(min_length=1, frozen=True) email: EmailStr = Field(pattern=r".+@example\.com$") date_of_birth: date = Field(alias="birth_date", repr=False, frozen=True) salary: float = Field(alias="compensation", gt=0, repr=False) department: Department elected_benefits: bool @field_validator("date_of_birth") @classmethod def check_valid_age(cls, date_of_birth: date) -> date: today = date.today() eighteen_years_ago = date(today.year - 18, today.month, today.day) if date_of_birth > eighteen_years_ago: raise ValueError("Employees must be at least 18 years old.") return date_of_birth
В этом блоке мы импортируем field_validator
и используем его для оформления метода класса в Employee под названием .check_valid_age()
. Валидаторы полей должны быть определены как методы класса. В .check_valid_age()
мы вычисляем сегодняшнюю дату, но восемнадцать лет назад. Если date_of_birth
сотрудника окажется после этой даты, будет выдана ошибка.
Чтобы увидеть, как работает этот валидатор, посмотрите этот пример:
>>> from pydantic_models import Employee >>> from datetime import date, timedelta >>> young_employee_data = { ... "name": "Jake Bar", ... "email": "jbar@example.com", ... "birth_date": date.today() - timedelta(days=365 * 17), ... "compensation": 90_000, ... "department": "SALES", ... "elected_benefits": True, ... } >>> Employee.model_validate(young_employee_data) Traceback (most recent call last): pydantic_core._pydantic_core.ValidationError: 1 validation error for Employee birth_date Value error, Employees must be at least 18 years old. [type=value_error, input_value=datetime.date(2007, 4, 10), input_type=date] For further information visit https://errors.pydantic.dev/2.6/v/value_error
Здесь мы указываем birth_date
на семнадцать лет раньше текущей даты. Когда вы вызываете .model_validate()
для проверки данных young_employee_data
, вы получаете ошибку, говорящую о том, что сотрудникам должно быть не менее восемнадцати лет.
Функция field_validator()
в Pydantic позволяет произвольно настраивать валидацию полей. Однако field_validator()
не подойдет, если вы хотите сравнить несколько полей друг с другом или проверить модель в целом. Для этого вам придется использовать валидаторы модели.
В качестве примера предположим, что ваша компания нанимает людей в IT-отдел только по контракту (т. е. не берет их в штат компании). В связи с этим IT-сотрудники не имеют права на льготы, и их поле elected_benefits
должно быть False. Вы можете использовать функцию Pydantic model_validator()
, чтобы обеспечить соблюдение этого ограничения:
from typing import Self from datetime import date from uuid import UUID, uuid4 from enum import Enum from pydantic import ( BaseModel, EmailStr, Field, field_validator, model_validator, ) class Department(Enum): HR = "HR" SALES = "SALES" IT = "IT" ENGINEERING = "ENGINEERING" class Employee(BaseModel): employee_id: UUID = Field(default_factory=uuid4, frozen=True) name: str = Field(min_length=1, frozen=True) email: EmailStr = Field(pattern=r".+@example\.com$") date_of_birth: date = Field(alias="birth_date", repr=False, frozen=True) salary: float = Field(alias="compensation", gt=0, repr=False) department: Department elected_benefits: bool @field_validator("date_of_birth") @classmethod def check_valid_age(cls, date_of_birth: date) -> date: today = date.today() eighteen_years_ago = date(today.year - 18, today.month, today.day) if date_of_birth > eighteen_years_ago: raise ValueError("Employees must be at least 18 years old.") return date_of_birth @model_validator(mode="after") def check_it_benefits(self) -> Self: department = self.department elected_benefits = self.elected_benefits if department == Department.IT and elected_benefits: raise ValueError( "IT employees are contractors and don't qualify for benefits" ) return self
Здесь мы добавили к импортам тип Self из Python и model_validator()
из Pydantic. Затем мы создали метод .check_it_benefits()
, который выдает ошибку, если сотрудник принадлежит к отделу IT и поле elected_benefits
равно True. Если в @model_validator
установить mode
как after, Pydantic будет ждать, пока после инстанцирования модели не будет запущен метод .check_it_benefits()
.
Примечание. Вы могли заметить, что .check_it_benefits()
аннотирован пайтоновским типом Self. Это потому, что .check_it_benefits()
возвращает экземпляр класса Employee
, а тип Self является предпочтительной аннотацией для этого. Если вы используете версию Python меньше 3.11, вам придется импортировать тип Self из typing_extensions
.
Чтобы увидеть новый валидатор модели в действии, посмотрите этот пример:
>>> from pydantic_models import Employee >>> new_employee = { ... "name": "Alexis Tau", ... "email": "ataue@example.com", ... "birth_date": "2001-04-012", ... "compensation": 100_000, ... "department": "IT", ... "elected_benefits": True, ... } >>> Employee.model_validate(new_employee) Traceback (most recent call last): pydantic_core._pydantic_core.ValidationError: 1 validation error for Employee Value error, IT employees are contractors and don't qualify for benefits. [type=value_error, input_value={'name': 'Alexis Tau', ...elected_benefits': True}, input_type=dict] For further information visit https://errors.pydantic.dev/2.6/v/value_error
Здесь мы пытаемся создать модель Employee
с IT-отделом и параметром elected_benefits
, установленным в True. При вызове .model_validate()
Pydantic выдает ошибку, сообщая, что сотрудники IT-отдела не имеют права на льготы, поскольку работают по контракту.
С помощью валидаторов модели и поля вы можете реализовать практически любую пользовательскую проверку, которая только может прийти вам в голову.
Теперь у вас должна быть прочная основа для создания моделей Pydantic для ваших собственных случаев использования. Далее мы рассмотрим, как можно использовать Pydantic для проверки произвольных функций, а не только полей BaseModel
.
Использование декораторов для проверки функций
Хотя BaseModel
— это хлеб и масло Pydantic для валидации схем данных, вы также можете использовать Pydantic для валидации аргументов функций с помощью декоратора @validate_call
. Это позволяет создавать надежные функции с информативными ошибками типа без необходимости вручную реализовывать логику валидации.
Чтобы увидеть, как это работает, предположим, что вы пишете функцию, которая отправляет счета клиентам после совершения ими покупки. Ваша функция принимает имя клиента, его электронную почту, купленные товары и общую сумму счета, формирует и отправляет ему письмо. Вам необходимо проверить все эти данные, поскольку их неправильное заполнение может привести к тому, что письмо не будет отправлено, будет неправильно отформатировано или клиенту будет выставлен неверный счет.
Для этого вы пишете следующую функцию:
import time from typing import Annotated from pydantic import PositiveFloat, Field, EmailStr, validate_call @validate_call def send_invoice( client_name: Annotated[str, Field(min_length=1)], client_email: EmailStr, items_purchased: list[str], amount_owed: PositiveFloat, ) -> str: email_str = f""" Dear {client_name}, \n Thank you for choosing xyz inc! You owe ${amount_owed:,.2f} for the following items: \n {items_purchased} """ print(f"Sending email to {client_email}...") time.sleep(2) return email_str
Сначала вы импортируете зависимости, необходимые для написания и аннотирования send_invoice()
. Затем вы создаете функцию send_invoice()
, декорированную @validate_call
.
Перед выполнением send_invoice()
@validate_call
проверяет, что каждый инпут соответствует вашим аннотациям. В данном случае @validate_call
проверяет, что client_name
содержит хотя бы один символ, client_email
правильно отформатирован, items_purchased
представляет собой список строк, а amount_owed
— положительный float.
Если один из инпутов не соответствует вашей аннотации, Pydantic выдаст ошибку, подобную той, что вы уже видели в BaseModel
. Если все входные данные верны, send_invoice()
создает строку и имитирует ее отправку клиенту с помощью time.sleep(2)
.
Примечание. Вы могли заметить, что client_name
аннотировано пайтоновским типом Annotated. В целом, вы можете использовать Annotated, когда хотите предоставить метаданные об аргументе функции. Pydantic рекомендует использовать Annotated, когда вам нужно проверить аргумент функции, имеющий метаданные, указанные Field.
Но если вы используете default_factory
для присвоения аргументу функции значения по умолчанию, вам следует присвоить аргумент непосредственно экземпляру Field. Пример этого можно посмотреть в документации Pydantic.
Чтобы увидеть @validate_call
и send_invoice()
в действии, откройте новый Python REPL и выполните следующий код:
>>> from validate_functions import send_invoice >>> send_invoice( ... client_name="", ... client_email="ajolawsonfakedomain.com", ... items_purchased=["pie", "cookie", 17], ... amount_owed=0, ... ) Traceback (most recent call last): pydantic_core._pydantic_core.ValidationError: 4 validation errors for send_invoice client_name String should have at least 1 character [type=string_too_short, input_value='', input_type=str] For further information visit https://errors.pydantic.dev/2.6/v/string_too_short client_email value is not a valid email address: The email address is not valid. It must have exactly one @-sign. [type=value_error, input_value='ajolawsonfakedomain.com', input_type=str] items_purchased.2 Input should be a valid string [type=string_type, input_value=17, input_type=int] For further information visit https://errors.pydantic.dev/2.6/v/string_type amount_owed Input should be greater than 0 [type=greater_than, input_value=0, input_type=int] For further information visit https://errors.pydantic.dev/2.6/v/greater_than
В этом примере мы импортируем send_invoice()
и передаем недопустимые аргументы функции. @validate_call
распознает это и выбрасывает ошибки, сообщая, что аргумент client_name
должен содержать хотя бы один символ, client_email
невалиден, items_purchased
должен содержать строки, а amount_owed
должен быть больше нуля.
При передаче корректных данных send_invoice()
работает, как и ожидалось:
>>> email_str = send_invoice( ... client_name="Andrew Jolawson", ... client_email="ajolawson@fakedomain.com", ... items_purchased=["pie", "cookie", "cake"], ... amount_owed=20, ... ) Sending email to ajolawson@fakedomain.com... >>> print(email_str) Dear Andrew Jolawson, Thank you for choosing xyz inc! You owe $20.00 for the following items: ['pie', 'cookie', 'cake']
Хотя @validate_call
не так гибок, как BaseModel
, вы все равно можете использовать его для мощной проверки аргументов функции. Это сэкономит вам много времени и позволит избежать написания шаблонной логики проверки типов и валидации. Если вы уже занимались этим, то знаете, насколько громоздким может быть написание утверждений для каждого аргумента функции. Для многих случаев использования @validate_call
позаботится об этом за вас.
В заключительном разделе этого руководства вы узнаете, как можно использовать Pydantic для управления настройками и конфигурацией.
Управление настройками
Один из самых популярных способов настройки приложений Python — это переменные окружения. Переменная окружения — это переменная, которая находится в операционной системе, вне вашего кода Python, но может быть прочитана вашим кодом или другими программами. В качестве примеров данных, которые вы можете захотеть сохранить как переменные окружения, можно привести секретные ключи, учетные данные баз данных, учетные данные API, адреса серверов и токены доступа.
Переменные окружения среды разработки и производственной среды часто разные, и многие из них содержат конфиденциальную информацию. В связи с этим вам нужен надежный способ анализа, проверки и интеграции переменных окружения в ваш код. Это идеальный вариант использования pydantic-settings, и именно его мы рассмотрим в этом разделе.
Настройка приложений с помощью BaseSettings
pydantic-settings
— это один из самых мощных способов управления переменными окружения в Python, который широко используется и рекомендуется такими популярными библиотеками, как FastAPI. Вы можете использовать pydantic-settings для создания моделей, подобных BaseModel
, которые анализируют и проверяют переменные окружения.
Основной класс в pydantic-settings
— BaseSettings
, и он обладает всеми теми же функциями, что и BaseModel
. Но если вы создаете модель, которая наследуется от BaseSettings
, инициализатор модели будет пытаться считывать из переменных окружения любые поля, не переданные в качестве именованных аргументов.
Чтобы понять, как это работает, рассмотрим пример. Предположим, что ваше приложение подключается к базе данных и другому API-сервису. Учетные данные базы данных и ключ API могут меняться со временем и часто меняются в зависимости от того, в какой среде вы развертываете приложение. Чтобы справиться с этим, вы можете создать следующую модель BaseSettings
:
from pydantic import HttpUrl, Field from pydantic_settings import BaseSettings class AppConfig(BaseSettings): database_host: HttpUrl database_user: str = Field(min_length=5) database_password: str = Field(min_length=10) api_key: str = Field(min_length=20)
В этом скрипте мы импортируем зависимости, необходимые для создания модели BaseSettings
. Обратите внимание, что мы импортируем BaseSettings
из pydantic_settings
с подчеркиванием вместо тире. Затем мы определяем модель AppConfig
, которая наследуется от BaseSettings
и хранит поля о нашей базе данных и ключе API. В этом примере database_host
должен быть действительным HTTP URL, а остальные поля имеют ограничение на минимальную длину.
Далее откройте терминал и добавьте следующие переменные окружения. Если вы работаете в Linux, macOS или Windows Bash, вы можете сделать это с помощью команды export
:
(venv) $ export DATABASE_HOST="http://somedatabaseprovider.us-east-2.com" (venv) $ export DATABASE_USER="username" (venv) $ export DATABASE_PASSWORD="asdfjl348ghl@9fhsl4" (venv) $ export API_KEY="ajfsdla48fsdal49fj94jf93-f9dsal"
Вы также можете установить переменные окружения в Windows PowerShell. Затем вы можете открыть новый Python REPL и создать AppConfig
:
>>> from settings_management import AppConfig >>> AppConfig() AppConfig( database_host=Url('http://somedatabaseprovider.us-east-2.com/'), database_user='username', database_password='asdfjl348ghl@9fhsl4', api_key='ajfsdla48fsdal49fj94jf93-f9dsal' )
Обратите внимание, что при создании AppConfig
мы не указываем никаких имен полей. Вместо этого наша модель BaseSettings
считывает поля из заданных нами переменных окружения. Также обратите внимание на то, что мы экспортировали переменные окружения, написанные в верхнем регистре, но AppConfig
успешно разобрала и сохранила их. Это потому, что BaseSettings
не чувствительна к регистру при сопоставлении переменных окружения с именами полей.
Далее закройте Python REPL и создайте невалидные переменные окружения:
(venv) $ export DATABASE_HOST="somedatabaseprovider.us-east-2" (venv) $ export DATABASE_USER="usee" (venv) $ export DATABASE_PASSWORD="asdf" (venv) $ export API_KEY="ajf"
Теперь откройте другой Python REPL и заново запустите AppConfig
:
>>> from settings_management import AppConfig >>> AppConfig() Traceback (most recent call last): pydantic_core._pydantic_core.ValidationError: 4 validation errors for AppConfig database_host Input should be a valid URL, relative URL without a base [type=url_parsing, input_value='somedatabaseprovider.us-east-2', input_type=str] For further information visit https://errors.pydantic.dev/2.6/v/url_parsing database_user String should have at least 5 characters [type=string_too_short, input_value='usee', input_type=str] For further information visit https://errors.pydantic.dev/2.6/v/string_too_short database_password String should have at least 10 characters [type=string_too_short, input_value='asdf', input_type=str] For further information visit https://errors.pydantic.dev/2.6/v/string_too_short api_key String should have at least 20 characters [type=string_too_short, input_value='ajf', input_type=str] For further information visit https://errors.pydantic.dev/2.6/v/string_too_short
Теперь при попытке создать AppConfig
pydantic-settings
выдает ошибку, говоря, что database_host
не является действительным URL, а остальные поля не соответствуют минимальному ограничению по длине.
Хотя это был упрощенный пример конфигурации, вы можете использовать BaseSettings
для парсинга и проверки практически всего, что вам нужно из переменных окружения. Любую проверку, которую можно выполнить с помощью BaseModel
, можно выполнить и с помощью BaseSettings
, включая пользовательскую проверку с помощью валидаторов модели и полей.
Наконец, давайте разберем, как еще лучше настроить поведение BaseSettings
с помощью SettingsConfigDict
.
Кастомизация настроек с помощью SettingsConfigDict
В предыдущем примере вы увидели наглядный пример создания модели BaseSettings
, которая анализирует и проверяет переменные окружения. Однако вы можете захотеть дополнительно настроить поведение вашей модели BaseSettings
. Это можно сделать с помощью SettingsConfigDict
.
Предположим, вы не можете вручную экспортировать все переменные окружения, что часто бывает, и вам нужно прочитать их из файла .env. Вы должны убедиться, что BaseSettings
чувствительна к регистру при парсинге и что в вашем .env-файле нет дополнительных переменных окружения, кроме тех, которые вы указали в модели. Вот как это можно сделать с помощью SettingsConfigDict
:
from pydantic import HttpUrl, Field from pydantic_settings import BaseSettings, SettingsConfigDict class AppConfig(BaseSettings): model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", case_sensitive=True, extra="forbid", ) database_host: HttpUrl database_user: str = Field(min_length=5) database_password: str = Field(min_length=10) api_key: str = Field(min_length=20)
Этот сценарий такой же, как и в предыдущем примере, только на этот раз мы импортировали SettingsConfigDict
и инициализировали его в AppConfig
. В SettingsConfigDict
мы указали, что переменные окружения должны считываться из файла .env, должна соблюдаться чувствительность к регистру, а дополнительные переменные окружения в файле .env запрещены.
Далее создайте файл с именем .env в том же каталоге, что и settings_management.py, и заполните его следующими переменными окружения:
database_host=http://somedatabaseprovider.us-east-2.com/ database_user=username database_password=asdfjfffffl348ghl@9fhsl4 api_key=ajfsdla48fsdal49fj94jf93-f9dsal
Теперь вы можете открыть Python REPL и инициализировать вашу модель AppConfig
:
>>> from settings_management import AppConfig >>> AppConfig() AppConfig( database_host=Url('http://somedatabaseprovider.us-east-2.com/'), database_user='username', database_password='asdfjfffffl348ghl@9fhsl4', api_key='ajfsdla48fsdal49fj94jf93-f9dsal' )
Как видите, AppConfig
успешно разобрала и проверила переменные окружения в вашем файле .env.
Наконец, добавьте несколько невалидных переменных в ваш файл .env:
DATABASE_HOST=http://somedatabaseprovider.us-east-2.com/ database_user=username database_password=asdfjfffffl348ghl@9fhsl4 api_key=ajfsdla48fsdal49fj94jf93-f9dsal extra_var=shouldntbehere
Здесь мы изменили database_host
на DATABASE_HOST
, нарушив ограничение чувствительности к регистру, и добавили дополнительные переменные окружения, которых там не должно быть. Вот как реагирует наша модель, когда пытается проверить это:
>>> from settings_management import AppConfig >>> AppConfig() Traceback (most recent call last): pydantic_core._pydantic_core.ValidationError: 3 validation errors for AppConfig database_host Field required [type=missing, input_value={'database_user': 'userna..._var': 'shouldntbehere'}, input_type=dict] For further information visit https://errors.pydantic.dev/2.6/v/missing DATABASE_HOST Extra inputs are not permitted [type=extra_forbidden, input_value='http://somedatabaseprovider.us-east-2.com/', input_type=str] For further information visit https://errors.pydantic.dev/2.6/v/extra_forbidden extra_var Extra inputs are not permitted [type=extra_forbidden, input_value='shouldntbehere', input_type=str] For further information visit https://errors.pydantic.dev/2.6/v/extra_forbidden
Мы получаем хороший список ошибок, говорящих о том, что database_host
отсутствует и что у нас есть лишние переменные окружения в нашем .env-файле. Обратите внимание, что из-за ограничения чувствительности к регистру наша модель считает, что DATABASE_HOST
является дополнительной переменной наряду с extra_var
.
С помощью SettingsConfigDict
и BaseSettings
можно сделать гораздо больше, но эти примеры должны дать вам представление о том, как можно использовать pydantic-settings
для управления переменными окружения в вашем конкретном случае.
Заключение
Pydantic — это простая в использовании, быстрая и широко распространенная библиотека проверки данных на Python. В этой статье вы получили широкий обзор Pydantic, и теперь у вас есть знания и ресурсы, необходимые для того, чтобы начать использовать эту библиотеку в ваших собственных проектах.
Перевод статьи «Pydantic: Simplifying Data Validation in Python».