
Pydantic V2: Essentials: REST API (セクション12/13)
Pydantic V2 を使用すると、REST API のレスポンスや CSV ファイルからのデータ読み込み、関数引数の実行時検証など、さまざまなデータ検証と変換を簡単に行える。
モデル定義により、入力データの整合性が自動的に保証され、不要な特殊文字列(例:"Unknown")を適切な値(None)に変換するなど、データ品質が維持される。
さらに、datamodel-code-generator などのツールで JSON スキーマや生データから Pydantic モデルのひな型を自動生成できるが、複雑な場合は手動での調整が必要となる。
Pydantic は、データのシリアライズ、デシリアライズ、および検証を簡単に行えるようにします。単に REST API の入出力 JSON をモデル化するだけでなく、他にも強力な使い方があります。この投稿では、以下の 4 つの一般的な実世界のシナリオについて見ていきます。
REST API の利用(レスポンスのモデル化)
CSV ファイルからのデータの取り込み(検証および型変換)
関数の引数の検証(実行時)
モデルの自動生成(JSON スキーマ、JSON、CSV などから)
1. REST API の利用
外部の API を呼び出し、そのレスポンスを Pydantic モデルでラップしたい場合がよくあります。これにより、各フィールドが正しいかどうかが検証され、データが Python らしい型安全なオブジェクトに変換されます。ここでは、シンプルでレート制限もなく認証も不要な GeoJS という API を例に説明します。以下はその概要です。
import requests
from pydantic import BaseModel, ConfigDict, Field, field_validator, ValidationError, IPvAnyAddress
class IPGeo(BaseModel):
model_config = ConfigDict(extra="ignore")
ip: IPvAnyAddress
country: str | None = None
country_code: str | None = Field(default=None, min_length=2, max_length=2)
country_code3: str | None = Field(default=None, min_length=2, max_length=3)
city: str | None = None
region: str | None = None
timezone: str | None = None
organization_name: str | None = None
@field_validator("organization_name", mode="after")
@classmethod
def set_unknown_to_none(cls, value: str):
# API から "Unknown" という値が返された場合、None に変換する
if value.casefold() == "unknown":
return None
return value
ここでは以下の点に注意しています:
`model_config = ConfigDict(extra="ignore")`
モデルが気にしない余分なフィールドは無視します。`organization_name`
API が不明な場合に `"Unknown"` という文字列を返すため、バリデーターを用いてこれを実際の Python の `None` に変換します。検証と利用方法:
url_template = "https://get.geojs.io/v1/geo/{ip_address}.json"
response = requests.get(url_template.format(ip_address="8.8.8.8"))
response.raise_for_status() # エラーステータスの場合、例外を発生させる
geo_data = IPGeo.model_validate(response.json())
print(geo_data)
ここでは、
`requests.get(...)` によって API を呼び出し、
`raise_for_status()` により 200 番台以外の HTTP ステータスの場合に例外を発生させ、
`model_validate(...)` によって JSON の辞書を検証済みの Pydantic モデルに変換します。
このアプローチは、ネットワーキングコードに Pydantic を組み込む典型的な方法です。Pydantic は、受け取ったデータが期待通りであることを確認し、特殊なプレースホルダー値(例:"Unknown")を正しい Python の値(`None`)に変換するのに役立ちます。
2. CSV ファイルからのデータの取り込み
CSV ファイルはデータ型が内蔵されていないため、すべてのカラムが単なる文字列として扱われます。Pydantic を使えば、これらの文字列を特定の型に強制変換し、範囲検証なども行えます。たとえば、ヘッダー行があり、人口数が `" 4,464,356 "` のような形式になっている CSV(`pop_estimates.csv`)があるとします。
まず、カンマを含む文字列を適切な整数に変換するためのヘルパー関数を定義します。
def name_int(value: str):
try:
return int(value.strip().replace(",", ""))
except Exception:
raise ValueError("data could not be parsed into a valid integer")
次に、この関数を使って Pydantic に対してフィールドの前処理(BeforeValidator)を実行させるために、annotated type を作成します。
from typing import Annotated
from pydantic import BeforeValidator
FunkyInt = Annotated[int, BeforeValidator(name_int)]
そして、各行を表す Pydantic モデルを作成します。
from pydantic import BaseModel
class Estimate(BaseModel):
area: str
july_1_2001: FunkyInt
july_1_2000: FunkyInt
april_1_2000: FunkyInt
最後に、CSV の各行を読み込み、このモデルに渡して検証します。
import csv
def estimates():
with open("pop_estimates.csv") as f:
reader = csv.DictReader(
f,
fieldnames=["area", "july_1_2001", "july_1_2000", "april_1_2000"]
)
next(reader) # ヘッダー行をスキップする
for row in reader:
yield Estimate.model_validate(row)
for est in estimates():
print(est)
ここでは、
最初の行(ヘッダー行)はスキップし、
`model_validate(...)` により各辞書を `Estimate` インスタンスに変換し、
`FunkyInt` によって、人口数の文字列が前処理され整数に変換され、検証されます。
Pydantic は、正しくないデータに対して例外を発生させることで正確性を保証し、また変換処理を自動で行うため、Python 上で扱いやすい整数フィールドとして利用できるようになります。
3. 関数引数の検証
Pydantic のもうひとつの興味深い応用は、関数パラメーターの検証です。Python は静的型付けされていないため、実行時に引数の型や制約を強制したい場合があります。Pydantic は、これを実行時に検証するための `@validate_call` デコレーターを提供しています。
3.1 基本例
from typing import Annotated
from pydantic import validate_call, Field, ValidationError
NonEmptyString = Annotated[str, Field(min_length=1)]
@validate_call
def extract_first_char(s: NonEmptyString):
return s[0]
ここでは、
annotated type `NonEmptyString` を定義し、最低 1 文字以上であることを指定し、
`@validate_call` により、関数が呼び出される際に引数が自動的に検証されるようにしています。
try:
extract_first_char(None)
except ValidationError as e:
print(e)
この場合、引数が `None` であれば「有効な文字列でなければならない」という ValidationError が発生します。これにより、関数内に手動で `if not isinstance(s, str)` や空文字のチェックを書く必要がなくなります。Pydantic が自動的に検証を行い、要件に合わない場合は `ValidationError` を発生させます。
3.2 より高度な例:データの変換
また、Pydantic のカスタムバリデーターを使って、引数を変換することもできます。たとえば、以下の例では、文字列で渡された日付をパースし、UTC 時刻に変換します。
from datetime import datetime
from typing import Any
import pytz
from dateutil.parser import parse
from pydantic import BeforeValidator, AfterValidator
def parse_datetime(value: Any):
if isinstance(value, str):
return parse(value)
return value
def make_utc(dt: datetime) -> datetime:
return dt.astimezone(pytz.utc) if dt.tzinfo else pytz.utc.localize(dt)
DatetimeUTC = Annotated[
datetime,
BeforeValidator(parse_datetime),
AfterValidator(make_utc)
]
@validate_call
def format_iso(dt: DatetimeUTC):
return dt.isoformat()
print(format_iso("2020/1/1 3pm")) # "2020-01-01T15:00:00+00:00"
この例では、
文字列 `"2020/1/1 3pm"` がパースされ、UTC 時刻に変換されてから関数に渡され、
関数内では、確実に正しいタイムゾーン付きの `datetime` オブジェクトが渡されるため、`isoformat()` を安全に呼び出せます。
注意: もし Pydantic をプロジェクトに導入していない場合、たった一つの関数の検証のためだけに依存関係として追加するのは適切かどうかを検討してください。しかし、すでに Pydantic を利用している場合は、`@validate_call` を使うことでランタイムチェックを簡単に実現できます。
4. モデルコード生成ツール
Pydantic は非常に広く使われているため、JSON スキーマ、OpenAPI 仕様、または生の JSON や CSV データからモデルを自動生成するツールが存在します。
その中で有名なものが datamodel-code-generator です。
これは主にコマンドラインツールとして利用され、一度実行すると生成された Pydantic モデルコードをアプリケーションに「貼り付け」、その後は再実行しない(もちろん、用途によっては再生成することも可能)という使い方が一般的です。
たとえば、以下のようなコマンドで JSON スキーマからモデルを生成できます:
datamodel-codegen \
--input schema_1.json \
--input-file-type jsonschema \
--output-model-type pydantic_v2.BaseModel \
--target-python-version "3.12" \
--allow-population-by-field-name \
--field-constraints \
--snake-case-field
このコマンドは、JSON スキーマ(`schema_1.json`)を読み込み、Pydantic V2 の BaseModel を使った Python コードを出力します。単純な場合には大幅な時間節約となりますが、注意点もあります。
コード生成ツールは、条件分岐(if/then/else)のような複雑な制約をそのまま再現できない場合があります。
生の JSON データからモデルを生成する場合、フィールドの型推論が自動で行われますが、たとえば複数のフィールド(例えば `upper_left` と `lower_right` の両方が `x`、`y` を持つ)が同一の概念であるにもかかわらず、別々のクラスが生成されることもあり、手動で修正が必要になることもあります。
CSV データの場合、すべてが文字列として生成されるため、実際の型(整数や浮動小数点数など)に直すための手動修正が必要になることがあります。
結局のところ、コード生成ツールは大規模で安定したスキーマの場合には有用ですが、多くの場合、手作業でモデルを書くほうが最終的には効率的であると感じるかもしれません。
最終的なまとめ
まとめると、Pydantic V2 の応用例は多岐にわたります。
REST API の利用: レスポンスを検証し、不要な特殊文字列(例:"Unknown")を適切な値(`None`)に変換する。
CSV ファイルの取り込み: CSV の各カラムを適切な型に変換・検証し、Python のオブジェクトとして利用できるようにする。
関数引数の検証: `@validate_call` を利用して、関数に渡される引数を動的に検証し、不要なバリデーションコードを削減する。
モデル自動生成: JSON スキーマや生の JSON、CSV などからモデルのひな型を自動生成するツールが存在するが、場合によっては手動で調整する必要がある。
用途に応じて、これらのアプローチを組み合わせることで、データの取り扱いが容易になり、ボイラープレートコードを削減し、システムが期待通りのデータを受け渡ししているという確信を得ることができます。
以上が、Pydantic V2 における Pydantic の実際の応用例に関する詳細な説明です。どのアプローチも、データの検証、変換、およびシリアライズ・デシリアライズをシンプルかつ信頼性高く行うためのものです。