コンテンツにスキップ

Pydantic を使用する理由

現在、Pydantic は月に何度もダウンロードされ、世界最大規模で最も有名な組織によって使用されています。

6 年前の開始以来、なぜこれほど多くの人が Pydantic を採用しているのかを知るのは困難ですが、ここではいくつかの推測を示します。

スキーマ検証を強化するタイプヒント

Pydantic が検証するスキーマは、通常、Python の型ヒントによって定義されます。

最新の Python を作成している場合は、型ヒントの使用方法をすでに知っているため、型ヒントはこれに最適です。型ヒントを使用するということは、Pydantic が mypy や pyright などの静的型指定ツールや、pycharm や vscode などの IDE とうまく統合されることも意味します。

???+ example "Example - just type hints" (This example requires Python 3.9+) ```py requires="3.9" from typing import Annotated, Dict, List, Literal, Tuple

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. The `name` field is simply annotated with `str` - any string is allowed.
2. The [`Literal`](https://docs.python.org/3/library/typing.html#typing.Literal) type is used to enforce that `color` is either `'red'` or `'green'`.
3. Even when we want to apply constraints not encapsulated in python types, we can use [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated) and [`annotated-types`](https://github.com/annotated-types/annotated-types) to enforce constraints without breaking type hints.
4. I'm not claiming "bazam" is really an attribute of fruit, but rather to show that arbitrarily complex types can easily be validated.

!!! tip "Learn more" See the documentation on supported types.

Performance

Pydantic's core validation logic is implemented in a separate package pydantic-core, where validation for most types is implemented in Rust.

As a result, Pydantic is among the fastest data validation libraries for Python.

??? example "Performance Example - Pydantic vs. dedicated code" In general, dedicated code should be much faster than a general-purpose validator, but in this example Pydantic is >300% faster than dedicated code when parsing JSON and validating URLs.

```py title="Performance Example"
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
```

Unlike other performance-centric libraries written in compiled languages, Pydantic also has excellent support for customizing validation via functional validators.

!!! tip "Learn more" Samuel Colvin's talk at PyCon 2023 explains how pydantic-core works and how it integrates with Pydantic.

Serialization

Pydantic provides functionality to serialize model in three ways:

  1. To a Python dict made up of the associated Python objects
  2. To a Python dict made up only of "jsonable" types
  3. To a JSON string

In all three modes, the output can be customized by excluding specific fields, excluding unset fields, excluding default values, and excluding None values

??? example "Example - Serialization 3 ways" ```py 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"}
```

!!! tip "Learn more" See the documentation on serialization.

JSON Schema

JSON Schema can be generated for any Pydantic schema — allowing self-documenting APIs and integration with a wide variety of tools which support JSON Schema.

??? example "Example - JSON Schema" ```py from datetime import datetime

from pydantic import BaseModel クラスアドレス(BaseModel): 通り: str 都市: str 郵便番号: str クラス会議(BaseModel): いつ: 日時 ここで: 住所 なぜ: str = 'わからない' print(Meeting.model_json_schema()) 「」 { ' $defs': { '住所': { 'プロパティ': { 'ストリート': {'タイトル': 'ストリート', 'タイプ': '文字列'}, 'city': {'title': 'City', 'type': 'string'}, '郵便番号': {'タイトル': '郵便番号', 'タイプ': '文字列'}, }, '必須': ['番地', '都市', '郵便番号'], 'タイトル': '住所', 'タイプ': 'オブジェクト', } }, 'プロパティ': { 'when': {'format': 'date-time', 'title': 'When', 'type': 'string'}, 'どこ': {'$ ref': '#/$defs/Address'}, 'why': {'default': 'No idea', 'title': 'Why', 'type': 'string'}, }, '必須': ['いつ'、'どこ'], 'タイトル': '会議', 'タイプ': 'オブジェクト', } 「」 ```

Pydantic generates JSON Schema version 2020-12, the latest version of the standard which is compatible with OpenAPI 3.1.

!!!ヒント "詳細" JSON スキーマに関するドキュメントを参照してください。

Strict モードとデータ強制

デフォルトでは、Pydantic は一般的な不正な型を許容し、データを正しい型に強制します。たとえば、int フィールドに渡された数値文字列は int として解析されます。

Pydantic には、「Strict モード」とも呼ばれる strict=True モードもあり、入力データがスキーマまたは型ヒントと正確に一致しない限り、型は強制されず、検証エラーが発生します。

ただし、JSON には datetime、UUID、bytes などの多くの一般的な Python 型と一致する型がないため、JSON データを検証する場合は厳密モードはほとんど役に立ちません。

これを解決するために、Pydantic は 1 ステップで JSON を解析して検証できます。これにより、RFC3339 (別名 ISO8601) 文字列から日時オブジェクトへの適切なデータ変換が可能になります。 JSON 解析は Rust で実装されているため、パフォーマンスも非常に優れています。

??? example "例 - 実際に便利な Strict モード" ```py 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'
```

!!!ヒント "詳細" 厳密モードに関するドキュメントを参照してください。

データクラス、TypedDict など

Pydantic では、スキーマを作成し、検証とシリアル化を実行する 4 つの方法を提供します。

  1. BaseModel — インスタンス メソッド経由で利用できる多くの一般的なユーティリティを備えた Pydantic 独自のスーパークラス。
  2. pydantic.dataclasses.dataclass — データクラスの初期化時に検証を実行する標準データクラスのラッパー。
  3. TypeAdapter — 検証とシリアル化のために任意の型を適応させる一般的な方法。これにより、TypedDict や NamedTuple などの型だけでなく、int や timedelta などの単純なスカラー値も検証できるようになり、サポートされているすべての型を TypeAdapter で使用できます。
  4. validate_call — 関数呼び出し時に検証を実行するデコレータ。

??? example "Example - schema based on TypedDict" ```py 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` for a `TypedDict` performing validation, it can also validate JSON data directly with `validate_json`
2. `dump_python` to serialise a `TypedDict` to a python object, it can also serialise to JSON with `dump_json`
3. `TypeAdapter` can also generate JSON Schema

Customisation

Functional validators and serializers, as well as a powerful protocol for custom types, means the way Pydantic operates can be customized on a per-field or per-type basis.

??? example "Customisation Example - wrap validators" "wrap validators" are new in Pydantic V2 and are one of the most powerful ways to customize Pydantic validation. ```py from datetime import datetime, timezone

from pydantic import BaseModel, field_validator


class Meeting(BaseModel):
    when: datetime

    @field_validator('when', mode='wrap')
    def when_now(cls, input_value, handler):
        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(+01:00))
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)
```

!!! tip "Learn more" See the documentation on validators, custom serializers, and custom types.

Ecosystem

At the time of writing there are 214,100 repositories on GitHub and 8,119 packages on PyPI that depend on Pydantic.

Some notable libraries that depend on Pydantic:

More libraries using Pydantic can be found at Kludex/awesome-pydantic.

Organisations using Pydantic

Some notable companies and organisations using Pydantic together with comments on why/how we know they're using Pydantic.

The organisations below are included because they match one or more of the following criteria:

  • Using pydantic as a dependency in a public repository
  • Referring traffic to the pydantic documentation site from an organization-internal domain - specific referrers are not included since they're generally not in the public domain
  • Direct communication between the Pydantic team and engineers employed by the organization about usage of Pydantic within the organization

We've included some extra detail where appropriate and already in the public domain.

{{ organisations }}


本文总阅读量