結論

Pydanticとは、Pythonの型ヒントどおりにデータを検証し、型をそろえるライブラリです。外から届くJSONや設定値の入口に置きます。

この記事でわかること

  • 用途7つの早見表とdataclass・attrsとの違い
  • 10万件の検証速度を11通りで比べた実測
  • v1→v2の移行表19行とエラー原文・直し方

2026年10月24日更新|Pydantic 2.14.1・Python 3.14.7で実行確認

Pydantic はモデルのクラスに書いた型ヒントを「データの決まり」として使い、入力が決まりに合えば型をそろえたオブジェクトを、合わなければ場所と理由の入ったエラーを返します。

flowchart TD
    A[外から届くデータ<br>JSON・フォーム・環境変数] --> B[Pydantic のモデル<br>型ヒントが決まり]
    B -- 合う --> C[型のそろった<br>オブジェクト]
    B -- 合わない --> D[ValidationError<br>どこが・なぜ]
    C --> E[model_dump で<br>dict・JSON に戻す]

Pydantic がよく使われる用途です。多い順に並べました。

用途 使うもの 例
API の入力を検証 BaseModel+FastAPI リクエストの JSON
JSON を型つきで読む model_validate_json 外部 API の応答
設定・環境変数を読む pydantic-settings DB の URL・API キー
dict・JSON に書き出す model_dump / model_dump_json レスポンス・保存
LLM の出力を検証 JSON Schema+検証 構造化出力
list・dict をそのまま検証 TypeAdapter CSV の行の一覧
関数の引数を検証 validate_call ライブラリの公開関数

確認環境:Ubuntu 24.04.4 LTS(2 vCPU)・Python 3.14.7 の venv に、PyPI から Pydantic 2.14.1(pydantic-core 2.50.1)・pydantic-settings 2.16.1・FastAPI 0.162.0・attrs 26.1.0・email-validator 2.3.0・bump-pydantic 0.8.0 を入れ、記事のコードをすべて実行して出力をそのまま載せています。uv 0.8.17 の uv add でも同じ版が入ることを確かめました。速さは10万件のデータを9回ずつ交互に測った中央値です。版の履歴は PyPI と GitHub のリリースノート、仕様と用語は Pydantic 公式ドキュメント(pydantic.dev)、FastAPI の動きは FastAPI 公式ドキュメントで確かめました。図は mermaid で描画を確かめています(確認日 2026年10月24日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Pydanticとは|何に使う・dataclassとの違いと使い方を実測で解説」

Pydanticとは(読み方・何をするライブラリか)

この節では、Pydantic が何をするライブラリで、どこが便利なのかが分かります。

Pydantic(パイダンティックと読まれることが多い)は、Python の型ヒントを使ってデータを検証(バリデーション)し、型を変換するライブラリです。 標準ライブラリではないので pip install pydantic で入れます。公式ドキュメントは、検証の決まり(スキーマ)を「主に Python の型ヒントで定義する」と説明しています。

Python の型ヒントは、ふつうは実行時に何もしません。id: int と書いても、文字列を入れればそのまま入ります。Pydantic は、この型ヒントを実行時に読んで、入ってきた値を確かめます。

from datetime import date
from pydantic import BaseModel, ValidationError

class Member(BaseModel):
    id: int
    name: str
    joined: date

# 外から届いたデータ(JSON・フォーム・CSV は文字列で届くことが多い)
data = {"id": "42", "name": "佐藤", "joined": "2026-04-01"}
m = Member.model_validate(data)
print(repr(m))
print(type(m.id).__name__, type(m.joined).__name__)

try:
    Member.model_validate({"id": "abc", "name": "佐藤"})
except ValidationError as e:
    print(e)
Member(id=42, name='佐藤', joined=datetime.date(2026, 4, 1))
int date
2 validation errors for Member
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.14/v/int_parsing
joined
  Field required [type=missing, input_value={'id': 'abc', 'name': '佐藤'}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.14/v/missing

文字列の "42" は int の 42 に、"2026-04-01" は date になりました。間違った入力では、2つの項目の問題がまとめて返っています。

1型ヒントで決まりを書く

クラスに型を書くだけ。エディタの補完や mypy などの型チェッカーともそのまま合う。

2変換して検証する

"42" を 42 に、日付の文字列を date にそろえる。合わない値は場所と理由つきのエラーにする。

3dict・JSON に戻せる

model_dump・model_dump_json で書き出し、JSON Schema も作れる。API と相性がよい。

検証の中心部分は Rust で書かれた pydantic-core が受け持っています。公式ドキュメントの「Why use Pydantic?」のページには、執筆時点で GitHub の 466,400 リポジトリと PyPI の 8,119 パッケージが Pydantic に依存していると書かれ、依存している例として FastAPI・LangChain・Hugging Face の transformers などが挙がっています。

型ヒントそのものの書き方は、関連記事「Pythonの型ヒントの書き方|3.12以降の新しい書き方まで」で扱っています。

Pydanticは何に使う?用途の早見表

この節では、Pydantic が置かれる場所と、置かなくてよい場所が分かります。

Pydantic は「外から来るデータの入口」に置くのが基本です。 外からのデータは、文字列で届く・項目が足りない・余計な項目がある、ということが起きます。入口で1回確かめておけば、内側のコードは型を信じて書けます。

用途 Pydantic がしてくれること 本文の節
Web API の入力 JSON を読み、型をそろえ、違えば 422 を返す(FastAPI) FastAPIでの使われ方
外部 API の応答・ファイル JSON 文字列を1回で解析と検証 シリアライズ
設定・環境変数・.env 文字列を int・bool・list に変換し、足りなければ起動時にエラー pydantic-settings
API の応答・保存 モデルを dict・JSON に戻す。項目の出し入れも指定できる シリアライズ
LLM の構造化出力 モデルから JSON Schema を作り、返ってきた JSON を検証 LLMの構造化出力
CSV の行・一覧の検証 クラスを作らずに list[dict] などを検証 TypeAdapter
関数の引数 呼ばれたときに引数を検証する TypeAdapter・validate_call

Pydantic を置くところ

API のリクエスト・レスポンス

外部 API・ファイルから読んだ JSON

環境変数・設定ファイル

LLM が返した JSON

標準の dataclass でよいところ

関数の間で値を持ち運ぶだけの入れ物

すでに検証済みの値しか入らないクラス

数百万回作る、速さが大事な内側の処理

依存ライブラリを増やしたくない小さなスクリプト

Pydanticとdataclass・TypedDict・attrsの違い

この節では、似た書き方をする4つの道具が、間違った値をどう扱うかの違いが分かります。

いちばん大きな違いは「実行時に値を確かめて変換するかどうか」です。 標準の dataclass と TypedDict は型ヒントを書いても値を確かめません。Pydantic は確かめて変換し、attrs は書いた分だけ確かめます。

from dataclasses import dataclass
from typing import TypedDict
import attrs
from pydantic import BaseModel, TypeAdapter, ValidationError
from pydantic import dataclasses as pdc

class MemberTD(TypedDict):
    id: int
    name: str

@pdc.dataclass                     # Pydantic の検証つき dataclass
class MemberPDC:
    id: int
    name: str

@dataclass                         # 標準の dataclass
class MemberDC:
    id: int
    name: str

@attrs.define                      # attrs(変換と検証を自分で書く)
class MemberAT:
    id: int = attrs.field(converter=int, validator=attrs.validators.instance_of(int))
    name: str = attrs.field(validator=attrs.validators.instance_of(str))

class MemberBM(BaseModel):
    id: int
    name: str

src = {"id": "7", "name": "b"}
print("BaseModel    :", repr(MemberBM.model_validate(src)))
print("pydantic dc  :", MemberPDC(**src))
print("TypedDict    :", TypeAdapter(MemberTD).validate_python(src))
print("dataclass    :", MemberDC(**src))
print("attrs        :", MemberAT(**src))
print("型ヒントだけ :", MemberTD(**src))
BaseModel    : MemberBM(id=7, name='b')
pydantic dc  : MemberPDC(id=7, name='b')
TypedDict    : {'id': 7, 'name': 'b'}
dataclass    : MemberDC(id='7', name='b')
attrs        : MemberAT(id=7, name='b')
型ヒントだけ : {'id': '7', 'name': 'b'}

同じクラスに {"id": "x", "name": 1}(どちらも型が違う)を渡した結果も並べます。

BaseModel    ValidationError 2件
pydantic dc  ValidationError 2件
TypedDict    ValidationError 2件
dataclass    通った: MemberDC(id='x', name=1)
attrs        ValueError: invalid literal for int() with base 10: 'x'
書き方 "7" を渡すと 間違った値を渡すと
Pydantic BaseModel 7(int に変換) ValidationError(2件まとめて)
pydantic.dataclasses 7(int に変換) ValidationError(2件まとめて)
TypedDict+TypeAdapter 7(int に変換) ValidationError(2件まとめて)
標準の dataclass '7'(str のまま) そのまま通る
attrs(converter+validator) 7(書いた converter で変換) ValueError(最初の1件で止まる)
TypedDict(型ヒントだけ) '7'(ただの dict) そのまま通る

dataclass と Pydantic の BaseModel

標準の dataclass型ヒントは注釈だけ。"x" も 1 も入る。検証は __post_init__ に自分で書く。依存なしで軽い
↔
Pydantic BaseModel型ヒントどおりに変換・検証し、全項目の問題をまとめて返す。dict・JSON への書き出しと JSON Schema が付く

外から来るデータは BaseModel、検証済みの値を運ぶだけなら dataclass

選択肢 検証・変換 得意なこと 向いている場面
Pydantic BaseModel あり JSON の入出力・エラー情報・JSON Schema API の入力・設定・外部データ
pydantic.dataclasses あり dataclass の書き方のまま検証 今の dataclass に検証を足す
TypeAdapter あり list・dict・TypedDict をそのまま検証 クラスを作らずに検証
標準の dataclass なし 依存なし・軽い 内側のデータの受け渡し
TypedDict なし(型チェッカーだけ) dict のまま型を付ける JSON を dict で扱うコード
attrs 書いた分だけ slots・柔軟な設定・converter ライブラリの内側のクラス

pydantic.dataclasses には model_dump などのメソッドが付きません(公式ドキュメントの Dataclasses のページ)。手元でも hasattr(MemberPDC, "model_dump") は False で、書き出すときは TypeAdapter(MemberPDC).dump_python(...) を使いました。標準の dataclass の書き方と既定値の注意は、関連記事「Python dataclassの使い方|既定値の罠からslotsまで実行結果で解説」で扱っています。

Pydanticは遅い?dataclass・attrsとの速さの実測

この節では、検証のぶん、どれくらい時間がかかるのかが数字で分かります。

10万件の dict(id は文字列→int に変換、name と price に制約、tags はリスト)を、11通りの方法で作りました。 2 vCPU の共有の環境なので、9回ずつ交互に測って中央値を載せます。

N = 100_000
data = [{"id": str(i), "name": f"item{i}", "price": i % 1000, "tags": ["a", "b"]} for i in range(N)]
raw = json.dumps(data)

class Row(BaseModel):
    id: int
    name: str = Field(min_length=1)
    price: int = Field(ge=0)
    tags: list[str] = []

ta_rows = TypeAdapter(list[Row])
[Row.model_validate(d) for d in data]   # 1件ずつ
ta_rows.validate_python(data)           # まとめて
ta_rows.validate_json(raw)              # JSON 文字列から直接
方法(10万件) 検証 中央値
標準の dataclass を作る なし 48.0 ms
attrs を作る なし 42.9 ms
TypeAdapter(list[TypedDict]) あり 78.1 ms
dataclass+__post_init__ に手書きの検証 あり 113.4 ms
TypeAdapter(list[Row]) でまとめて あり 191.5 ms
attrs(validators+converter) あり 214.8 ms
TypeAdapter.validate_json(JSON 解析込み) あり 280.7 ms
Row.model_validate を1件ずつ あり 324.5 ms
pydantic.dataclasses を1件ずつ あり 348.3 ms
model_construct(検証なしで作る) なし 423.3 ms
json.loads+model_validate(解析込み) あり 567.1 ms
1.7倍まとめて検証の速さTypeAdapter と1件ずつの比
2.0倍JSON を直接渡す速さvalidate_json と json.loads 経由の比
3.7倍v1 より速い同じモデルで v1 1079.8ms・v2 294.7ms

分かったことは4つです。

  • 検証のない dataclass・attrs が速いのは当然で、Pydantic の1件ずつは約7倍の時間がかかった。そのかわり変換・制約・エラー情報がそろう
  • リストは TypeAdapter(list[...]) でまとめて渡すと、1件ずつより約1.7倍速い
  • JSON 文字列は json.loads を挟まず validate_json に渡すと約2倍速い
  • model_construct は検証を省いても、今回は model_validate より遅かった。速さのためより「信頼できるデータを検証なしで作る」ための道具として使う

v1 との比較は、同じ項目と制約のモデルを pydantic.v1(2.14 に同梱の v1 の API)で作り、parse_obj と model_validate を7回ずつ交互に測った中央値です。

Pydanticのインストールと最新版(2.14)

この節では、入れ方と、今の最新版・対応する Python の版が分かります。

2026年10月24日時点の最新は 2.14.1(2026年10月11日公開)です。 2.14.0(2026年10月8日)で Python 3.9 の対応が外れて 3.15 の対応が入り、PyPI の requires_python は >=3.10 です。

版 公開日(PyPI) 主なこと
2.14.1 2026年10月11日 2.14 系の最新(2026年10月24日時点)
2.14.0 2026年10月8日 Python 3.9 の対応を終了・3.15 に対応
2.13.0 2026年4月13日 pydantic.v1 を v1.10.26 に更新(リリースノート)
2.12.0 2025年10月7日 ―
pip install pydantic                  # 本体
pip install pydantic-settings         # 環境変数・.env を読むとき
pip install "pydantic[email]"         # EmailStr を使うとき(email-validator も入る)
uv add pydantic                       # uv のプロジェクトに足すとき
python -c "import pydantic; print(pydantic.version.version_info())"
             pydantic version: 2.14.1
        pydantic-core version: 2.50.1
          pydantic-core build: profile=release pgo=false
               python version: 3.14.7 (main, Sep 24 2026, 17:58:18) [Clang 22.1.3 ]
                     platform: Linux-6.18.44-fc-v153-x86_64-with-glibc2.39
             related packages: fastapi-0.162.0 email-validator-2.3.0 pydantic-settings-2.16.1 typing_extensions-4.16.0
                       commit: unknown

uv add pydantic では、本体と一緒に4つのパッケージが入りました。

 + annotated-types==0.8.0
 + pydantic==2.14.1
 + pydantic-core==2.50.1
 + typing-extensions==4.16.0
 + typing-inspection==0.4.4

pydantic.version.version_info() の出力は、不具合を報告するときや、環境による違いを調べるときにそのまま使えます。以下のコードは、どれも各節のコードブロックを .py に貼れば動きます。エラーの末尾の https://errors.pydantic.dev/2.14/v/... は、エラーの種類ごとの公式の説明ページです。

BaseModelの使い方と型変換(laxとstrict)

この節では、モデルの作り方と、どの値が自動で変換されるのかが分かります。

BaseModel を継承したクラスに型を書くと、型ヒントどおりに入力を検証し、変換できる値は自動で型をそろえます(lax モード)。

from datetime import date
from pydantic import BaseModel, ValidationError

class Member(BaseModel):
    id: int
    name: str
    joined: date
    active: bool = True

m = Member(id="42", name="佐藤", joined="2026-04-01")
print(repr(m))
print(m.id + 1, type(m.joined).__name__)

try:
    Member(id="abc", name=123, joined="2026-13-01")
except ValidationError as e:
    print(e)
Member(id=42, name='佐藤', joined=datetime.date(2026, 4, 1), active=True)
43 date
3 validation errors for Member
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.14/v/int_parsing
name
  Input should be a valid string [type=string_type, input_value=123, input_type=int]
    For further information visit https://errors.pydantic.dev/2.14/v/string_type
joined
  Input should be a valid date or datetime, month value is outside expected range of 1-12 [type=date_from_datetime_parsing, input_value='2026-13-01', input_type=str]
    For further information visit https://errors.pydantic.dev/2.14/v/date_from_datetime_parsing
  • 文字列 "42" は int の 42 に、"2026-04-01" は date に変換された
  • 数値 123 は str に変換されずエラーになる。v2 は「数値→文字列」の自動変換をしない(v1 との違い)
  • エラーは最初の1件で止まらず、全項目まとめて返る

lax(変換する)と strict(変換しない)の違い

同じ値を、既定の lax と strict で検証した結果です。 TypeAdapter(型).validate_python(値, strict=...) で全行を実行しました。

入力 → 型 lax(既定) strict
"42" → int 42 エラー(int_type)
42.0 → int 42 エラー(int_type)
42.5 → int エラー(int_from_float) エラー(int_type)
123 → str エラー(string_type) エラー(string_type)
"true" / "yes" / 1 → bool True エラー(bool_type)
"3.14" → float 3.14 エラー(float_type)
"2026-04-01" → date date(2026, 4, 1) エラー(date_type)
[1, 2] → tuple (1, 2) エラー(tuple_type)
("1", "2") → list[int] [1, 2] エラー(list_type)
from typing import Annotated
from pydantic import BaseModel, ConfigDict, Field

class StrictMember(BaseModel):
    model_config = ConfigDict(strict=True)   # モデル全体を strict に
    id: int
    name: str

class PartStrict(BaseModel):
    id: Annotated[int, Field(strict=True)]   # この項目だけ strict
    qty: int

StrictMember(id="42", name="佐藤")   # → エラー
PartStrict(id=1, qty="3")             # → PartStrict(id=1, qty=3)
PartStrict(id="1", qty="3")           # → エラー
1 validation error for StrictMember
id
  Input should be a valid integer [type=int_type, input_value='42', input_type=str]
    For further information visit https://errors.pydantic.dev/2.14/v/int_type
PartStrict(id=1, qty=3)
1 validation error for PartStrict
id
  Input should be a valid integer [type=int_type, input_value='1', input_type=str]
    For further information visit https://errors.pydantic.dev/2.14/v/int_type

目安は、外から文字列で届く値(フォーム・クエリ文字列・CSV)は lax、内側の関数の間で型を厳密に守りたいなら strict です。 JSON には日付の型が無いため、JSON 文字列から読むときは strict でも "2026-04-01" を date として受け付けます(TypeAdapter(date).validate_json('"2026-04-01"', strict=True) は 2026-04-01 を返しました)。

Fieldで制約をつける(Annotatedで使い回す)

この節では、数値の範囲・文字数・正規表現などの制約の付け方が分かります。

from pydantic import BaseModel, Field

class Item(BaseModel):
    name: str = Field(min_length=1, max_length=20)
    price: int = Field(gt=0, le=100_000)
    code: str = Field(pattern=r"^[A-Z]{3}-\d{4}$")
    tags: list[str] = Field(default_factory=list)

print(repr(Item(name="ペン", price=120, code="STA-0001")))
Item(name="", price=0, code="sta-1")   # → エラー
Item(name='ペン', price=120, code='STA-0001', tags=[])
3 validation errors for Item
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.14/v/string_too_short
price
  Input should be greater than 0 [type=greater_than, input_value=0, input_type=int]
    For further information visit https://errors.pydantic.dev/2.14/v/greater_than
code
  String should match pattern '^[A-Z]{3}-\d{4}$' [type=string_pattern_mismatch, input_value='sta-1', input_type=str]
    For further information visit https://errors.pydantic.dev/2.14/v/string_pattern_mismatch
引数 意味 対象
gt / ge より大きい/以上 数値
lt / le より小さい/以下 数値
multiple_of 倍数(2.14 から正の値だけ) 数値
min_length / max_length 長さ 文字列・リスト
pattern 正規表現 文字列
default / default_factory 既定値/既定値を作る関数 すべて
alias 入出力のキー名 すべて
description / examples 説明(JSON Schema に出る) すべて
exclude=True model_dump に出さない すべて
strict=True この項目だけ strict すべて

Annotatedで制約つきの型を使い回す

同じ制約を何度も書くなら、Annotated で型に名前を付けます。

from typing import Annotated
from pydantic import BaseModel, Field

Price = Annotated[int, Field(gt=0, le=100_000, description="税込価格")]
Code = Annotated[str, Field(pattern=r"^[A-Z]{3}-\d{4}$")]

class Stock(BaseModel):
    code: Code
    unit_price: Price
    qty: Annotated[int, Field(ge=0)] = 0

Stock(code="STA-0001", unit_price=200_000)   # → エラー
1 validation error for Stock
unit_price
  Input should be less than or equal to 100000 [type=less_than_equal, input_value=200000, input_type=int]
    For further information visit https://errors.pydantic.dev/2.14/v/less_than_equal

Optionalと既定値(必須かどうかの決まり方)

この節では、項目が必須になる条件と、None を許す書き方の違いが分かります。

v2 では「既定値があるかどうか」だけで必須かが決まります。 str | None と書いても、既定値が無ければ必須です。

from pydantic import BaseModel

class Profile(BaseModel):
    a: str | None            # 必須(None は許可)
    b: str | None = None     # 省略可、既定 None
    c: str = "x"             # 省略可

Profile()          # → エラー
Profile(a=None)    # → Profile(a=None, b=None, c='x')
1 validation error for Profile
a
  Field required [type=missing, input_value={}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.14/v/missing
Profile(a=None, b=None, c='x')
書き方 省略すると None を渡すと
a: str エラー エラー
a: str | None エラー(v1 では省略可だった) 通る
a: str | None = None 通る(None) 通る
a: str = "x" 通る("x") エラー

list = [] のような変更できる既定値は、インスタンスごとに別のものになります。 手元でも、1つ目のインスタンスのリストに "pen" を足したとき、2つ目は [] のままでした(['pen'] [])。関数で既定値を作りたいときは default_factory を使います。

ネストしたモデルとエラーの読み方(errors())

この節では、モデルの中にモデルを入れたときの検証と、エラーの場所の読み方が分かります。

from pydantic import BaseModel, Field

class Address(BaseModel):
    zip: str = Field(pattern=r"^\d{3}-\d{4}$")
    city: str

class Customer(BaseModel):
    name: str
    address: Address
    history: list[Address] = []

c = Customer.model_validate({"name": "田中", "address": {"zip": "100-0001", "city": "千代田区"}})
print(repr(c.address))

Customer.model_validate({"name": "田中", "address": {"zip": "1000001"},
                         "history": [{"zip": "100-0001", "city": "A"}, {"city": "B"}]})
Address(zip='100-0001', city='千代田区')
3 validation errors for Customer
address.zip
  String should match pattern '^\d{3}-\d{4}$' [type=string_pattern_mismatch, input_value='1000001', input_type=str]
    For further information visit https://errors.pydantic.dev/2.14/v/string_pattern_mismatch
address.city
  Field required [type=missing, input_value={'zip': '1000001'}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.14/v/missing
history.1.zip
  Field required [type=missing, input_value={'city': 'B'}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.14/v/missing

内側のエラーも address.zip、history.1.zip(リストの2番目)のように場所つきで返ります。 画面のどの入力欄にエラーを出すかを、この場所から決められます。

errors() でエラーをプログラムで扱う

from datetime import date

class Line(BaseModel):
    unit_price: int
    qty: int

class Order(BaseModel):
    id: int
    ordered_on: date
    note: str | None = None
    lines: list[Line]

try:
    Order.model_validate({"id": "x", "ordered_on": "2026-10-01", "lines": [{"unit_price": 1}]})
except ValidationError as e:
    print(e.error_count())
    for err in e.errors(include_url=False):
        print(err["loc"], err["type"], err["msg"])
2
('id',) int_parsing Input should be a valid integer, unable to parse string as an integer
('lines', 0, 'qty') missing Field required

API のエラーレスポンスには e.errors() のリスト(loc・type・msg)を返す形がよく使われます。 FastAPI の 422 エラーもこの形です(後の節で実物を載せています)。

field_validatorとmodel_validator(独自の検証)

この節では、型と Field で書けない決まりを、自分で足す方法と、呼ばれる順番が分かります。

1つの項目で完結する決まりは field_validator、2つ以上の項目の関係は model_validator に書きます。 4種類を1つのモデルに置き、どの順で呼ばれるかを確かめました。

flowchart TD
    A[生の入力 dict・JSON] --> B[model_validator before]
    B --> C[field_validator before]
    C --> D[型の検証・変換と<br>Field の制約]
    D --> E[field_validator after]
    E --> F[model_validator after]
1 model_validator(before): {'qty': '3'}
2 field_validator(before): '3'
3 field_validator(after) : 3
4 model_validator(after) : M(qty=3)
1 model_validator(before): {'qty': '0'}
2 field_validator(before): '0'
ValidationError greater_than_equal

qty: int = Field(ge=1) に "0" を渡すと、Field の制約で止まり、after の2つは呼ばれませんでした。after の中では「型と制約はもう通っている」と考えて書けます。

field_validator:1項目ずつの検証・整形

from pydantic import BaseModel, field_validator

class Signup(BaseModel):
    email: str
    nickname: str

    @field_validator("email")             # 既定は mode="after"(型変換の後)
    @classmethod
    def normalize_email(cls, v: str) -> str:
        if "@" not in v:
            raise ValueError("メールアドレスに @ がありません")
        return v.strip().lower()          # 返した値が最終的な値になる

    @field_validator("nickname", mode="before")   # 型変換の前に呼ばれる
    @classmethod
    def strip_nickname(cls, v):
        return v.strip() if isinstance(v, str) else v

print(repr(Signup(email="  Hana@Example.COM ", nickname="  hana  ")))
Signup(email="hana.example.com", nickname="hana")   # → エラー
Signup(email='hana@example.com', nickname='hana')
1 validation error for Signup
email
  Value error, メールアドレスに @ がありません [type=value_error, input_value='hana.example.com', input_type=str]
    For further information visit https://errors.pydantic.dev/2.14/v/value_error

model_validator:複数項目の関係を確かめる

from datetime import date
from typing import Self
from pydantic import BaseModel, ValidationInfo, field_validator, model_validator

class Booking(BaseModel):
    start: date
    end: date

    @model_validator(mode="after")        # 全項目の検証が済んだ後。self を受け取る
    def check(self) -> Self:
        if self.end < self.start:
            raise ValueError("end は start 以降の日付にします")
        return self

class Booking2(BaseModel):                # 同じ決まりを end の項目のエラーにする
    start: date
    end: date

    @field_validator("end")
    @classmethod
    def after_start(cls, v: date, info: ValidationInfo) -> date:
        if "start" in info.data and v < info.data["start"]:
            raise ValueError("end は start 以降の日付にします")
        return v

class Legacy(BaseModel):
    full_name: str

    @model_validator(mode="before")       # 生の入力(dict など)を受け取って形を変える
    @classmethod
    def from_old_format(cls, data):
        if isinstance(data, dict) and "first" in data:
            return {"full_name": f"{data['last']} {data['first']}"}
        return data

Booking(start="2026-10-05", end="2026-10-01")    # → エラー
Booking2(start="2026-10-05", end="2026-10-01")   # → エラー
print(repr(Legacy.model_validate({"first": "花子", "last": "山田"})))
1 validation error for Booking
  Value error, end は start 以降の日付にします [type=value_error, input_value={'start': '2026-10-05', 'end': '2026-10-01'}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.14/v/value_error
1 validation error for Booking2
end
  Value error, end は start 以降の日付にします [type=value_error, input_value='2026-10-01', input_type=str]
    For further information visit https://errors.pydantic.dev/2.14/v/value_error
Legacy(full_name='山田 花子')

model_validator のエラーは項目名の行が空(loc が空)になります。 画面の特定の欄にエラーを出したいときは、Booking2 のように field_validator で info.data(先に検証が済んだ項目)を見ると、end のエラーとして返せます。

デコレーター mode 受け取るもの 使う場面
@field_validator "after"(既定) 変換済みの値 整形・追加の確認
@field_validator "before" 生の値 前処理(空白を除く・カンマ区切りをリストに)
@field_validator "wrap" 値と次の処理 例外を受けて既定値にする等
@model_validator "after" self 項目どうしの関係(開始≦終了)
@model_validator "before" 生の入力全体 前の形式からの変換

シリアライズ:model_dump・JSON・エイリアス

この節では、モデルを dict・JSON に戻す方法と、JSON のキー名を Python の名前と分ける方法が分かります。

from datetime import date
from pydantic import BaseModel, computed_field

class Line(BaseModel):
    unit_price: int
    qty: int

    @computed_field
    @property
    def subtotal(self) -> int:
        return self.unit_price * self.qty

class Order(BaseModel):
    id: int
    ordered_on: date
    note: str | None = None
    lines: list[Line]

l = Line(unit_price=120, qty=3)
print(l.subtotal, l.model_dump())
o = Order(id=1, ordered_on="2026-10-01", lines=[{"unit_price": 100, "qty": 2}])
print(o.model_dump())
print(o.model_dump(mode="json"))
print(o.model_dump(exclude_none=True, include={"id", "note"}))
print(o.model_dump_json())
print(o.model_dump_json(indent=2, exclude={"lines"}))
print(repr(Order.model_validate_json('{"id": 2, "ordered_on": "2026-10-02", "lines": []}')))
print(repr(o.model_copy(update={"note": "至急"})))
360 {'unit_price': 120, 'qty': 3, 'subtotal': 360}
{'id': 1, 'ordered_on': datetime.date(2026, 10, 1), 'note': None, 'lines': [{'unit_price': 100, 'qty': 2, 'subtotal': 200}]}
{'id': 1, 'ordered_on': '2026-10-01', 'note': None, 'lines': [{'unit_price': 100, 'qty': 2, 'subtotal': 200}]}
{'id': 1}
{"id":1,"ordered_on":"2026-10-01","note":null,"lines":[{"unit_price":100,"qty":2,"subtotal":200}]}
{
  "id": 1,
  "ordered_on": "2026-10-01",
  "note": null
}
Order(id=2, ordered_on=datetime.date(2026, 10, 2), note=None, lines=[])
Order(id=1, ordered_on=datetime.date(2026, 10, 1), note='至急', lines=[Line(unit_price=100, qty=2, subtotal=200)])

model_dump() は date を Python のオブジェクトのまま返し、mode="json" を付けると文字列になります。 そのまま json.dumps() に渡すなら mode="json"、JSON 文字列が欲しいなら model_dump_json() が近道です。computed_field で定義した subtotal は出力にも入ります。

メソッド 入力 → 出力 主なオプション
model_validate(obj) dict・オブジェクト → モデル strict・from_attributes
model_validate_json(s) JSON 文字列 → モデル strict
model_dump() モデル → dict mode・include・exclude・by_alias・exclude_none・exclude_unset
model_dump_json() モデル → JSON 文字列 indent ほか上と同じ
model_copy(update=...) モデル → 新しいモデル deep=True(update の値は検証されない)
model_json_schema() クラス → JSON Schema(dict) by_alias・mode
model_construct(**kw) 検証せずに作る 信頼できるデータだけ

出力の形を項目ごとに変えるときは field_serializer を使います。datetime(2026, 10, 24, 9, 30) を "%Y/%m/%d %H:%M" で書き出すと、{'at': '2026/10/24 09:30'} になりました。JSON の書き方の決まりは、関連記事「JSONとは|JSONファイルの書き方・開き方とエラー例を実物で解説」で扱っています。

エイリアス(JSON のキー名と Python の名前を分ける)

JSON が userId のような camelCase で、Python では user_id と書きたいときはエイリアスを使います。 2.11 から、入力で項目名を許す validate_by_name と、出力をエイリアスにする serialize_by_alias を分けて指定できます。

from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel

class ApiUser(BaseModel):
    model_config = ConfigDict(validate_by_name=True, serialize_by_alias=True)
    user_id: int = Field(alias="userId")
    display_name: str = Field(alias="displayName")

u = ApiUser.model_validate({"userId": 1, "displayName": "Hana"})
print(repr(u))
print(u.model_dump(), u.model_dump(by_alias=False))
print(repr(ApiUser(user_id=2, display_name="Ken")))   # Python の名前でも作れる

class CamelModel(BaseModel):                           # 全項目を自動で camelCase に
    model_config = ConfigDict(alias_generator=to_camel, validate_by_name=True)
    order_id: int
    created_by: str

print(CamelModel.model_validate({"orderId": 5, "createdBy": "me"}).model_dump(by_alias=True))
ApiUser(user_id=1, display_name='Hana')
{'userId': 1, 'displayName': 'Hana'} {'user_id': 1, 'display_name': 'Hana'}
ApiUser(user_id=2, display_name='Ken')
{'orderId': 5, 'createdBy': 'me'}

前からある populate_by_name=True も、2.14.1 では警告なしで動きました。

model_configの設定(strict・frozen・extra など)

この節では、モデル全体の動きを変える設定と、変えたときのエラーが分かります。

from pydantic import BaseModel, ConfigDict, Field

class Task(BaseModel):
    model_config = ConfigDict(validate_assignment=True)
    title: str
    priority: int = Field(ge=1, le=5)

t = Task(title="棚卸し", priority=3)
t.priority = 9        # → エラー

class Point(BaseModel):
    model_config = ConfigDict(frozen=True)
    x: int

Point(x=1).x = 2      # → エラー

class StrictKeys(BaseModel):
    model_config = ConfigDict(extra="forbid")
    name: str

StrictKeys(name="a", age=3)   # → エラー

class Loose(BaseModel):       # 既定の extra="ignore"
    name: str

print(repr(Loose(name="a", age=3)))
1 validation error for Task
priority
  Input should be less than or equal to 5 [type=less_than_equal, input_value=9, input_type=int]
    For further information visit https://errors.pydantic.dev/2.14/v/less_than_equal
1 validation error for Point
x
  Instance is frozen [type=frozen_instance, input_value=2, input_type=int]
    For further information visit https://errors.pydantic.dev/2.14/v/frozen_instance
1 validation error for StrictKeys
age
  Extra inputs are not permitted [type=extra_forbidden, input_value=3, input_type=int]
    For further information visit https://errors.pydantic.dev/2.14/v/extra_forbidden
Loose(name='a')

既定の extra="ignore" では、余分な age は黙って捨てられました。

設定 既定 変えると
extra "ignore" "forbid" で余分なキーをエラー、"allow" で残す
validate_assignment False 代入したときも検証
frozen False 変更できない・ハッシュできる
strict False 型を変換しない
from_attributes False ORM のオブジェクトなど属性から作れる
coerce_numbers_to_str False 数値→文字列の変換を許す(v1 と同じ動き)
str_strip_whitespace False 文字列の前後の空白を除く
use_enum_values False Enum を値で持つ

from_attributes=True を付けると、ORM の行のような「属性を持つオブジェクト」からモデルを作れます。手元では、id・name・password_hash を持つオブジェクトから UserOut(id=1, name='佐藤') を作れました(モデルに無い password_hash は入りません)。付けないと次のエラーになります。

1 validation error for NoAttr
  Input should be a valid dictionary or instance of NoAttr [type=model_type, input_value=<__main__.UserRow object at 0x7fe03e2dec10>, input_type=UserRow]
    For further information visit https://errors.pydantic.dev/2.14/v/model_type

TypeAdapter・validate_call・よく使う型

この節では、クラスを作らずに検証する方法と、メールアドレスや URL などの型が分かります。

from pydantic import BaseModel, ConfigDict, TypeAdapter, ValidationError, validate_call, EmailStr, HttpUrl, PositiveInt

# TypeAdapter:クラスを作らずに list・dict を検証
print(TypeAdapter(list[int]).validate_python(["1", 2, "3"]))
print(TypeAdapter(dict[str, float]).validate_json('{"a": "1.5", "b": 2}'))

# validate_call:関数の引数を検証
@validate_call
def repeat(text: str, times: PositiveInt) -> str:
    return text * times
print(repeat("ab", "3"))
repeat("ab", 0)                       # → エラー

# よく使う型(EmailStr は pydantic[email] が要る)
class Contact(BaseModel):
    email: EmailStr
    site: HttpUrl
print(repr(Contact(email="Hana@Example.com", site="https://example.com/a?b=1")))
Contact(email="hana@", site="example.com")   # → エラー

# 数値→文字列を許す(v1 と同じ動き)
class Code(BaseModel):
    model_config = ConfigDict(coerce_numbers_to_str=True)
    code: str
print(repr(Code(code=123)))
[1, 2, 3]
{'a': 1.5, 'b': 2.0}
ababab
1 validation error for repeat
1
  Input should be greater than 0 [type=greater_than, input_value=0, input_type=int]
    For further information visit https://errors.pydantic.dev/2.14/v/greater_than
Contact(email='Hana@example.com', site=HttpUrl('https://example.com/a?b=1'))
2 validation errors for Contact
email
  value is not a valid email address: There must be something after the @-sign. [type=value_error, input_value='hana@', input_type=str]
site
  Input should be a valid URL, relative URL without a base [type=url_parsing, input_value='example.com', input_type=str]
    For further information visit https://errors.pydantic.dev/2.14/v/url_parsing
Code(code='123')

EmailStr はドメインの部分を小文字にそろえました(Hana@Example.com → Hana@example.com)。validate_call のエラーの場所 1 は、2番目の引数(0 から数える)を指しています。

pydantic-settingsで環境変数と.envを読む

この節では、環境変数や .env を型つきで読み、足りない値を起動時に見つける方法が分かります。

v2 では BaseSettings が別のパッケージ pydantic-settings に分かれました。 環境変数は文字列でしか届きませんが、モデルの型どおりに int・bool・list に変換されます。

# settings_demo.py
import os
from pydantic import SecretStr, ValidationError
from pydantic_settings import BaseSettings, SettingsConfigDict

with open(".env", "w") as f:   # デモ用に .env を作る
    f.write("APP_DB_URL=postgresql://localhost/app\nAPP_DEBUG=true\nAPP_API_KEY=abc123\nAPP_PORT=7000\n")
os.environ["APP_PORT"] = "8080"
os.environ["APP_ALLOWED_HOSTS"] = '["a.example.com","b.example.com"]'

class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_prefix="APP_", env_file=".env")
    db_url: str
    port: int = 8000
    debug: bool = False
    api_key: SecretStr                 # print しても伏せ字になる
    allowed_hosts: list[str] = []      # list は JSON の形で渡す

s = Settings()
print(s)
print(s.port + 1, s.api_key.get_secret_value())
print(Settings(port=9000).port)        # 引数が最優先

os.environ["APP_PORT"] = "eighty"
Settings()                             # → エラー
os.environ["APP_PORT"] = "8080"
os.environ["APP_ALLOWED_HOSTS"] = "a.example.com,b.example.com"
Settings()                             # → エラー(JSON の形でない)
db_url='postgresql://localhost/app' port=8080 debug=True api_key=SecretStr('**********') allowed_hosts=['a.example.com', 'b.example.com']
8081 abc123
9000
1 validation error for Settings
port
  Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='eighty', input_type=str]
    For further information visit https://errors.pydantic.dev/2.14/v/int_parsing
SettingsError : error parsing value for field "allowed_hosts" from source "EnvSettingsSource"

優先の順は「初期化の引数 > 環境変数 > .env > 既定値」です。 .env の APP_PORT=7000 より環境変数の 8080 が、それより引数の 9000 が使われました。公式ドキュメントでは、環境変数の名前は既定で大文字・小文字を区別しないこと、list・dict などは「値を JSON の文字列として」読むこと、env_nested_delimiter="__" で入れ子のモデルに分けられることが説明されています。

FastAPIでのPydanticの使われ方

この節では、FastAPI がリクエストを Pydantic で検証し、エラーを返す様子が実物で分かります。

FastAPI では、関数の引数に BaseModel を書くと、リクエストの本文の JSON を読み、型を変換し、検証し、合わなければ 422 を返します。 公式ドキュメントは、これに加えてエディタの補完と、JSON Schema(OpenAPI)の自動生成を挙げています。

1

本文の JSON を読む

リクエストの本文を JSON として解析する。

FastAPI

2

モデルで検証・変換

"120" は 120 に。合わなければ errors() と同じ形の 422 を返す。

Pydantic

3

関数を呼ぶ

引数には検証済みのモデルが入る。中の処理は型を信じて書ける。

自分のコード

4

応答の形をそろえる

response_model の項目だけを返す。モデルに無い項目は出ない。

Pydantic

import json
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field

class ItemIn(BaseModel):
    name: str = Field(min_length=1, max_length=20)
    price: int = Field(gt=0)
    tags: list[str] = []

class ItemOut(BaseModel):
    id: int
    name: str
    price: int

app = FastAPI()

@app.post("/items", response_model=ItemOut, status_code=201)
def create_item(item: ItemIn):
    # item はすでに検証済みの ItemIn。price は int になっている
    return {"id": 1, "name": item.name, "price": item.price, "secret": "返さない"}

c = TestClient(app)
r = c.post("/items", json={"name": "ペン", "price": "120"})
print(r.status_code, r.json())
r = c.post("/items", json={"name": "", "price": 0})
for d in r.json()["detail"]: print(r.status_code, json.dumps(d, ensure_ascii=False))
201 {'id': 1, 'name': 'ペン', 'price': 120}
422 {"type": "string_too_short", "loc": ["body", "name"], "msg": "String should have at least 1 character", "input": "", "ctx": {"min_length": 1}}
422 {"type": "greater_than", "loc": ["body", "price"], "msg": "Input should be greater than 0", "input": 0, "ctx": {"gt": 0}}

"120" は 120 として受け取られ、関数が返した secret は response_model に無いので応答に出ませんでした。422 の detail は、loc の先頭に body が付くほかは e.errors() と同じ形です。/openapi.json には、ItemIn の制約が "minLength": 1・"exclusiveMinimum": 0.0 などの JSON Schema として入っていました。

FastAPI で CRUD の API を作る手順は、関連記事「FastAPI入門|uvで作るCRUD API完成コードと実行結果」で、Pydantic のモデルをそのままデータベースのテーブルにも使う方法は、関連記事「SQLModel入門|FastAPIで使うモデル定義とCRUD」で扱っています。

LLMの構造化出力とJSON Schema

この節では、Pydantic のモデルを LLM の出力の「型」として使う流れが分かります。

モデルから JSON Schema を作って LLM に渡し、返ってきた JSON 文字列を model_validate_json で検証します。 公式ドキュメントによると、出力される JSON Schema は 2020-12 の仕様に沿っていて、OpenAPI 3.1 と互換です。

import json
from typing import Literal
from pydantic import BaseModel, Field, ValidationError

class Inquiry(BaseModel):
    """問い合わせメールから取り出す項目"""
    category: Literal["見積もり", "不具合", "その他"]
    company: str = Field(description="会社名")
    budget_yen: int | None = Field(default=None, ge=0, description="予算(円)")

print(json.dumps(Inquiry.model_json_schema(), ensure_ascii=False))

# LLM が返した(ことにする)文字列を検証する
print(repr(Inquiry.model_validate_json('{"category": "見積もり", "company": "サンプル商事", "budget_yen": "300000"}')))
Inquiry.model_validate_json('{"category": "相談", "company": "サンプル商事", "budget_yen": -1')    # 途中で切れた JSON
Inquiry.model_validate_json('{"category": "相談", "company": "サンプル商事", "budget_yen": -1}')   # 値が決まりと違う
{"description": "問い合わせメールから取り出す項目", "properties": {"category": {"enum": ["見積もり", "不具合", "その他"], "title": "Category", "type": "string"}, "company": {"description": "会社名", "title": "Company", "type": "string"}, "budget_yen": {"anyOf": [{"minimum": 0, "type": "integer"}, {"type": "null"}], "default": null, "description": "予算(円)", "title": "Budget Yen"}}, "required": ["category", "company"], "title": "Inquiry", "type": "object"}
Inquiry(category='見積もり', company='サンプル商事', budget_yen=300000)
1 validation error for Inquiry
  Invalid JSON: EOF while parsing an object at line 1 column 72 [type=json_invalid, input_value='{"category": "相談", "...事", "budget_yen": -1', input_type=str]
    For further information visit https://errors.pydantic.dev/2.14/v/json_invalid
2 validation errors for Inquiry
category
  Input should be '見積もり', '不具合' or 'その他' [type=literal_error, input_value='相談', input_type=str]
    For further information visit https://errors.pydantic.dev/2.14/v/literal_error
budget_yen
  Input should be greater than or equal to 0 [type=greater_than_equal, input_value=-1, input_type=int]
    For further information visit https://errors.pydantic.dev/2.14/v/greater_than_equal

クラスの docstring と Field(description=...) は、JSON Schema の description に入ります。LLM に項目の意味を伝える場所として使えます。途中で切れた JSON は json_invalid、決まりと違う値は項目ごとのエラーになるので、エラーの内容を添えて LLM に再生成を頼む、という作りにできます。各社の API の構造化出力の方式の違いは、関連記事「LLMの構造化出力とJSON|4方式の違いとPydantic検証の実行結果」で扱っています。

Pydantic v1からv2への移行(対応表)

この節では、v1 のコードを v2 に書き換えるときの対応と、移行ツールの実行結果が分かります。

v1 の .dict() などは v2 でも動きますが、PydanticDeprecatedSince20 の警告が出ます。

ex11_v1.py:6: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.14/migration/
ex11_v1.py:7: PydanticDeprecatedSince20: The `parse_obj` method is deprecated; use `model_validate` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.14/migration/
v1 v2
Model.parse_obj(d) Model.model_validate(d)
Model.parse_raw(s) Model.model_validate_json(s)
m.dict() m.model_dump()
m.json() m.model_dump_json()
m.copy() m.model_copy()
Model.schema() Model.model_json_schema()
Model.construct() Model.model_construct()
Model.update_forward_refs() Model.model_rebuild()
Model.__fields__ Model.model_fields
parse_obj_as(list[M], d) TypeAdapter(list[M]).validate_python(d)
@validator @field_validator(@classmethod を付ける)
@root_validator @model_validator
class Config: model_config = ConfigDict(...)
orm_mode = True from_attributes=True
allow_population_by_field_name populate_by_name(2.11 から validate_by_name も)
Field(regex=...) Field(pattern=...)
min_items / max_items min_length / max_length
from pydantic import BaseSettings from pydantic_settings import BaseSettings
Optional[X] は省略できた 既定値が無ければ必須

bump-pydantic で書き換えた結果

公式の移行ツール bump-pydantic(ベータ版)は、pip install bump-pydantic で入れ、bump-pydantic パッケージのフォルダ で実行します。 v1 の書き方のファイルに通した前と後です。

# 前(v1 の書き方)
from typing import Optional
from pydantic import BaseModel, validator


class User(BaseModel):
    id: int
    name: str
    note: Optional[str]

    class Config:
        orm_mode = True

    @validator("name")
    def strip_name(cls, v):
        return v.strip()


def load(d):
    u = User.parse_obj(d)
    return u.dict()
# 後(bump-pydantic 0.8.0 が書き換えたもの)
from typing import Optional
from pydantic import field_validator, ConfigDict, BaseModel


class User(BaseModel):
    id: int
    name: str
    note: Optional[str] = None
    model_config = ConfigDict(from_attributes=True)

    @field_validator("name")
    @classmethod
    def strip_name(cls, v):
        return v.strip()


def load(d):
    u = User.parse_obj(d)
    return u.dict()

Config・@validator・Optional の既定値は書き換わり、parse_obj と .dict() の呼び出しはそのまま残りました。実行すると、この2行に PydanticDeprecatedSince20 の警告が出ます。ツールの後は、警告を手がかりに上の表で書き換えます。

  • 1bump-pydantic を通して差分を確かめる--diff で書き換える前に中身を見られる
  • 2警告を表示してテストを流すpython -W always で PydanticDeprecatedSince20 を全部出す
  • 3Optional で既定値の無い項目を探すv2 では必須になる
  • 4数値を str の項目に入れている所を探すv2 はエラー。coerce_numbers_to_str で前と同じにできる
  • 5BaseSettings の import を直すpydantic-settings を入れて import 先を変える

一度に書き換えられないときは、from pydantic.v1 import BaseModel で v1 の動きのまま使えます。 2.13.0 のリリースノートでは、pydantic.v1 を Python 3.14 に対応した v1.10.26 に更新したと書かれています。手元の Python 3.14.7 でも、pydantic.v1 の @validator を使ったモデルが動きました({'id': 3, 'name': '123'}。v1 では数値 123 が文字列になる)。

よくあるエラーと直し方

この節では、Pydantic でよく出るエラーの原文と、原因・直し方が分かります。

エラー・症状 原因 直し方
Field required [type=missing] X | None に既定値が無い = None を付ける
Input should be a valid string [type=string_type] 数値を str に渡した(v2 は変換しない) 呼ぶ側で str()、または coerce_numbers_to_str=True
Input should be a valid integer, unable to parse string 数字でない文字列 入力を直す。前処理は field_validator(mode="before")
Extra inputs are not permitted extra="forbid" で決めていないキー モデルに項目を足すか、キーを除く
Instance is frozen frozen=True のモデルに代入 model_copy(update=...) で新しく作る
Input should be a valid dictionary or instance of ... ORM のオブジェクトをそのまま渡した from_attributes=True
Invalid JSON: EOF while parsing JSON 文字列が途中で切れている 送る側を直す。LLM なら再生成
Decorators defined with incorrect fields @field_validator("無い項目名") 項目名を直す(継承先で定義するなら check_fields=False)
BaseModel.__init__() takes 1 positional argument Line(120, 3) と位置で渡した Line(unit_price=120, qty=3) とキーワードで渡す
BaseSettings has been moved v1 の import pydantic-settings を入れて import 先を変える
regex is removed. use pattern instead v1 の Field(regex=...) Field(pattern=...)
If you use @root_validator with pre=False ... v1 の @root_validator @model_validator(mode="after")
'orm_mode' has been renamed の警告 v1 の設定名 from_attributes=True
shadows an attribute in parent "BaseModel" の警告 項目名に json などを使った 別の名前にして alias="json" を付ける
SettingsError: error parsing value for field list の環境変数が JSON の形でない '["a","b"]' の形で渡す
PydanticUserError : Decorators defined with incorrect fields: __main__.a.<locals>.Bad:1111272448.chk (use check_fields=False if you're inheriting from the model and intended this)
TypeError : BaseModel.__init__() takes 1 positional argument but 3 were given
UserWarning: Valid config keys have changed in V2:
* 'orm_mode' has been renamed to 'from_attributes'
PydanticImportError : `BaseSettings` has been moved to the `pydantic-settings` package. See https://pydantic.dev/docs/validation/2.14/migration/#basesettings-has-moved-to-pydantic-settings for more details.
UserWarning : Field name "json" in "M2" shadows an attribute in parent "BaseModel"
PydanticUserError : `regex` is removed. use `pattern` instead
PydanticUserError : If you use `@root_validator` with pre=False (the default) you MUST specify `skip_on_failure=True`. Note that `@root_validator` is deprecated and should be replaced with `@model_validator`.

検証が効かない落とし穴と、作り直し方

この節では、Pydantic のモデルなのに決まりに合わない値が入ってしまう場面と、その直し方が分かります。

次の3つは、検証を通らずに値が入ります。 priority: int = Field(ge=1, le=5) のモデルで試しました。

t = Task(priority=3)
t.priority = "高"                          # 既定では代入は検証されない
u = Task.model_construct(priority=-5)       # 検証しないで作る
v = t.model_copy(update={"priority": 99})   # update の値も検証されない
w = Task.model_validate({**v.model_dump(), "priority": 4})   # 作り直すと検証される
Task(priority='高')
Task(priority=-5)
Task(priority=99)
Task(priority=4)
操作 何が起きるか 代わりの書き方
作った後の代入 型も制約も確かめない validate_assignment=True か frozen=True
model_construct() 検証も変換もしない 外から来たデータには使わず model_validate()
model_copy(update=...) update の値を確かめない model_validate({**m.model_dump(), ...}) で作り直す

model_copy で id に "not-int" を入れた場合も、そのまま 'not-int' が入りました。外から来た値は、必ず model_validate 系の入口を通します。

よくある質問

Q. Pydanticとは何ですか? A. Python の型ヒントを使って、データを検証し、型をそろえるライブラリです。API の入力・設定・外部の JSON など、外から来るデータの入口で使います(「Pydanticとは」の節)。

Q. Pydanticの読み方は? A. 日本では「パイダンティック」と読まれることが多いです。

Q. Pydanticは標準ライブラリですか? A. 標準ライブラリではありません。pip install pydantic か uv add pydantic で入れます。2026年10月24日時点の最新は 2.14.1 で、Python 3.10 以上が対象です。

Q. Pydanticとdataclassの違いは? A. 標準の dataclass は型ヒントを書いても値を確かめず、"x" も入ります。Pydantic は型どおりに変換・検証し、問題を全項目まとめて返します。外から来るデータは Pydantic、検証済みの値を運ぶだけなら dataclass が向いています(違いの節の表)。

Q. PydanticとTypedDictの違いは? A. TypedDict は dict に型の注釈を付けるだけで、実行時には確かめません。TypeAdapter(TypedDict のクラス) に通すと、Pydantic で検証できます。10万件では BaseModel をまとめて検証するより速く、78.1ms でした。

Q. Pydanticは遅くないですか? A. 検証のない dataclass より時間はかかります(10万件で 48.0ms と 324.5ms)。リストは TypeAdapter でまとめて、JSON は validate_json に直接渡すと速くなります。v1 より約3.7倍速くなっています。

Q. Pydantic v1のコードはv2でそのまま動きますか? A. 多くのメソッドは警告つきで動きますが、Optional の必須の決まり、数値→文字列の変換、@validator の書き方などが変わっています。bump-pydantic で書き換えた後、警告を手がかりに対応表で直します。

Q. field_validatorとmodel_validatorはどちらを使えばよいですか? A. 1項目で完結する決まりは field_validator、2つ以上の項目の関係(開始日と終了日、パスワードの確認など)は model_validator(mode="after") です。

Q. FastAPIではどう使われますか? A. 引数に BaseModel を書くと、FastAPI が本文の JSON を検証し、合わなければ e.errors() と同じ形の 422 を返します。response_model に書いたモデルの項目だけが応答に出ます。

Q. 検証しないでモデルを作れますか? A. model_construct() で作れますが、今回の計測では速くなりませんでした。信頼できるデータを、検証なしで作りたいときだけに使います。

Q. mypy などの型チェッカーとPydanticの違いは? A. 型チェッカーはコードを実行する前に、書いたコードの型の食い違いを見つけます。Pydantic は実行中に、外から入ってきた値を確かめます。外からのデータは実行するまで分からないので、両方を使うと役割が分かれます。

まとめ

  • Pydantic は、型ヒントどおりにデータを検証し、型をそろえるライブラリ。最新は 2.14.1(2026年10月24日時点、Python 3.10 以上)
  • 置き場所は外から来るデータの入口(API・外部の JSON・設定・LLM の出力)。内側の受け渡しは dataclass で済む
  • dataclass と TypedDict は値を確かめず、Pydantic は変換・検証して全項目のエラーを返す。attrs は書いた分だけ確かめる
  • 既定は lax("42" → 42)。strict=True で変換しない。数値→文字列は既定でも変換しない
  • X | None は既定値が無ければ必須。項目は field_validator、項目どうしは model_validator(mode="after")
  • 書き出しは model_dump(mode="json") / model_dump_json()、読み込みは model_validate_json()
  • 速さは、リストは TypeAdapter でまとめて、JSON は validate_json に直接。代入・model_construct・model_copy(update=...) は検証されない

参考・出典

確認日はいずれも 2026年10月24日です。

  • Pydantic 公式ドキュメント:https://pydantic.dev/docs/validation/latest/
  • Pydantic「Why use Pydantic?」:https://pydantic.dev/docs/validation/latest/get-started/why/
  • Pydantic「Models」:https://pydantic.dev/docs/validation/latest/concepts/models/
  • Pydantic「Validators」:https://pydantic.dev/docs/validation/latest/concepts/validators/
  • Pydantic「Serialization」:https://pydantic.dev/docs/validation/latest/concepts/serialization/
  • Pydantic「Strict Mode」:https://pydantic.dev/docs/validation/latest/concepts/strict_mode/
  • Pydantic「Dataclasses」:https://pydantic.dev/docs/validation/latest/concepts/dataclasses/
  • Pydantic「Settings Management」:https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/
  • Pydantic「Migration Guide」:https://pydantic.dev/docs/validation/latest/get-started/migration/
  • PyPI「pydantic」:https://pypi.org/project/pydantic/
  • GitHub「pydantic/pydantic」リリース:https://github.com/pydantic/pydantic/releases
  • GitHub「pydantic/bump-pydantic」:https://github.com/pydantic/bump-pydantic
  • FastAPI「Request Body」:https://fastapi.tiangolo.com/tutorial/body/
  • attrs 公式ドキュメント:https://www.attrs.org/

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Pydanticとは|何に使う・dataclassとの違いと使い方を実測で解説」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?