pydantic について
Pydantic は型ヒントに基づいて実行時の検証とシリアライズを行う Python ライブラリです。外部または信頼できないデータを扱う用途に向け、フィールド制約や型変換、複数の検証方法を備えています。
主な機能
- 01型ヒントに基づく検証
- 02データのシリアライズ
- 03フィールドのメタデータと制約
- 04型変換
- 05独自および注釈型バリデーター
活用シーン
- 01HTTP API が受け取るデータの検証
- 02外部データの型付きモデルへの解析
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ pydantic/ skills --skill pydanticインストール後、Claude Code で/pydanticを入力して読み込まれたか確認します。
詳細
Pydantic によるバリデーション
一言でいうと、Pydantic は実行時バリデーション機能を備えたデータクラスです。型ヒントを利用して、バリデーション(およびシリアライズ)の方法を判断します。主に、HTTP API の定義など、信頼できない外部データを扱う場合に役立ちます。
ユーザーコード内でインスタンス化するクラスの定義に Pydantic を使うことは、一般に 推奨されません。そうすると柔軟性が失われます(たとえば、Pydantic がサポートしていない型を使えなくなり、初期化後の変更も難しくなります)。この場合は通常、通常のクラス(または標準ライブラリのデータクラス)を使うほうが適切です。静的型チェッカーが型の不一致を検出してくれるためです。
基本的な使い方
Pydantic モデルを使った簡単な例を次に示します。
from datetime import date
from pydantic import BaseModel, Field
class Person(BaseModel):
name: str
age: int = Field(description='The age of the person')
birthdate: date | None = None
p = Person(name='John', age=20, birthdate='1970-01-01')
Pydantic は互換性のある入力を型強制します。ISO 形式の日付文字列 '1970-01-01' は date に変換されます。
制約とフィールドメタデータ
Field() 関数はメタデータと制約の指定に使います。次の 2 種類のメタデータを区別する必要があります。
- フィールド固有のメタデータ:
deprecatedやaliasのように、フィールドに付与された場合にのみ意味を持つメタデータです。 - 型固有のメタデータ:
gtやmax_lengthなどの制約に加え、JSON Schema に影響するメタデータ(例:description、title)が含まれます。
モデルフィールドは、代入形式の Field() を使って宣言します。
from pydantic import BaseModel, Field
class User(BaseModel):
first_name: str = Field(alias='name')
または、注釈パターンを使って宣言します。
from typing import Annotated
from pydantic import BaseModel, Field
class Model(BaseModel):
value: Annotated[int, Field(deprecated=True)] = 1
注釈パターンには、次のような利点があります。
f: <type> = Field()の形式(デフォルト値なし)は紛らわしく、実際には必須フィールドであるにもかかわらず、fにデフォルト値があると誤解させる可能性があります。- フィールドには、任意の数のメタデータ要素を指定できます。上の例で示したように、
Field()関数がサポートする制約やメタデータは限られているため、場合によってはWithJsonSchemaなど、別の Pydantic ユーティリティを使う必要があります。
ただし、次の点に注意してください。
-
静的型チェッカーにとって意味のあるメタデータには、代入形式を使ってください。これには
alias、default、default_factoryが含まれます。 -
フィールド固有のメタデータは、「最上位」の型にのみ使用できます。よくある落とし穴は、次のように記述することです。
from typing import Annotated from pydantic import BaseModel, Field class Model(BaseModel): field_bad: Annotated[int, Field(deprecated=True)] | None = None field_ok: Annotated[int | None, Field(deprecated=True)] = Noneこの例では、フィールド固有のメタデータはユニオン全体に適用してください。
制約
可能な限り、カスタムバリデーターを定義するのではなく、「組み込み」のバリデーション制約を使ってください。
from typing import Annotated
from annotated_types import Gt # annotated_types is an alternative to the `Field()` function.
from pydantic import BaseModel, field_validator
class Model(BaseModel):
constrained_int_ok: Annotated[int, Gt(1)] # This is good
constrained_int_bad: int
@field_validator('constrained_int_bad') # This is bad
@classmethod
def validate(cls, v: int) -> int:
if not v > 1:
raise ValueError('Value is not greater than 1')
return v
Field() 関数では表現できない制約もあります。たとえば、strip_whitespace、to_upper、to_lower、ascii_only などの文字列制約は、pydantic.StringConstraints を使ってのみ指定できます。
from typing import Annotated
from pydantic import BaseModel, StringConstraints
class Model(BaseModel):
# Do this instead of a validator calling s.strip():
a: Annotated[str, StringConstraints(strip_whitespace=True)]
https://pydantic.dev/docs/validation/latest/api/pydantic/standard_library_types/ は、サポートされているすべての標準ライブラリ型とその制約についての公式ドキュメントです。
バリデーター
状況によっては、カスタムバリデーターが必要になることがあります。可能な限り、after バリデーターを使ってください。Pydantic のバリデーション後に実行されるため、値はすでにフィールドの型になっています。before バリデーターを使う場合、入力データは文字どおり何でもあり得るため、誤りが起きやすくなります(特にモデルバリデーターでは、入力は必ずしも辞書とは限らず、任意のオブジェクトの場合もあります)。
可能であれば、バリデーターには注釈パターンを使うことをおすすめします。
from typing import Annotated
from pydantic import AfterValidator, BaseModel, field_validator
def is_even(value: int) -> int:
if value % 2 == 1:
raise ValueError(f'{value} is not an even number')
return value
class Model(BaseModel):
# Prefer this form: the validator is right next to the field, making it easy to understand
even: Annotated[int, AfterValidator(is_even)]
odd: int
# If you define a validator as decorator, make sure to define it as classmethod.
@field_validator('odd', mode='after')
@classmethod
def is_odd(cls, value: int) -> int:
if value % 2 == 0:
raise ValueError(f'{value} is not an odd number')
return value
デコレーターパターンを使うと、特にバリデーターが実行される順序(とりわけサブクラスの場合)について、動作がわかりにくくなることがあります。
型の強制変換、コレクション、ユニオン
厳密モードを使用していない場合、Pydantic はほとんどのケースで型の強制変換を適用します。たとえば、int 型のフィールドでは、'123' のような文字列も受け入れられます。これはコレクション型にも当てはまり、list[str] ではタプルやセットなども受け入れられます。
そのため、次のことは避けてください。
- バリデーターを使って
strをintに強制変換することが目的であれば、int | strのようなユニオンを使うこと。 - リストとタプルの両方を受け入れることが目的であれば、
collections.abc.Sequenceのような抽象コレクションを使うこと。こうした抽象コレクションの使用は非効率です。
一般に、ユニオンは避けるのが最善です。フィールドを使うたびに、何らかの処理を行う前に各型を確認する必要があるためです。
前方参照アノテーション
Python では、文字列を使ってアノテーションを前方参照として記述できます。これにより Pydantic での評価が難しくなる場合があるため、可能であれば避けるのが最善です。
モジュール内で Pydantic モデルを定義する場合、可能であれば from __future__ import annotations(すべてのアノテーションをデフォルトで文字列化します)の使用を避けてください。まだ定義されていないアノテーションにのみ、明示的に引用符を付けてください。例:
from pydantic import BaseModel
class Model(BaseModel):
self_ref: 'Model'
また、Python >= 3.14 ではアノテーションの評価が遅延されるため、文字列アノテーションは使用しないでください。
再帰的な型エイリアス
次のようなエイリアスを定義したくなるかもしれません。
from typing import TypeAlias
JsonValue: TypeAlias = 'list[JsonValue] | dict[str, JsonValue] | str | bool | int | float | None'
再帰的なため、このエイリアスは引用符で囲む必要があります。Pydantic は通常、引用符で囲まれた TypeAlias を評価できません。代わりに、Pydantic が解決できる明示的な型エイリアス(Python 3.12 以降では type、または TypeAliasType)を使用してください。
type JsonValue = list[JsonValue] | dict[str, JsonValue] | str | bool | int | float | None
# Or, if not on Python >= 3.12:
from typing_extensions import TypeAliasType
JsonValue = TypeAliasType('JsonValue', 'list[JsonValue] | dict[str, JsonValue] | str | bool | int | float | None')
モデルのサブクラス、判別可能ユニオン
サブクラス化は Python で非常によく使われるパターンですが、Pydantic では問題の原因になることがあります。次のようにしたくなるかもしれません。
from pydantic import BaseModel
class Base(BaseModel):
base_field: int
def common_method(self) -> None: ...
class Sub1(Base):
sub1_field: str
class Sub2(Base):
sub2_field: bool
class Main(BaseModel):
model: Base
m: Main = Main(model=Sub1(base_field=1, sub1_field='test'))
この例は動作しますが、m のシリアル化時に期待どおりには動作しません。
m.model_dump()
#> {'model': {'base_field': 1}} -> sub1_field missing
これは、Pydantic が実行時のサブクラスではなく、宣言された型(Base)に従ってシリアル化するためです。検証も同じ規則に従います。Main(model={'base_field': 1, 'sub1_field': 'test'}) は Base に対して検証されるため、Sub1 インスタンスが生成されるのではなく、sub1_field は無視されます。
代わりに、判別可能ユニオン(モデルを区別するために type フィールドを設定できる場合)を使ってみてください。
from typing import Annotated, Literal, TypeAlias
from pydantic import BaseModel, Field
class Sub1(Base):
type: Literal['sub1']
sub1_field: str
class Sub2(Base):
type: Literal['sub2']
sub2_field: bool
Subs: TypeAlias = Annotated[Sub1 | Sub2, Field(discriminator='type')]
class Main(BaseModel):
model: Subs
または、ジェネリクスを使ってください。
from pydantic import BaseModel
class Main[BaseT: Base](BaseModel):
model: BaseT
m: Main[Sub1] = Main[Sub1](model={'base_field': 1, 'sub1_field': 'test'}) # Will work
判別可能ユニオンもジェネリクスも適さない場合は、最後の手段として、Pydantic >=2.13 ではポリモーフィックシリアル化、Pydantic <2.13 では任意の型としてシリアル化を使用できます。