Перейти к содержанию

Зачем использовать проверку данных с помощью Pydantic?

Сегодня Pydantic ежемесячно скачивают множество раз, а используют его одни из крупнейших и наиболее известных организаций в мире.

Трудно точно сказать, почему с момента появления Pydantic шесть лет назад он завоевал такую ​​популярность, но можно выдвинуть несколько предположений.

Использование аннотаций типов для проверки схемы

Схема, на соответствие которой Pydantic выполняет проверку, как правило, определяется с помощью аннотаций типов (type hints) Python.

Аннотации типов отлично подходят для этой задачи: если вы пишете код на современном Python, то наверняка уже умеете ими пользоваться. Кроме того, благодаря использованию аннотаций типов Pydantic хорошо сочетается со средствами статического анализа кода (например, mypy и Pyright), а также с различными IDE (такими как PyCharm и VSCode).

Пример — только подсказки типов
from typing import Annotated, Literal

from annotated_types import Gt

from pydantic import BaseModel


class Fruit(BaseModel):
    name: str  # (1)!
    color: Literal['red', 'green']  # (2)!
    weight: Annotated[float, Gt(0)]  # (3)!
    bazam: dict[str, list[tuple[int, bool, float]]]  # (4)!


print(
    Fruit(
        name='Apple',
        color='red',
        weight=4.2,
        bazam={'foobar': [(1, True, 0.1)]},
    )
)
#> name='Apple' color='red' weight=4.2 bazam={'foobar': [(1, True, 0.1)]}
  1. Поле name просто помечено типом str — допустима любая строка.
  2. Тип [Literal][typing.Literal] используется для того, чтобы ограничить возможные значения поля color вариантами 'red' или 'green'.
  3. Даже если требуется наложить ограничения, не предусмотренные стандартными типами Python, можно воспользоваться [Annotated][typing.Annotated] и библиотекой annotated-types — это позволит задать нужные условия, сохранив при этом поддержку типизации.
  4. Я не утверждаю, что «bazam» — это действительно свойство фрукта; этот пример лишь показывает, что можно легко проверять корректность типов любой сложности.

Производительность

Основная логика валидации в Pydantic реализована в отдельном пакете (pydantic-core), в котором проверка большинства типов выполнена на языке Rust.

Благодаря этому Pydantic является одной из самых быстрых библиотек для валидации данных в Python.

Пример производительности — Pydantic против специализированного кода

Как правило, специализированный код должен работать значительно быстрее, чем универсальный валидатор, однако в данном примере Pydantic оказывается более чем на 300% быстрее специализированного кода при синтаксическом разборе JSON и проверке URL-адресов.

Пример производительности
import json
import timeit
from urllib.parse import urlparse

import requests

from pydantic import HttpUrl, TypeAdapter

reps = 7
number = 100
r = requests.get('https://api.github.com/emojis')
r.raise_for_status()
emojis_json = r.content


def emojis_pure_python(raw_data):
    data = json.loads(raw_data)
    output = {}
    for key, value in data.items():
        assert isinstance(key, str)
        url = urlparse(value)
        assert url.scheme in ('https', 'http')
        output[key] = url


emojis_pure_python_times = timeit.repeat(
    'emojis_pure_python(emojis_json)',
    globals={
        'emojis_pure_python': emojis_pure_python,
        'emojis_json': emojis_json,
    },
    repeat=reps,
    number=number,
)
print(f'pure python: {min(emojis_pure_python_times) / number * 1000:0.2f}ms')
#> pure python: 5.32ms

type_adapter = TypeAdapter(dict[str, HttpUrl])
emojis_pydantic_times = timeit.repeat(
    'type_adapter.validate_json(emojis_json)',
    globals={
        'type_adapter': type_adapter,
        'HttpUrl': HttpUrl,
        'emojis_json': emojis_json,
    },
    repeat=reps,
    number=number,
)
print(f'pydantic: {min(emojis_pydantic_times) / number * 1000:0.2f}ms')
#> pydantic: 1.54ms

print(
    f'Pydantic {min(emojis_pure_python_times) / min(emojis_pydantic_times):0.2f}x faster'
)
#> Pydantic 3.45x faster

В отличие от других высокопроизводительных библиотек, написанных на компилируемых языках, Pydantic также предлагает отличные возможности для настройки валидации с помощью функциональных валидаторов.

Узнать больше

Сэмюэль Колвин в докладе на PyCon 2023 объясняет, как работает pydantic-core и как он интегрируется с Pydantic.

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

Pydantic предоставляет возможность сериализации модели тремя способами:

  1. В словарь Python (dict), состоящий из соответствующих объектов Python.
  2. В словарь Python (dict), состоящий только из типов, поддерживающих сериализацию в JSON.
  3. В JSON-строку.

Во всех трех режимах результат можно настроить: исключить определенные поля, поля, которые не были заданы, значения по умолчанию, а также значения None.

Пример — 3 способа сериализации
from datetime import datetime

from pydantic import BaseModel


class Meeting(BaseModel):
    when: datetime
    where: bytes
    why: str = 'No idea'


m = Meeting(when='2020-01-01T12:00', where='home')
print(m.model_dump(exclude_unset=True))
#> {'when': datetime.datetime(2020, 1, 1, 12, 0), 'where': b'home'}
print(m.model_dump(exclude={'where'}, mode='json'))
#> {'when': '2020-01-01T12:00:00', 'why': 'No idea'}
print(m.model_dump_json(exclude_defaults=True))
#> {"when":"2020-01-01T12:00:00","where":"home"}

Узнать больше

Смотри documentation on serialization.

JSON схема

Для любой схемы Pydantic можно сгенерировать JSON схему, что позволяет создавать самодокументируемые API и обеспечивает интеграцию с широким спектром инструментов, поддерживающих формат JSON Schema.

Пример - JSON схема
from datetime import datetime

from pydantic import BaseModel


class Address(BaseModel):
    street: str
    city: str
    zipcode: str


class Meeting(BaseModel):
    when: datetime
    where: Address
    why: str = 'No idea'


print(Meeting.model_json_schema())
"""
{
    '$defs': {
        'Address': {
            'properties': {
                'street': {'title': 'Street', 'type': 'string'},
                'city': {'title': 'City', 'type': 'string'},
                'zipcode': {'title': 'Zipcode', 'type': 'string'},
            },
            'required': ['street', 'city', 'zipcode'],
            'title': 'Address',
            'type': 'object',
        }
    },
    'properties': {
        'when': {'format': 'date-time', 'title': 'When', 'type': 'string'},
        'where': {'$ref': '#/$defs/Address'},
        'why': {'default': 'No idea', 'title': 'Why', 'type': 'string'},
    },
    'required': ['when', 'where'],
    'title': 'Meeting',
    'type': 'object',
}
"""

Pydantic соответствует последней версии спецификации JSON схемы (2020-12), которая совместима с OpenAPI 3.1.

Узнать больше

Смотри documentation on JSON Schema.

Строгий режим и приведение типов данных

По умолчанию Pydantic не допускает распространенных неправильных типов и приводит данные к правильному типу — например числовая строка, переданная в поле int, будет анализироваться как int.

В Pydantic также есть строгий режим, где типы не приводятся и ошибка проверки возникает, если входные данные точно не соответствуют ожидаемой схеме.

Но строгий режим был бы совершенно бесполезен при проверке данных JSON, поскольку JSON не имеет совпадающих типов. многие распространенные типы Python, такие как [datetime][datetime.datetime], [UUID][uuid.UUID] или [bytes][].

Чтобы решить эту проблему, Pydantic может анализировать и проверять JSON за один шаг. Это позволяет разумно преобразовывать данные. (например, при анализе строк в объекты [datetime][datetime.datetime]). Поскольку анализ JSON реализован на Rust, он также очень эффективен.

Пример — строгий режим, который действительно полезен
from datetime import datetime

from pydantic import BaseModel, ValidationError


class Meeting(BaseModel):
    when: datetime
    where: bytes


m = Meeting.model_validate({'when': '2020-01-01T12:00', 'where': 'home'})
print(m)
#> when=datetime.datetime(2020, 1, 1, 12, 0) where=b'home'
try:
    m = Meeting.model_validate(
        {'when': '2020-01-01T12:00', 'where': 'home'}, strict=True
    )
except ValidationError as e:
    print(e)
    """
    2 validation errors for Meeting
    when
      Input should be a valid datetime [type=datetime_type, input_value='2020-01-01T12:00', input_type=str]
    where
      Input should be a valid bytes [type=bytes_type, input_value='home', input_type=str]
    """

m_json = Meeting.model_validate_json(
    '{"when": "2020-01-01T12:00", "where": "home"}'
)
print(m_json)
#> when=datetime.datetime(2020, 1, 1, 12, 0) where=b'home'

Узнать больше

Смотри documentation on strict mode.

Dataclasses, TypedDicts, и многое другое

Pydantic предлагает четыре способа создания схем, а также выполнения валидации и сериализации:

  1. BaseModel — базовый класс Pydantic, предоставляющий множество полезных инструментов, доступных через методы экземпляра.
  2. Pydantic dataclasses — обертка над стандартными классами данных (dataclasses), дополненная механизмами валидации.
  3. [TypeAdapter][pydantic.type_adapter.TypeAdapter] — универсальный инструмент для адаптации любого типа данных к процессам валидации и сериализации. Он позволяет выполнять валидацию как для таких структур, как TypedDict и NamedTuple, так и для простых типов (например, [int][] или [timedelta][datetime.timedelta]). С помощью [TypeAdapter][pydantic.type_adapter.TypeAdapter] можно работать со всеми типами, поддерживаемыми Pydantic.
  4. validate_call — декоратор, обеспечивающий валидацию данных при вызове функции.
Пример — схема на основе [TypedDict][typing.TypedDict]
from datetime import datetime

from typing_extensions import NotRequired, TypedDict

from pydantic import TypeAdapter


class Meeting(TypedDict):
    when: datetime
    where: bytes
    why: NotRequired[str]


meeting_adapter = TypeAdapter(Meeting)
m = meeting_adapter.validate_python(  # (1)!
    {'when': '2020-01-01T12:00', 'where': 'home'}
)
print(m)
#> {'when': datetime.datetime(2020, 1, 1, 12, 0), 'where': b'home'}
meeting_adapter.dump_python(m, exclude={'where'})  # (2)!

print(meeting_adapter.json_schema())  # (3)!
"""
{
    'properties': {
        'when': {'format': 'date-time', 'title': 'When', 'type': 'string'},
        'where': {'format': 'binary', 'title': 'Where', 'type': 'string'},
        'why': {'title': 'Why', 'type': 'string'},
    },
    'required': ['when', 'where'],
    'title': 'Meeting',
    'type': 'object',
}
"""
  1. [TypeAdapter][pydantic.type_adapter.TypeAdapter] для [TypedDict][typing.TypedDict] выполняет проверку данных (валидацию); кроме того, он позволяет проверять JSON-данные напрямую с помощью метода [validate_json][pydantic.type_adapter.TypeAdapter.validate_json].
  2. Метод [dump_python][pydantic.type_adapter.TypeAdapter.dump_python] используется для сериализации [TypedDict][typing.TypedDict] в объект Python; также возможна сериализация в формат JSON с помощью метода [dump_json][pydantic.type_adapter.TypeAdapter.dump_json].
  3. [TypeAdapter][pydantic.type_adapter.TypeAdapter] также может генерировать JSON-схему (JSON Schema).

Настройка

Благодаря функциональным валидаторам и сериализаторам, а также мощному протоколу для пользовательских типов, поведение Pydantic можно гибко настраивать — как для отдельных полей, так и для целых типов данных.

Пример настройки — обертки для валидаторов

«Wrap-валидаторы» — это нововведение в Pydantic V2; они представляют собой один из самых мощных инструментов для настройки процесса валидации.

from datetime import datetime, timezone
from typing import Any

from pydantic_core.core_schema import ValidatorFunctionWrapHandler

from pydantic import BaseModel, field_validator


class Meeting(BaseModel):
    when: datetime

    @field_validator('when', mode='wrap')
    def when_now(
        cls, input_value: Any, handler: ValidatorFunctionWrapHandler
    ) -> datetime:
        if input_value == 'now':
            return datetime.now()
        when = handler(input_value)
        # in this specific application we know tz naive datetimes are in UTC
        if when.tzinfo is None:
            when = when.replace(tzinfo=timezone.utc)
        return when


print(Meeting(when='2020-01-01T12:00+01:00'))
#> when=datetime.datetime(2020, 1, 1, 12, 0, tzinfo=TzInfo(3600))
print(Meeting(when='now'))
#> when=datetime.datetime(2032, 1, 2, 3, 4, 5, 6)
print(Meeting(when='2020-01-01T12:00'))
#> when=datetime.datetime(2020, 1, 1, 12, 0, tzinfo=datetime.timezone.utc)

Экосистема

На момент написания этого текста в GitHub насчитывается 466 400 репозиториев, а в PyPI — 8 119 пакетов, зависящих от Pydantic.

Вот некоторые известные библиотеки, использующие Pydantic:

Некоторые примечательные библиотеки, зависящие от Pydantic:

Больше библиотек, использующих Pydantic, можно найти в репозитории Kludex/awesome-pydantic.

Команда разработчиков Pydantic также создает Pydantic Logfire — платформу для обеспечения наблюдаемости (observability) с нативной интеграцией Pydantic, предназначенную для трассировки и отладки процессов валидации в рабочей среде (production).

Организации, использующие Pydantic

Ниже представлены некоторые известные компании и организации, использующие Pydantic, а также пояснения о том, почему или откуда нам известно об этом использовании.

Организации включены в этот список, если они соответствуют одному или нескольким из следующих критериев:

  • Используют Pydantic в качестве зависимости в публичном репозитории.
  • Направляют трафик на документацию Pydantic с домена, принадлежащего организации (конкретные источники переходов не указываются, так как эта информация, как правило, не является публичной).
  • Осуществляли прямое общение с командой Pydantic по поводу использования библиотеки внутри своей организации.

Там, где это уместно и информация уже находится в открытом доступе, мы добавили дополнительные подробности.

Adobe

adobe/dy-sql использует Pydantic.

Amazon и AWS

Anthropic

anthropics/anthropic-sdk-python использует Pydantic.

Apple

(На основании критериев, описанных выше)

ASML

(На основании критериев, описанных выше)

AstraZeneca

Многие репозитории в организации AstraZeneca на GitHub зависят от Pydantic.

Cisco Systems

Capital One

(На основании критериев, описанных выше)

Comcast

(На основании критериев, описанных выше)

Datadog

  • Широкое использование Pydantic в DataDog/integrations-core и других репозиториях.
  • Общение с инженерами Datadog о том, как они применяют Pydantic.

Facebook

Несколько репозиториев в организации facebookresearch на GitHub зависят от Pydantic.

GitHub

В 2022 году GitHub предоставил Pydantic спонсорскую поддержку в размере 750 долларов.

Google

Активное использование Pydantic в google/turbinia и других репозиториях.

HSBC

(На основании критериев, описанных выше)

IBM

Ряд репозиториев в организации IBM на GitHub зависят от Pydantic.

Intel

(На основании критериев, описанных выше)

Intuit

(На основании критериев, описанных выше)

Intergovernmental Panel on Climate Change

Твит объяснение того, как МГЭИК использует Pydantic.

JPMorgan

(На основании критериев, описанных выше)

Jupyter

  • Разработчики Jupyter Notebook используют Pydantic в своих подпроектах.
  • В Jupyverse — сервере для Jupyter, построенном на базе FastAPI.
  • В системе управления конфигурациями FPS.

Microsoft

  • Библиотека для оптимизации глубокого обучения DeepSpeed активно использует Pydantic.
  • Pydantic применяется во многих репозиториях, принадлежащих организации microsoft на GitHub.
  • Pydantic также используется в организации Azure на GitHub.
  • В комментариях на GitHub можно увидеть, что инженеры Microsoft применяют Pydantic при разработке Windows и Office.

Molecular Science Software Institute

Ряд репозиториев в организации MolSSI на GitHub зависят от Pydantic.

NASA

Ряд репозиториев в организации NASA на GitHub зависят от Pydantic.

NASA также использует Pydantic (через FastAPI) в проекте JWST для обработки изображений, полученных с космического телескопа «Джеймс Уэбб»; см. этот твит.

Netflix

Ряд репозиториев в организации Netflix на GitHub зависят от Pydantic.

NSA

Репозиторий nsacyber/WALKOFF зависит от Pydantic.

NVIDIA

Ряд репозиториев в организации NVIDIA на GitHub зависят от Pydantic.

Согласно их документации, их «Omniverse Services» зависят от Pydantic.

OpenAI

Как следует из этого обсуждения на GitHub, OpenAI использует Pydantic в своем API ChatCompletions.

По имеющимся сведениям, OpenAI также активно применяет Pydantic в своих внутренних сервисах.

Oracle

(На основании критериев, описанных выше)

Palantir

(На основании критериев, описанных выше)

Qualcomm

(На основании критериев, описанных выше)

Red Hat

(На основании критериев, описанных выше)

Revolut

По имеющимся сведениям, все внутренние сервисы в Revolut построены с использованием FastAPI и, следовательно, Pydantic.

Robusta

Репозиторий robusta-dev/robusta зависит от Pydantic.

Salesforce

В 2022 году компания Salesforce выделила Сэмюэлу Колвину 10 000 долларов на работу над Pydantic.

Starbucks

(На основании критериев, описанных выше)

Texas Instruments

(На основании критериев, описанных выше)

Twilio

(На основании критериев, описанных выше)

Twitter

В репозитории Twitter the-algorithm, где компания открыла исходный код своего механизма рекомендаций, используется Pydantic.

UK Home Office

(На основании критериев, описанных выше)