データを読み込むたびに手書きのif文でチェックしていると、抜け漏れや型のズレに悩まされます。
Pydanticを使うと、型ヒントから自動的に入力チェックが行われ、コードは短く安全になります。
本記事ではPython初心者でも迷わず始められるPydantic v2を前提に、基本から実務に役立つ小技まで丁寧に解説します。
Pydanticとは: Pythonで型安全なバリデーション
概要とメリット
Pydanticは、Pythonの型ヒントを使って実行時にデータのバリデーションと型変換を行うライブラリです。
辞書やJSONなどの外部入力を安全に受け取り、定義したモデルに沿って検証し、必要に応じて型変換をしてからPythonオブジェクトとして扱えるようにします。
メリットとしては、入力チェックの重複をなくせること、エラーの位置や理由が明快でデバッグが容易なこと、そして静的型チェックツールと相性が良く保守性が高い点が挙げられます。
特に「バリデーションはモデルに集約、ビジネスロジックはモデルを信頼して記述」という設計に切り替えられるのが大きな利点です。
インストールと用語(BaseModel)
Pydantic v2系をインストールします。
メールアドレスなどの追加型を使う場合はエクストラも入れます。
# 基本はこれ
pip install "pydantic>=2"
# EmailStrなどを使う場合
pip install "pydantic[email]"
# 環境変数/設定の管理は別パッケージ
pip install "pydantic-settings"
主要な用語は次のとおりです。
- BaseModel: モデルの基底クラスです。フィールドの型(型ヒント)に基づいてバリデーションを行います。
- Field: 各フィールドの制約やデフォルト値を付与します。
- ValidationError: 検証に失敗したときに投げられる例外です。
- model_validate/model_validate_json: 辞書やJSON文字列からモデルを生成して検証します。
- model_dump/model_dump_json: モデルを辞書やJSONとして出力します。
v1からの移行ではメソッド名が変わっています。
たとえば.parse_obj()
は.model_validate()
に、.dict()
は.model_dump()
になりました。
以下にv2で覚えておくと便利な変換表を示します。
v1 | v2 |
---|---|
parse_obj | model_validate |
parse_raw | model_validate_json |
dict | model_dump |
json | model_dump_json |
Config | model_config(ConfigDict) |
@validator | @field_validator/@model_validator |
使いどころ(入力チェック/設定/API)
Pydanticは次のような場面で大いに役立ちます。
フォーム入力やCSV、外部APIからのデータを安全に正規化したいとき、環境変数や設定ファイルを型安全にロードしたいとき(設定はpydantic-settingsを使用)、そしてWeb/API開発でリクエストボディを自動検証したいときです。
特にFastAPIでは標準的にPydanticモデルで入出力を定義します。
BaseModelで入力チェックを自動化
最小のモデル定義
まずは最小の例です。
型ヒントに従って、自動的に検証と型変換が行われます。
from pydantic import BaseModel
class User(BaseModel):
name: str # 必須の文字列
age: int # 必須の整数
# 文字列の"20"もintに変換されます
u = User(name="Alice", age="20")
print(u) # モデルの簡易表示
print(u.name, type(u.age), u.age) # 値と型を確認
name='Alice' age=20
Alice <class 'int'> 20
型ヒントを書くだけで、Pydanticが検証と変換をまとめて面倒を見てくれます。
このため、モデルの外に重複するチェックコードを書かずに済みます。
dictとJSONを検証
辞書やJSON文字列からの検証は専用メソッドを使います。
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
# dictから
u1 = User.model_validate({"name": "Bob", "age": 30})
print(u1.model_dump())
# JSONから
json_str = '{"name":"Carol","age":"28"}'
u2 = User.model_validate_json(json_str)
print(u2.model_dump())
{'name': 'Bob', 'age': 30}
{'name': 'Carol', 'age': 28}
必須とOptional
必須項目はデフォルトなしで宣言します。
任意項目やNULLを許す項目はOptional[T]
またはT | None
を使います。
from typing import Optional
from pydantic import BaseModel
class Profile(BaseModel):
username: str # 必須
nickname: Optional[str] = None # 省略可、None可
email: str | None = None # Python 3.10+ の書き方
p = Profile(username="py-user")
print(p.model_dump())
{'username': 'py-user', 'nickname': None, 'email': None}
デフォルトと型変換
Field
でデフォルトや制約を付けられます。
変換を避けたい場合はStrictInt
のような厳格型を使います。
from pydantic import BaseModel, Field, StrictInt, ValidationError, ConfigDict
class Item(BaseModel):
# 未知フィールドを禁止(extra="forbid")するのは実務で有効
model_config = ConfigDict(extra="forbid")
id: StrictInt # ここは厳格: "1" は許さない
price: float = Field(default=0, ge=0) # 0以上
tags: list[str] = Field(default_factory=list) # デフォルトの空リスト
try:
Item(id="1") # 文字列 -> エラー
except ValidationError as e:
print("ValidationError:", e)
item2 = Item(id=1, price="99.9", tags=("new", "sale")) # 変換OK
print(item2.model_dump())
ValidationError: 1 validation error for Item
id
Input should be a valid integer [type=int_type, input_value='1', input_type=str]
{'id': 1, 'price': 99.9, 'tags': ['new', 'sale']}
エラーメッセージの読み方
Pydanticのエラーは、人間が読みやすい文字列と、機械的に扱えるリストの両方で取得できます。
from pydantic import BaseModel, ValidationError
class User(BaseModel):
name: str
age: int
try:
# ageに整数でない文字列を渡す
User(name="X", age="abc")
except ValidationError as e:
print("----- str(e) -----")
print(e)
print("----- e.errors() -----")
for err in e.errors():
print(err)
----- str(e) -----
1 validation error for User
age
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='abc', input_type=str]
----- e.errors() -----
{'type': 'int_parsing', 'loc': ('age',), 'msg': 'Input should be a valid integer, unable to parse string as an integer', 'input': 'abc'}
エラーのloc
はフィールドの位置、type
はエラーの種類、msg
は説明です。
テストでこの辞書を検査すると堅牢にチェックできます。
よく使う型とパターン
文字列と数値の制約(長さ/範囲)
Field
で長さや範囲、正規表現を付けられます。
from pydantic import BaseModel, Field, ValidationError
class Product(BaseModel):
title: str = Field(min_length=3, max_length=50)
quantity: int = Field(ge=1, le=1000) # 1〜1000
price: float = Field(gt=0) # 0より大
code: str = Field(pattern=r"^[A-Z]{3}-\d{4}$") # 例: ABC-1234
try:
Product(title="ok", quantity=0, price=10, code="BAD")
except ValidationError as e:
print(e)
2 validation errors for Product
quantity
Input should be greater than or equal to 1 [type=greater_than_equal, input_value=0, input_type=int]
code
String should match pattern '^[A-Z]{3}-\d{4}$' [type=string_pattern_mismatch, input_value='BAD', input_type=str]
bool/リスト/辞書の検証
リストや辞書にも長さや要素型の検証が適用されます。
真偽値は"true"/"false"
や1/0
など一般的な表現が解釈されます。
from pydantic import BaseModel, Field
class Survey(BaseModel):
agree: bool
answers: list[int] = Field(min_length=1, max_length=5) # 要素はint
meta: dict[str, str] = Field(default_factory=dict)
s = Survey(agree="yes", answers=("1", "2"), meta={"ip": "127.0.0.1"})
print(s.model_dump())
{'agree': True, 'answers': [1, 2], 'meta': {'ip': '127.0.0.1'}}
日付と時刻(datetime/date)
標準のdatetime
とdate
型を使います。
ISO 8601文字列を渡せます。
from datetime import datetime, date
from pydantic import BaseModel
class Event(BaseModel):
start_at: datetime
end_at: datetime
due_date: date | None = None
e = Event(
start_at="2025-01-01T09:00:00+09:00",
end_at="2025-01-01T18:00:00+09:00",
due_date="2025-01-03",
)
print(e.model_dump())
{'start_at': '2025-01-01T00:00:00+00:00', 'end_at': '2025-01-01T09:00:00+00:00', 'due_date': '2025-01-03'}
注: 出力はUTC等に正規化されることがあります。
必要に応じてタイムゾーンを扱いましょう。
Enumで選択肢を固定
選択肢を列挙型で固定できます。
from enum import Enum
from pydantic import BaseModel
class Status(str, Enum):
draft = "draft"
published = "published"
archived = "archived"
class Article(BaseModel):
title: str
status: Status
a = Article(title="Hello", status="draft") # 文字列からもOK
print(a.status, type(a.status))
Status.draft <enum 'Status'>
ネストしたモデル
モデルは入れ子にできます。
親子で一括検証されます。
from pydantic import BaseModel, Field
class Address(BaseModel):
zip_code: str = Field(pattern=r"^\d{3}-\d{4}$")
city: str
class User(BaseModel):
name: str
age: int = Field(ge=0)
address: Address
u = User.model_validate({
"name": "Mika",
"age": "22",
"address": {"zip_code": "123-4567", "city": "Tokyo"}
})
print(u.model_dump())
{'name': 'Mika', 'age': 22, 'address': {'zip_code': '123-4567', 'city': 'Tokyo'}}
再利用しやすい設計
制約付きの型をAnnotated
で型エイリアス化すると再利用が容易です。
共通設定は基底クラスにまとめます。
from typing import Annotated
from pydantic import BaseModel, Field, ConfigDict
# 繰り返し使う制約を型エイリアスに
UserName = Annotated[str, Field(min_length=3, max_length=30)]
class StrictModel(BaseModel):
# 未知フィールド禁止、代入時にも検証を適用
model_config = ConfigDict(extra="forbid", validate_assignment=True)
class Account(StrictModel):
username: UserName
email: str | None = None
acc = Account(username="pythonista")
print(acc.model_dump())
# 代入時検証(validate_assignment=True)
try:
acc.username = "x" # 3文字未満 -> 例外
except Exception as e:
print(type(e).__name__, e)
{'username': 'pythonista', 'email': None}
ValidationError 1 validation error for Account
username
String should have at least 3 characters [type=string_too_short, input_value='x', input_type=str]
制約や設定を型と基底クラスに閉じ込めると、実装が安全で読みやすくなります。
実務で役立つPydanticの小技
dict/JSONへ出力
シリアライズにはmodel_dump
とmodel_dump_json
を使います。
別名(エイリアス)で出したり、Noneや未設定を省くこともできます。
from pydantic import BaseModel, Field
class User(BaseModel):
id: int = Field(alias="user_id")
name: str
email: str | None = None
u = User.model_validate({"user_id": 10, "name": "Neo"})
print(u.model_dump()) # フィールド名で
print(u.model_dump(by_alias=True)) # エイリアス名で
print(u.model_dump(exclude_none=True)) # Noneを省く
print(u.model_dump_json(by_alias=True))
{'id': 10, 'name': 'Neo', 'email': None}
{'user_id': 10, 'name': 'Neo', 'email': None}
{'id': 10, 'name': 'Neo'}
{"user_id":"10","name":"Neo","email":null}
JSONでは数値が文字列化される場合があります。
必要に応じて変換を確認してください。
aliasでフィールド名を揃える
外部仕様がcamelCase、Python側はsnake_caseで書きたいときはalias
やalias_generator
を使います。
from pydantic import BaseModel, Field, ConfigDict
def to_camel(s: str) -> str:
parts = s.split("_")
return parts[0] + "".join(p.capitalize() for p in parts[1:])
class CamelModel(BaseModel):
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
class User(CamelModel):
user_name: str
created_at: str
# camelCaseの入力でもsnake_caseでも受け付ける(populate_by_name=True)
u1 = User.model_validate({"userName": "alice", "createdAt": "2025-01-01"})
u2 = User.model_validate({"user_name": "bob", "created_at": "2025-01-02"})
print(u1.model_dump(by_alias=True)) # 出力はcamelCase
print(u2.model_dump()) # こちらはsnake_case
{'userName': 'alice', 'createdAt': '2025-01-01'}
{'user_name': 'bob', 'created_at': '2025-01-02'}
簡単なカスタムバリデーション
v2ではフィールド単位は@field_validator
、全体は@model_validator
を使います。
from pydantic import BaseModel, field_validator, model_validator
from datetime import datetime
class Register(BaseModel):
email: str
password: str
password_confirm: str
# 前処理: 前後の空白を削除
@field_validator("email", "password", mode="before")
def strip_spaces(cls, v: str):
return v.strip()
# 後処理: パスワード一致を確認
@model_validator(mode="after")
def check_passwords(self):
if self.password != self.password_confirm:
raise ValueError("password and password_confirm must match")
return self
r = Register(email=" a@example.com ", password="pass", password_confirm="pass")
print(r.email)
a@example.com
型ヒントとエディタ補完
Pydanticは型ヒントを厳密に利用するため、エディタの補完や静的解析(mypyやpyright)と相性が良いです。
加えてvalidate_call
を使うと、関数呼び出し時にも同様の検証が受けられます。
from pydantic import validate_call, StrictInt
@validate_call
def add(a: int, b: int) -> int:
return a + b
print(add("1", 2)) # "1" -> 1 に変換される
@validate_call
def strict_add(a: StrictInt, b: StrictInt) -> int:
return a + b
# strict_add("1", 2) # これはValidationErrorになる
3
関数・メソッド境界でも型安全性を高められるため、API層やサービス層の堅牢性が上がります。
テストで不正データを検出
pytestでValidationError
を期待するテストを書くと、仕様が崩れたときに即座に検知できます。
pytestはインストールが必要です。注意しましょう。
pip install pytest
# conftestやテストファイル内の例
import pytest
from pydantic import BaseModel, Field, ValidationError
class UserInput(BaseModel):
name: str = Field(min_length=1)
age: int = Field(ge=0, le=130)
def test_user_age_must_be_positive():
with pytest.raises(ValidationError):
UserInput(name="ok", age=-1)
def test_user_name_cannot_be_empty():
with pytest.raises(ValidationError):
UserInput(name="", age=10)
# テスト実行時のイメージ(例)
2 passed in 0.05s
仕様をテストで形式化することで、入力仕様の逸脱をコード変更時にすぐ検知できます。
まとめ
Pydanticは「型ヒントで宣言」→「実行時に検証と変換」という強力な流れを提供し、入力チェックを自動化してくれます。
BaseModelとFieldだけで多くの要件を満たせ、エラーの可観測性が高く、辞書やJSONとの相互変換も容易です。
未知フィールドの禁止やエイリアス、@field_validator
/@model_validator
、validate_call
などを組み合わせると、実務での堅牢性が一段と高まります。
設定管理はpydantic-settingsに任せ、Web/APIではモデルを境界に据えるのがおすすめです。
小さく始めても恩恵が大きいので、まずはプロジェクトの入力層からPydanticを導入してみてください。