Python | Web フレームワーク:Pydantic モデル

Python
スポンサーリンク

概要(Pydantic モデル=「型付きのデータの入れ物+自動チェックマン」)

Pydantic モデルは一言でいうと、

「型ヒントをちゃんと書くだけで、
その形どおりにデータをチェックしてくれる“賢いデータクラス”」

です。

FastAPI では、

リクエストボディ(入力 JSON)
レスポンス(出力 JSON)
設定情報
DB から取ってきたデータの表現

など、あらゆる「構造化されたデータ」に Pydantic モデルを使います。

ポイントは、

Python の型ヒント(str, int, bool, list など)を書くだけで、
Pydantic が「その通りになっているか」を自動で検証してくれる

というところです。

ここをちゃんと理解すると、

辞書(dict)を手でいじくり回す世界から、
「安心して触れる型付きデータ」の世界にステップアップできます。


Pydantic モデルの基本構造(まずは形に慣れる)

BaseModel を継承して「フィールドと型」を書く

一番シンプルな Pydantic モデルは、次のような形です。

from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float
    in_stock: bool = True
Python

この 3 行に、かなり多くの情報が詰まっています。

name: str
これは「name という名前のフィールドが必須で、型は str(文字列)」を意味します。

price: float
これは「price という名前のフィールドが必須で、型は float(数値)」です。

in_stock: bool = True
これは「in_stock というフィールドは bool 型で、指定されなかったら True を入れる」という意味です。
「任意 + デフォルト値」として扱われます。

ここで大事なのは、「型ヒントそのものが“仕様”になっている」という感覚です。
このクラスを見るだけで、

このデータは、
名前(文字列)
価格(数値)
在庫があるかどうか(省略可、デフォルト True)

を持つべきだ、と分かります。

モデルを実際に使ってみる(バリデーションの挙動を見る)

作ったモデルを使って、インスタンスを作ってみます。

item = Item(name="Apple", price=1.5)
print(item)
print(item.in_stock)
Python

この場合、in_stock を指定していませんが、デフォルト値 True が入ります。

逆に、明らかに変な値を入れてみます。

item = Item(name="Apple", price="abc")
Python

実行すると、Pydantic が例外(ValidationError)を投げます。
「price は float であるべきなのに、文字列 ‘abc’ は変換できないよ」と怒ってくれるわけです。

つまり、

Item クラスを「正しいデータしか通さないフィルター」として使える

ということです。


Pydantic モデルがやってくれること(何が “嬉しい” のか)

型チェック(型ヒントどおりになっているかを自動で検証)

Pydantic の一番の仕事は、「型ヒントどおりになっているか」をチェックすることです。

name: str と書いたら必ず文字列
price: float と書いたら数値(文字列でも数値っぽければ変換してくれる)
in_stock: bool と書いたら真偽値(”true”, 1 なども解釈してくれる)

例えば、

Item(name="Apple", price="1.5", in_stock="true")
Python

と書いても、Pydantic はがんばって型に合わせて変換してくれます。

price=”1.5″ → float(1.5)
in_stock=”true” → bool(True) だと判断

のように、多少の揺れは吸収してくれる。
でも、どうしても変換できない値(price=”abc” など)は ValidationError にしてくれる。
このバランスがちょうどよいです。

デフォルト値・必須/任意の扱い

デフォルト値を書かないフィールドは「必須」です。
書かないと ValidationError になります。

Item(price=1.5)  # name がないのでエラー
Python

デフォルト値を書いたフィールドは「任意」です。
指定されなければデフォルトが入り、指定されればその値になります。

Item(name="Apple", price=1.5)            # in_stock=True が自動で入る
Item(name="Apple", price=1.5, in_stock=False)  # in_stock=False で上書き
Python

つまり、

「必須か任意か」を、
型ヒント+デフォルト値で自然に表現できる

ということです。


Optional と None(「本当にないかもしれない値」の扱い)

Optional[T] は「値が T かもしれないし None かもしれない」

Optional[str]str | None のように、「None を許す」型を表現できます。

例えば、説明文(description)はあってもなくてもいい項目だとします。

from typing import Optional
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float
    description: Optional[str] = None
Python

この場合、

description に文字列が入ることもあるし、
指定されないなら None でもよい

という意味になります。

実際の挙動としては、

Item(name="Apple", price=1.5)
# → description=None

Item(name="Apple", price=1.5, description="Fresh apple")
# → description="Fresh apple"
Python

のようになります。

Optional とデフォルトの組み合わせで「3状態」を表現できる

実務では、
「値がある」「値がない(None)」「そもそもフィールドごと送られていない」
という 3 状態を区別したくなることがあります。

Pydantic モデルでは、

Optional[str] = None

という書き方で、

指定がない → None
指定あり → その値(str)

という挙動にできます。

これによって、

このフィールドは「明示的に null が送られてきたのか」
それとも「クライアントが単に送ってこなかったのか」

を区別できます。

FastAPI で「部分更新(PATCH)」のときなどに特に効いてくる考え方です。


ネストした Pydantic モデル(複雑なデータ構造をきれいに表す)

モデルの中にモデルを入れる

現実のデータは、たいてい「入れ子」になります。
ユーザーが住所を持つ、商品がタグ一覧を持つ、など。

例えば、ユーザー + 住所の例を考えます。

from pydantic import BaseModel

class Address(BaseModel):
    postal_code: str
    prefecture: str
    city: str

class User(BaseModel):
    id: int
    name: str
    address: Address
Python

この User モデルは、

id: 整数
name: 文字列
address: Address 型(住所オブジェクト)

という構造です。

実際にインスタンスを作るときは、

user = User(
    id=1,
    name="Taro",
    address={
        "postal_code": "123-4567",
        "prefecture": "Tokyo",
        "city": "Koto-ku"
    }
)
Python

のように、address に dict を渡しても OK です。
Pydantic がそれを Address モデルに変換してくれます。

このように、

「複雑な JSON を、そのまま Pydantic モデルのネストで表現する」

という感覚が大事です。

リストや辞書も型付きで扱える

Pydantic は、list や dict にも型を付けて扱えます。

例えば、「商品が複数のタグ(文字列)を持つ」モデル。

from typing import List
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    tags: List[str] = []
Python

tags は「文字列のリスト」であることを意味します。
数値のリストを入れようとするとエラーになります。

また、「キーが str で値が int の dict」なども書けます。

from typing import Dict

class Scores(BaseModel):
    points: Dict[str, int]
Python

こうしておくと、

points に {“math”: 90, “english”: 80} は OK
{“math”: “high”} のように値が文字列ならエラー

という風に、「構造」と「型」の両方を守らせることができます。


Pydantic モデルと FastAPI(どう組み合わせるか)

リクエストボディとして使う

FastAPI では、エンドポイントの引数に Pydantic モデルを書くと、
そのモデルに従ってリクエストボディ(JSON)を解釈してくれます。

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float
    in_stock: bool = True

@app.post("/items")
def create_item(item: Item):
    total_price = item.price * 1.1
    return {
        "name": item.name,
        "price": item.price,
        "in_stock": item.in_stock,
        "price_with_tax": total_price,
    }
Python

クライアントは、こんな JSON を送ります。

{
  "name": "Apple",
  "price": 100
}

FastAPI は自動で:

JSON → dict → Item モデル(型チェック付き)

と変換したうえで、create_item 関数に渡してくれます。

関数の中では、もう「正しい形と型のデータ」が保証されている前提で書けるので、
チェックのための if 文だらけにならずに済みます。

レスポンスとして使う(response_model)

レスポンスの型にも Pydantic モデルを使うと、
「返り値の形」も宣言できます。

from typing import List
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

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

fake_db = [
    Item(id=1, name="Apple"),
    Item(id=2, name="Banana"),
]

@app.get("/items", response_model=List[Item])
def list_items():
    return fake_db
Python

ここでは、

返すべきレスポンスは「Item のリスト」

という契約になっています。

FastAPI は、返された値を List[Item] に沿うように整形して返してくれます。
余計なフィールドが混ざっていたら削ったり、型が違っていたらエラーにすることもできます。

このように、Pydantic モデルは

入力の「門番」
出力の「型の約束」

の両方に使える、というのが大きな特徴です。


実務レベルで大事な観点(Pydantic をどう “設計” するか)

「一つのモデル=一つの意味」を意識する

Pydantic モデルを雑に増やすと、
「どのモデルが何に使われるのか」がすぐ分からなくなります。

例えば、User だけでも、

UserCreate(作成時に必要な情報)
UserUpdate(更新時に送ってよい情報)
UserResponse(クライアントに返す情報)

のように、「用途ごとに分ける」ほうがきれいです。

UserResponse には「パスワード」は絶対に含めたくないですよね。
でも、UserCreate にはパスワードが必要かもしれない。

この違いをモデル名で表現しておくと、

コードを読むだけで、「このモデルはどの場面で使うのか」が一発で分かります。

モデル定義を「仕様書」として扱う

Pydantic モデルは、そのまま API の仕様書でもあり、
DB や設定の「スキーマ(構造)」でもあります。

書くときは、「どう動くか」ではなく、

このデータは何を表していて、
どんなフィールドがあって、
どれが必須で、
どれが任意で、
型は何か

という「意味」と「制約」を言語化するつもりで書くのが大事です。

FastAPI の /docs でモデル構造がそのまま表示されるので、
Pydantic モデルをちゃんと作ることが、そのまま「相手に優しい API」につながります。


まとめ(Pydantic モデルは「データを信頼できる形に変えるフィルター」)

Pydantic モデルの本質を、初心者目線でまとめるとこうなります。

  • Pydantic モデルは、BaseModel を継承してフィールド名と型を書くだけで、「その形どおりのデータかどうか」を自動でチェックしてくれる“型付きデータ入れ物”。
  • 型ヒント(str, int, bool, list など)とデフォルト値、Optional を組み合わせることで、「必須/任意」「デフォルト値」「None を許すかどうか」を自然な形で表現できる。
  • ネストしたモデルやリスト・辞書型を使えば、複雑な JSON 構造も安全に扱え、構造と意味がコードだけで伝わる。
  • FastAPI では、リクエストボディ(引数の Item 型)とレスポンス(response_model)に Pydantic モデルを使うことで、「入力/出力の仕様」「バリデーション」「ドキュメント生成」を一体化できる。
  • 実務では、「用途ごとにモデルを分ける」「モデル定義を仕様書とみなす」意識を持つことで、長く保守しやすい Web API や自動化システムを作れる。

タイトルとURLをコピーしました