Pydanticのメジャーアップグレードに悩んでいませんか?v1からv2への移行で、それまで動いていたコードが突然エラーになる。設定が変わって何をどう修正すればいいのか分からない。そんな状況に陥っているエンジニアは少なくありません。
Pydantic v2は大幅な性能向上とAPI改善をもたらした一方で、互換性を保たない破壊的変更が数多く含まれています。しかし正しく理解して対応すれば、v2への移行は決して難しくありません。
本記事では、実際のエラーケースと具体的な解決策を通じて、Pydantic v2への移行をスムーズに進めるための実例ガイドを提供します。
Pydantic v2における主要な破壊的変更
Pydantic v2では、v1と互換性がない変更が複数あります。まずはどのような変更があるのかを理解することが重要です。
Configクラスからmodel_configへの移行
v1では、モデルの設定をConfigクラスで定義していました。v2ではこのアプローチが廃止され、model_configという新しい方法に統一されています。
v1の書き方:
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
class Config:
validate_assignment = True
str_strip_whitespace = True
v2の書き方:
from pydantic import BaseModel, ConfigDict
class User(BaseModel):
model_config = ConfigDict(
validate_assignment=True,
str_strip_whitespace=True
)
name: str
age: int
この変更は単なる記法の違いではなく、Pydantic v2の設計思想全体に関わるものです。設定をより明示的かつ型安全に管理できるようになりました。
バリデーター関数の装飾子の変更
v1の@validatorデコレータはv2で廃止され、@field_validatorと@model_validatorに分かれました。
v1の書き方:
from pydantic import BaseModel, validator
class Product(BaseModel):
price: float
@validator('price')
def price_must_be_positive(cls, v):
if v < 0:
raise ValueError('価格は正の数である必要があります')
return v
v2の書き方(フィールドレベル):
from pydantic import BaseModel, field_validator
class Product(BaseModel):
price: float
@field_validator('price')
@classmethod
def price_must_be_positive(cls, v):
if v < 0:
raise ValueError('価格は正の数である必要があります')
return v
v2の書き方(モデルレベル):
from pydantic import BaseModel, model_validator
class Product(BaseModel):
price: float
quantity: int
@model_validator(mode='after')
def check_inventory(self):
if self.price * self.quantity > 1000000:
raise ValueError('在庫価値が大きすぎます')
return self
重要なポイントは、v2では@classmethodデコレータが明示的に必要になった点です。またmodeパラメータで検証のタイミングを制御できるようになりました。
シリアライゼーションと出力の変更
データの出力方法もPydantic v2で大きく変わりました。
dict()からmodel_dump()へ
v1の書き方:
user = User(name='太郎', age=30)
user_dict = user.dict() # または user.dict(exclude={'age'})
user_json = user.json()
v2の書き方:
user = User(name='太郎', age=30)
user_dict = user.model_dump() # または model_dump(exclude={'age'})
user_json = user.model_dump_json()
dict()とjson()メソッドはv2では完全に削除されています。既存コードで.dict()を使用している箇所は全て.model_dump()に置き換える必要があります。
JSONスキーマの生成方法の変更
v1の書き方:
schema = User.schema()
with open('schema.json', 'w') as f:
json.dump(schema, f)
v2の書き方:
schema = User.model_json_schema()
with open('schema.json', 'w') as f:
json.dump(schema, f)
実践的な移行手順
では実際に、既存のPydantic v1プロジェクトをv2に移行する手順を説明します。
ステップ1:依存関係の更新
pip install --upgrade pydantic
Pydantic v2は内部エンジンとしてRust製のpydantic-coreを使用しており、バリデーション速度が飛躍的に向上しています。
ステップ2:モデル定義の修正
以下は実際の移行例です。ブログAPIのレスポンスモデルを想定しています。
修正前(v1コード):
from pydantic import BaseModel, validator
from datetime import datetime
from typing import Optional
class Article(BaseModel):
id: int
title: str
content: str
author: str
published_at: Optional[datetime] = None
views: int = 0
@validator('title')
def title_not_empty(cls, v):
if not v or len(v.strip()) == 0:
raise ValueError('タイトルは空にできません')
return v.strip()
@validator('views')
def views_must_positive(cls, v):
if v < 0:
raise ValueError('ビュー数は0以上である必要があります')
return v
class Config:
validate_assignment = True
json_encoders = {
datetime: lambda v: v.isoformat()
}
修正後(v2コード):
from pydantic import BaseModel, field_validator, ConfigDict
from datetime import datetime
from typing import Optional
class Article(BaseModel):
model_config = ConfigDict(validate_assignment=True)
id: int
title: str
content: str
author: str
published_at: Optional[datetime] = None
views: int = 0
@field_validator('title')
@classmethod
def title_not_empty(cls, v):
if not v or len(v.strip()) == 0:
raise ValueError('タイトルは空にできません')
return v.strip()
@field_validator('views')
@classmethod
def views_must_positive(cls, v):
if v < 0:
raise ValueError('ビュー数は0以上である必要があります')
return v
v2ではJSON encodersが廃止され、シリアライゼーションはより柔軟なfield_serializerで対応します。
ステップ3:メソッド呼び出しの更新
既存コードの以下の呼び出しを修正します。
| v1 | v2 |
|---|---|
model.dict() |
model.model_dump() |
model.json() |
model.model_dump_json() |
model.json_schema() |
model.model_json_schema() |
Model.parse_obj() |
Model.model_validate() |
Model.parse_raw() |
Model.model_validate_json() |
Model.construct() |
Model.model_construct() |
よくあるエラーと解決策
Pydantic v2への移行で直面しやすいエラーの解決策をまとめました。
「ValidationError: Input should be a valid integer」エラー
このエラーはv2のバリデーションが厳格化されたことが原因です。文字列の「123」を整数として入力した場合、v1では自動的に変換されましたが、v2ではデフォルトで変換されません。
解決方法:
from pydantic import BaseModel, ConfigDict
class Product(BaseModel):
model_config = ConfigDict(
str_to_number=True # v2では別途設定が必要
)
product_id: int
「AttributeError: 'User' object has no attribute 'Config'」
v1スタイルのConfigクラスを参照しているコードが残っている場合のエラーです。
解決方法:全てのモデル定義でclass Configをmodel_config = ConfigDict(...)に置き換えてください。
カスタムバリデーターが動作しない問題
v2ではmode='before'、mode='after'、mode='wrap'の3つの実行タイミングがあります。デフォルトは'after'ですが、必要に応じて指定してください。
from pydantic import field_validator
class User(BaseModel):
age: int
@field_validator('age', mode='before')
@classmethod
def convert_age(cls, v):
# 入力値がまだパース前の段階で実行
if isinstance(v, str):
return int(v)
return v