Open4

Pydanticを調べて使ってみる-2

ikeponikepon

Pydantic v2.11

https://docs.pydantic.dev/latest/

1. Pydanticの基礎

Pydantic は Python の型ヒントを活用して、データの検証(バリデーション)と変換(シリアライズ) を自動で行うライブラリです。

  • 🔧 型に基づく自動バリデーション
  • 🚀 Rust 製コアによる高速処理(pydantic-core)
  • 🔗 FastAPI との親和性が高く、API設計にも向いている

2. Pydantic による定義

📌 モデルの基本定義

Pydantic では、データ構造を Python のクラスで型安全に定義することが基本です。
この定義を「モデル(Model)」と呼びます。

✅ 1. モデルは BaseModel を継承して作る

Pydantic の全てのモデルは BaseModel を継承します。
この継承によって、バリデーション、変換、補完機能が自動で有効になります。

from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    email: str

この例では:

  • UserBaseModel を継承した データ定義クラス
  • id, name, emailフィールド(属性) で、それぞれのを指定しています

✅ 2. 型に基づいて自動でバリデーションされる

u = User.model_validate({
    "id": "123",
    "name": "Alice",
    "email": "alice@example.com"
})

このコードでは:

  • id"123"(文字列)を渡しても、int型に自動変換される
  • Pydantic は「id は int であるべき」と理解し、型変換 or エラーを自動で判断します

✅ 3. 型が合わない場合はエラーになる

u = User.model_validate({
    "id": "abc",
    "name": "Alice",
    "email": "alice@example.com"
})

→ このときは id="abc" が int に変換できないので、ValidationError が発生します。

ValidationError: 1 validation error for User
id
  Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='abc', input_type=str]
    For further information visit https://errors.pydantic.dev/2.11/v/int_parsing

✅ 4. 必須フィールドとオプションの扱い

デフォルト値なしで型だけ書くと、そのフィールドは必須になります。

class User(BaseModel):
    id: int                  # 必須
    name: Optional[str]      # 任意(None可)
  • id を省略するとエラーになります
  • name を省略すると name = None になります(Optional + 既定値なし)

✅ 5. デフォルト値を指定すれば任意項目になる

class User(BaseModel):
    name: str = "anonymous"  # 任意(省略時は "anonymous")

✅ 6. 入力チェック + 型変換 + 出力整形 が全て自動でできる

Pydantic モデルの強みは、以下の3つを1つのクラスで担ってくれることです:

機能 内容
バリデーション 入力値の正しさを検査(型・値・必須/任意など)
型変換 "123"123 のように、Pythonの型に変換
シリアライズ .model_dump().model_dump_json() で出力整形

✅ まとめ:モデル定義のポイント

概念 意味
BaseModel 全てのモデルの基礎。これを継承することで機能を得る
型ヒント 各フィールドに型(int, strなど)を書く
必須 / 任意 型だけ:必須、Optionalやデフォルトあり:任意
model_validate() dictからモデルを生成しつつ検証する

🛡 バリデーションの機能

フィールドバリデーション(@field_validator)

Pydantic v2 では、各フィールドに対して 個別にバリデーション(検証)ロジックを定義する方法として @field_validator が導入されています。

  • 型ヒントだけでは表現しきれない「値の条件」を追加できる
  • 例:数値が正である、文字列の長さが一定以上である、など
from pydantic import BaseModel, field_validator

class Product(BaseModel):
    name: str
    price: float

    @field_validator("price")
    def must_be_positive(cls, v):
        if v <= 0:
            raise ValueError("価格は正の値でなければなりません")
        return v # この return v を省略すると値が None になるので、return v は必要
処理の流れ
  1. モデルが作られるとき(model_validate(...)
  2. price フィールドに値が入る
  3. @field_validator("price") が呼ばれ、値をチェック
  4. 問題なければそのまま使われる/問題あればエラー
✅ 複数フィールドにも対応

以下の例では、count と rating の両方が正の値になることを検証しています

class Vote(BaseModel):
    count: int
    rating: float

    @field_validator("count", "rating")
    def must_be_positive(cls, v):
        if v <= 0:
            raise ValueError("正の値でなければなりません")
        return v
mode="before" / mode="after" で実行タイミングを調整できる
  • mode="before"(デフォルト): 型変換のにバリデーション
  • mode="after" : 型変換のにバリデーション

→ 以下の例では mode="after" を使って "18" のような文字列が int に変換されたにチェックされる

class User(BaseModel):
    age: int

    @field_validator("age", mode="after")
    def check_range(cls, v):
        if v < 0 or v > 120:
            raise ValueError("年齢が不正です")
        return v
info を使ってメッセージや設定にアクセス(上級)

@field_validator の第3引数には info: ValidationInfo を指定できます。
これを使うことで、バリデーション中のフィールドに関する追加情報(メタ情報) にアクセスできます。

from pydantic import Field, field_validator

class Account(BaseModel):
    # ... は必須を表す
    username: str = Field(..., min_length=3)

    @field_validator("username", mode="after")
    def validate_username(cls, v, info: ValidationInfo):
        if "@" in v:
            raise ValueError("ユーザー名に @ は使えません")
        return v
  • info からフィールド名・Field情報などを参照可能
属性 内容
info.field_name 現在検証中のフィールド名(文字列)
info.data 入力データの辞書(型変換前、未検証)
info.field_info Field(...) で指定した制約(min_length など)
  • 例:フィールド名と min_length を表示する
from pydantic import BaseModel, Field, field_validator, ValidationInfo

class User(BaseModel):
    username: str = Field(min_length=5, description="ユーザー名")

    @field_validator("username")
    def check_username(cls, v, info: ValidationInfo):
        print("フィールド名:", info.field_name)
        print("min_length:", info.field_info.min_length)
        return v

✅ エラーの例
p = Product.model_validate({"name": "Banana", "price": -10.0})

ValidationError: 価格は正の数でなければなりません


✅ 複数条件がある場合は return を忘れずに
@field_validator("email")
def check_email(cls, v):
    if "@" not in v:
        raise ValueError("メールアドレスが不正です")
    return v  # ← return を忘れると None が返ってエラーに

✅ まとめ:@field_validator のポイント
項目 内容
目的 フィールドごとに詳細な値チェックを行う
使い方 @field_validator("フィールド名") を定義
複数フィールド対応 @field_validator("a", "b", ...)
モード mode="before"(デフォルト) or mode="after"
使う場面 0以上である必要がある数値、禁止文字を含む文字列など

モデル全体のバリデーション(@model_validator)

Pydantic v2 では、モデル単位でのバリデーションを行うために @model_validator を使います。
これは「複数フィールドを組み合わせて整合性をチェックする」ときに使います。


✅ なぜ必要?

@field_validator は1つのフィールドしか扱えません。
「AがあるならBも必要」「合計が100を超えてはいけない」など、モデル全体の論理チェックには @model_validator が適しています。


✅ 基本構文(afterモード)
from pydantic import BaseModel, model_validator

class Product(BaseModel):
    name: str
    price: float
    discount: float

    @model_validator(mode="after")
    def check_discount(self):
        if self.discount >= self.price:
            raise ValueError("割引は価格未満でなければなりません")
        return self # この return self を省略すると値が None になるので、return self は必要
  • mode="after":型変換・fieldバリデーションのに呼ばれる
  • self はモデルのインスタンス
  • 戻り値は self(変更して返すことも可能)

✅ before モードとの違い
  • mode="before":フィールドがまだ dict の状態(型変換・fieldバリデーションの
  • mode="after"self で型変換後のインスタンスにアクセスできる(通常はこちらを使う)

✅ before モードの例(辞書に対する検証)
class RawInput(BaseModel):
    a: int
    b: int

    @model_validator(mode="before")
    @classmethod # NOTE: before の場合、まだモデルが作成されていないので classmethodになる
    def check_input(cls, values): # cls がクラス
        if "a" in values and "b" in values and values["a"] == values["b"]:
            raise ValueError("a と b は異なる値でなければなりません")
        return values
  • @classmethod がないとエラーになる
TypeError: `@model_validator` with mode='before' must be a classmethod.

✅ 複数条件の検証が可能
class Vote(BaseModel):
    count: int
    rating: float

    @model_validator(mode="after")
    def validate_vote(self):
        if self.count > 100 and self.rating < 2.0:
            raise ValueError("票数が多いわりに評価が低すぎます")
        return self

✅ 入力の補完や変更もできる
class Item(BaseModel):
    name: str
    tags: list[str] = []

    @model_validator(mode="after")
    def ensure_default_tag(self):
        if not self.tags:
            self.tags = ["default"]
        return self

✅ まとめ:@model_validator のポイント

機能 説明
モデル全体の検証 複数フィールドの整合性チェックができる
mode="after" 通常はこちら。self で型変換後の値にアクセス
mode="before" dict の状態での処理が必要なときに使う
検証だけでなく補完も可 self を書き換えて返せば、入力補正もできる

💡 @model_validator を使えば、「あるフィールドの値によって他のフィールドを制限する」といった複雑なロジックを1つの場所にきれいにまとめられます。

ikeponikepon

🔧 その他の便利機能

Pydantic v2 には、基本的な型検証やバリデーションに加えて、実務で非常に役立つ多くの補助機能があります。

✅ 1. Field() で制約を追加する

  • min_length, max_length:文字列の長さ制限
  • ge, le, gt, lt:数値の大小制約(greater/less than)
  • pattern:正規表現マッチ
from pydantic import BaseModel, Field

class User(BaseModel):
    name: str = Field(..., min_length=3, max_length=20, pattern=r"^[a-zA-Z0-9_]+$")
    age: int = Field(ge=0, le=150)

✅ 2. Annotated で型に意味を付加する(推奨スタイル)

→ 型と制約を 再利用可能な型エイリアスとして定義できる

from typing import Annotated
from pydantic import BaseModel, Field

Name = Annotated[str, Field(min_length=3, max_length=50)]
Age = Annotated[int, Field(ge=0, le=120)]

class User(BaseModel):
    name: Name
    age: Age

✅ 3. @computed_field で動的な値を定義する

.model_dump().model_dump_json() でも area が出力される
@property だけだと、.model_dump().model_dump_json() のときに出力されない

    from pydantic import BaseModel, computed_field

    class Rectangle(BaseModel):
        width: float
        height: float

        @computed_field
        @property
        def area(self) -> float:
            return self.width * self.height

✅ 4. model_dump()model_dump_json()

  • model_dump():辞書として出力
  • model_dump_json():JSON文字列として出力(utf-8対応)
class User(BaseModel):
    name: str
    age: int

u = User(name="Alice", age=30)

u.model_dump() # -> {'name': 'Alice', 'age': 30}
u_json = u.model_dump_json() # -> '{"name":"Alice","age":30}'

✅ 5. model_json_schema() で JSON Schema を自動生成

→ OpenAPI / FastAPI などでそのまま使える JSON Schema を生成できる

print(User.model_json_schema())
# -> {'properties': {'name': {'title': 'Name', 'type': 'string'}, 'age': {'title': 'Age', 'type': 'integer'}}, 'required': ['name', 'age'], 'title': 'User', 'type': 'object'}

✅ 6. TypeAdapter で個別の型だけ検証・変換する

BaseModel を使わなくても、型単体でバリデーションできる

from pydantic import TypeAdapter

adapter = TypeAdapter(list[int])
values = adapter.validate_python(["1", 2, 3])  # → [1, 2, 3]

✅ 7. model_copy() で部分更新

→ 元のインスタンスはそのまま、新しいインスタンスを生成

class User(BaseModel):
    name: str
    age: int

original = User(name="Alice", age=30)
modified = original.model_copy(update={"age": 31})

✅ 8. エラーのカスタマイズ

→ エラーには description も含まれるようになる(UI/ログ向けに便利)

from pydantic import BaseModel, Field

class Account(BaseModel):
    username: str = Field(..., min_length=3, description="3文字以上にしてください")

Account.model_validate({"username": "ab"})

✅ 9. Discriminator 付きの Union 型(構造分岐)

✅ 目的:複数の構造(型)を1つのモデルとして扱いたい

例えば、API で以下のような JSON を受け取るとします:

{"type": "cat", "lives": 9}
{"type": "dog", "bark_volume": 3.5}

このとき:

  • type の値に応じて
  • 適切なモデル(CatDog)を選んで処理したい
from pydantic import BaseModel, RootModel, TypeAdapter
from typing import Literal, Union

class Cat(BaseModel):
    type: Literal["cat"]
    lives: int

class Dog(BaseModel):
    type: Literal["dog"]
    bark_volume: float

Pet = RootModel[Union[Cat, Dog]]

adapter = TypeAdapter(Pet)

pet = adapter.validate_python({"type": "dog", "bark_volume": 3.5})
pet
# -> RootModel[Union[Cat, Dog]](root=Dog(type='dog', bark_volume=3.5))

print(pet.root)
# → Dog(type='dog', bark_volume=3.5)

✅ まとめ:実務でよく使う「その他の便利機能」

機能 説明
Field() の制約 長さ・範囲・パターンなどを型に追加
Annotated 型と制約の再利用・意味づけ
@computed_field 自動計算された出力フィールドを追加
model_dump() / JSON モデルを辞書や JSON に変換
model_json_schema() OpenAPI 用にスキーマ出力
TypeAdapter 型だけでバリデーション実行(モデルなしでOK)
model_copy(update=...) 元データを壊さず部分更新
エラーの description エラー時のヒント文や UI 表示用に使える
Discriminated Union type に応じてモデルを自動切り替え

🎓 こうした「ちょっと便利な機能」を知っておくと、Pydantic を使った実装の品質と効率が一気にアップします!

さらに「パフォーマンスチューニング」や「スキーマのカスタム拡張」など、上級者向けTipsも知りたければ続けてどうぞ!

ikeponikepon

3. Pydantic によるデータ操作

Pydantic では、モデルの作成・検証・変換・更新など、データを安全に扱うための操作方法が体系化されています。


✅ 1. データの検証とモデル作成

最も基本的な操作は、生データ(dictやJSON)を型安全なモデルに変換することです。

model_validate()

from pydantic import BaseModel

class User(BaseModel):
    name: str
    age: int

# dictからバリデーションしてインスタンス作成
u = User.model_validate({"name": "Alice", "age": "30"})
  • 型変換も自動
  • age は "30"(str)でも int に変換される

✅ 2. モデルから辞書・JSONへの変換

モデルインスタンスを、外部向けにシリアライズ(変換) する操作です。

model_dump()

data = u.model_dump()
# → {'name': 'Alice', 'age': 30}
  • dict型で取得
  • nested model(入れ子モデル)も辞書展開される

model_dump_json()

json_data = u.model_dump_json()
# → '{"name":"Alice","age":30}'
  • JSON文字列として出力
  • FastAPIなどでAPIレスポンスに使いやすい

✅ 3. モデルのコピーと部分更新

モデルインスタンスをコピーしつつ一部だけ更新したい場合、model_copy()を使います。

model_copy()

updated_user = u.model_copy(update={"age": 31})
  • update={"フィールド名": 値} で部分更新
  • 元のインスタンス(u)は変更されない
  • 新しいインスタンス(updated_user)が返る

✅ 4. 直接インスタンスを編集する

通常のPythonオブジェクトなので、属性代入で直接更新も可能です。

u.age = 32
  • model_copy() は「イミュータブル設計向き」
  • 直接代入は「ミュータブル設計向き」

✅ 5. バリデーションなしで作成(高速)

model_construct() を使うと、型検査・検証なしで超高速にインスタンスを作成できます。

u_fast = User.model_construct(name="Alice", age=30)

注意点:

  • 型変換・検証を一切しない
  • 既に信頼できるデータ専用
  • 普通の開発では model_validate() を優先するべき

✅ 6. 部分的なデータ更新・マージ(手動)

小さい更新だけをしたいとき、model_dump()update()パターンも使えます。

user_data = u.model_dump() # dict に変換
user_data.update({"age": 40}) # dict の age を更新
u_new = User.model_validate(user_data) # もう一度モデル化

→ dictで操作してから新インスタンス作成。


✅ 7. JSON Schema 生成(API設計用)

Pydanticモデルから、自動的にOpenAPI互換のJSON Schemaを出力できます。

model_json_schema()

schema = User.model_json_schema()

→ API設計やドキュメント作成で非常に便利!


✅ まとめ:Pydantic のデータ操作フロー

操作 関数 説明
データからモデルを作る model_validate(obj) 型変換+バリデーションあり
モデルを辞書に変換 model_dump() 外部シリアライズ用
モデルをJSON文字列に model_dump_json() APIレスポンスなどに最適
モデルをコピー+更新 model_copy(update=...) 一部変更した新しいインスタンスを作る
モデルを生で作る model_construct(**kwargs) バリデーションなし(高速・信頼データ専用)
モデルスキーマ生成 model_json_schema() OpenAPI設計向け JSON Schema 出力

🎓 まとめると、Pydantic は「外部データ → 型安全なモデル → 出力」までの流れを、検証・変換・シリアライズすべて含めてスマートに管理できるライブラリです。

ikeponikepon

4. Pydantic の応用

ここでは、Pydantic をさらに実務レベルで使いこなすための応用テクニックを紹介します。


✅ 1. カスタム型を作る

Pydanticでは、独自の型を定義して、特定の形式や制約を表現できます。

from typing import NewType
from pydantic import BaseModel

UserId = NewType("UserId", int)

class User(BaseModel):
    id: UserId
    name: str
  • NewType を使うと、型に意味づけできる
  • 内部では普通の int として扱われるが、静的型チェック(mypyなど)では別扱いできる

✅ 2. TypeAdapter で個別型のバリデーション

モデルを作らず、単体の型やリストだけを検証したいときに便利です。

from pydantic import TypeAdapter

adapter = TypeAdapter(list[int])

numbers = adapter.validate_python(["1", 2, 3])
# → [1, 2, 3]
  • 型だけ指定してバリデーションできる
  • BaseModel をわざわざ作らなくてよい

✅ 3. Strict モード(型を厳格にチェック)

通常、Pydanticは柔軟に型変換しますが、厳格にしたい場合は strict=True を使います。

  • strict=True を付けると型変換なし(例:str→floatの自動変換を拒否)
  • セキュリティ・信頼性を求めるAPIでは必須
from pydantic import BaseModel, Field

class Payment(BaseModel):
    amount: float = Field(strict=True)

Payment.model_validate({"amount": 100})  # OK
Payment.model_validate({"amount": "100"})  # ❌ ValidationError

✅ 4. イミュータブルモデル(frozen=True)

インスタンスを作ったあとに変更できないようにしたい場合は、frozen=True を使います。

class User(BaseModel):
    name: str
    age: int

    model_config = {
        "frozen": True # これを追加
    }

user = User(name="Alice", age=30)
user.age = 31  # ❌ TypeError
  • モデルが「読み取り専用」として扱われる
  • 状態の一貫性を守りたい場合に有効

✅ 5. @computed_field で計算フィールドを追加

実際のデータにはないけど、計算で得られる値を出力に含めたいときに使います。

from pydantic import BaseModel, computed_field

class Rectangle(BaseModel):
    width: float
    height: float

    @computed_field
    @property
    def area(self) -> float:
        return self.width * self.height

rect = Rectangle(width=2, height=3)
print(rect.model_dump())
# → {'width': 2.0, 'height': 3.0, 'area': 6.0}
  • .model_dump() にも area が含まれる!

✅ 6. Discriminator付きUnion型(構造分岐)

複数の型をひとつにまとめて、「どの型か?」を自動判別できる仕組みです。

from pydantic import BaseModel, RootModel
from typing import Union, Literal

class Cat(BaseModel):
    type: Literal["cat"]
    lives: int

class Dog(BaseModel):
    type: Literal["dog"]
    bark_volume: float

Pet = RootModel[Union[Cat, Dog]]

pet = Pet.model_validate({"type": "dog", "bark_volume": 3.5})
  • type フィールドを見て、自動的に Cat または Dog を選んでくれる
  • FastAPI で複雑なリクエストを受けるときに超便利

✅ 7. フィールド依存バリデーション(@model_validator)

モデル全体の整合性チェックもできる。

from pydantic import BaseModel, model_validator

class Vote(BaseModel):
    count: int
    rating: float

    @model_validator(mode="after")
    def check_vote(self):
        if self.count > 100 and self.rating < 2.0:
            raise ValueError("評価が低いのに票が多すぎます")
        return self
  • 複数フィールドを組み合わせたチェックが可能
  • mode="after" を指定すると、型変換後に実行される

✅ まとめ:応用テクニック一覧

機能 説明
カスタム型(NewType) 型に意味を付加
TypeAdapter 型単体をバリデーション
Strictモード 厳格な型チェック
frozenモデル インスタンスを変更不可にする
@computed_field 計算プロパティをシリアライズ対象にする
Discriminator付きUnion型 構造分岐して適切な型を選ぶ
モデル全体バリデーション フィールドの整合性をチェックできる

🎓 Pydantic は「型に意味を持たせる」「状態を安全に管理する」「複雑なデータを型安全に扱う」ためのツールセットをすべて揃えています。
応用機能を知ると、より堅牢な Python アプリケーション設計ができるようになります!

5. まとめ

✅ Pydanticとは?

Pydanticは、Pythonの型ヒントを活かして、

  • データ検証(Validation)
  • 型変換(Type Coercion)
  • シリアライズ(Serialization)

を自動で行ってくれるライブラリです。
Rust製の高速エンジン(pydantic-core)により、超高速で動作します。


✅ Pydanticの基本機能

機能 説明
モデル定義 BaseModel を継承してフィールドを型ヒント付きで定義
データ検証 model_validate()で生データから型安全なインスタンスを生成
データ変換 model_dump(), model_dump_json()で外部形式に変換
バリデーション @field_validator, @model_validator で個別・全体検証
自動スキーマ生成 model_json_schema()でOpenAPI互換のJSON Schemaを作成

✅ 実務で役立つ応用機能

応用機能 説明
カスタム型(NewType) 型に意味を持たせて、ドメインを明確にする
TypeAdapter 単独の型だけを高速に検証・変換できる
Strictモード 厳格な型チェック(自動変換を拒否)
Frozenモード モデルインスタンスをイミュータブル(変更禁止)にする
計算フィールド @computed_fieldでシリアライズ対象のプロパティを追加
Discriminator付きUnion type フィールドに応じて自動的に型分岐する

✅ よく使うデータ操作フロー

  1. 検証付きインスタンス生成

    model = Model.model_validate(data)
    
  2. モデルを辞書に変換

    data = model.model_dump()
    
  3. モデルをJSONに変換

    json_data = model.model_dump_json()
    
  4. 一部を更新して新しいモデルを作成

    updated_model = model.model_copy(update={"field": new_value})
    
  5. 高速に(検証なしで)インスタンス作成

    fast_model = Model.model_construct(**kwargs)
    

✅ Pydanticを使うメリット

  • Pythonコードが型安全になり、バグが減る
  • データ検証を自動化でき、コード量が減る
  • API設計(FastAPIなど)で型とドキュメントが一致する
  • シリアライズ、デシリアライズが超高速にできる
  • 入れ子構造・複雑なデータも型安全に制御できる

✅ 注意するべきポイント

注意点 理由・背景
model_construct()は信頼データ専用 バリデーションをしないため、不正データもそのまま入る
model_validate()は必ずdictなどを渡す 引数なしではエラーになる
frozenはインスタンスごとでは切り替えられない モデルクラス単位でしか設定できない
model_copy()は元インスタンスを変更しない 不変設計を守るため

✅ 最後に

🎓 Pydanticを使えば、Pythonで「型安全・データ検証・API設計」が一気通貫で実現できます。
実務でも学術用途でも、強力なデータ層の構築に欠かせないツールです。
型を味方につけることで、ミスを防ぎ、開発速度を加速させましょう!

6. その他

model_construct でバリデーションなしのインスタンスを作る

  • Account.model_construct(username="aaa") でインスタンスを作れる
    • ただし、これはバリデーションを行わないので型が違っていても入力できる
    • また、インスタンスを作って、そこにバリデーションを実行する方法は今のところないので、model_dump して model_validate する
from pydantic import BaseModel, Field

class User(BaseModel):
    name: str
    age: int = Field(ge=0)

# 1. model_construct で不正なインスタンスを作る
user = User.model_construct(name="Alice", age=-10)

print(user)  
# → name='Alice' age=-10  (エラーにならない)

# 2. model_dump して dict に変換
data = user.model_dump()

# 3. model_validate でバリデーション実施
validated_user = User.model_validate(data)