Pydantic入門 〜型安全なPythonプログラミングへ〜

こんにちは、バックエンドエンジニアの田村です。
今回はPythonの動的型付けの柔軟性を活かしつつ、型の安全性を両立させる強力なツールであるPydanticに関して解説していきます。

自分が初めてPydanticに触れた時、型定義に対して馴染みがなく正直なところとても大変でした。しかし、このデータの型チェックやバリデーションがどれほど便利で信頼性を高めるものかを理解するにつれて、改めてその重要性を実感するようになりました。
例えば、APIのリクエスト、レスポンスのデータの検証を自動的に行いエラーの早期発見ができたり、データモデルを簡単に定義してわかりやすいコードを記述できるなど非常に役立っています。

今回のブログでは、Pydanticに馴染みがない方々に向けて、その基本から少し発展した機能までを解説し、その有用性や必要性をしっかりお伝えします。

Pydanticとは?

Pydanticは、Pythonのデータクラスに似たモデルを提供し、データバリデーションと解析を行うライブラリです。

Pydanticの主な特徴には以下が含まれます。
- データバリデーション:モデルに渡されたデータが正しい形式かどうかをチェックします。
- 自動型変換:文字列を日付に変換するなど、適切な型に自動で変換します。
- 簡単なモデル定義Pythonの型ヒントを使用して直感的にモデルを定義できます。

参考はこちら
docs.pydantic.dev

Pydanticの基本的な使い方

1. 基本的なモデル定義

Pydanticを使い始めるには、まずインストールが必要です。

pip install pydantic

次に、基本的なモデルを定義してみましょう。

from datetime import date
from pydantic import BaseModel

class Student(BaseModel):
    id: int
    name: str
    entrance_date: date
    major: str

このStudentモデルは、ユーザーのID、名前、入学年度、専攻を持っています。
データをインスタンス化する際に、自動的にバリデーションが行われます。

student = Student(
    id=1111,
    name="John Doe",
    entrance_date=date(2020, 4, 1),
    major="CS",
)
print(Student.dict())

"""
{
  "id": 1111,
  "name": "John Doe",
  "entrance_date": datetime.date(2020, 4, 1),
  "major": "CS"
}
"""

もし、データが正しい形式でない場合、Pydanticはエラーを投げます。

from pydantic import ValidationError

try:
    Student = Student(
        id=1111,
        name="John Doe",
        entrance_date="2000年4月1日",
        major="CS",
    )
    print(Student)
except ValidationError as e:
    print(e)
 
"""
1 validation error for Student
entrance_date
  Input should be a valid date or datetime, invalid date separator, expected `-` [type=date_from_datetime_parsing, input_value='2000年4月1日', input_type=str]
    For further information visit https://errors.pydantic.dev/2.7/v/date_from_datetime_parsing
"""

Studentの中のentrance_dateにおいて入力形式がdateかdatetimeである必要があるというバリデーションエラーですね。
~~entrance_date= "2000年4月1日"~~
たしかに、文字列で入力していますね。

2. Pydanticの高度な機能

基本的な使い方を理解したところで、次は少し高度な機能について見ていきましょう。
ここでは、カスタムバリデーションとネストモデルについて紹介します。

2-1.カスタムバリデーション

Pydanticでは、カスタムバリデーションメソッドを定義することもできます。特定のフィールドに対するバリデーションロジックを追加できます。

from datetime import date
from pydantic import BaseModel, ValidationError, field_validator

class Student(BaseModel):
    id: int
    name: str
    entrance_date: date
    major: str

    @field_validator("entrance_date", mode="before")
    def validate_entrance_date(cls, v):
        establish_year = date(1950, 9, 1)
        if v < establish_year:
            raise ValueError("設立以前になります")
        return v

    @field_validator("major", mode="before")
    def validate_major(cls, v):
        major_columns = ["CS", "ME", "EE", "CE", "PHYS", "MATH"]
        if v not in major_columns:
            raise ValueError("存在しない専攻種別です")
        return v

try:
    Student = Student(
        id=1111,
        name="John Doe",
        entrance_date=date(1920, 4, 1),
        major="CS",
    )
    print(Student)
except ValidationError as e:
    print(e)

"""
1 validation error for Student
entrance_date
  Value error, 設立以前になります [type=value_error, input_value=datetime.date(1920, 4, 1), input_type=date]
    For further information visit https://errors.pydantic.dev/2.7/v/value_error
"""

この例では、entrance_dateフィールドに対してカスタムバリデーションを行っています。
大学の設立年月日(1990年9月1日)より前の年月日がentrance_dateとして指定された場合エラーになります。

2-2.ネストされたモデル

Pydanticでは、モデルの中に別のモデルをネストすることができます。
これにより複雑なデータ構造を簡単に扱うことができます。

from datetime import date
from pydantic import BaseModel

class Student(BaseModel):
    id: int
    name: str
    entrance_date: date
    major: str

class GradeResult(BaseModel):
    student: Student
    subject: str
    score: int
    took_date: date

student = Student(
    id=1111,
    name="John Doe",
    entrance_date=date(2020, 4, 1),
    major="CS",
)
grade_result = GradeResult(
    student=student,
    subject="DataStructure",
    score=96,
    took_date=date(2020, 4, 1),
)
print(grade_result.dict())

"""
{
  "student": {
      "id": 1111,
       "name": "John Doe",
       "entrance_date": datetime.date(2020,4,1)
       "major": "CS"
  },
   "subject": "DataStructure",
   "score": 96,
   "took_date": datetime.date(2020,4,1)
}
"""

この例では、grade_resultモデルの中にstudentモデルをネストしています。
これにより成績情報と生徒情報を一緒に扱うことができます。

Pydanticと他のライブラリの連携

Pydanticは単独でも強力ですが、他のライブラリと組み合わせることでさらに威力を発揮します。特に、FastAPIとの相性が抜群です。

弊社の請求書サービスでもこのPydanticとFastAPIを組み合わせを用いて開発を行なっております!!

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float
    is_offer: bool = None

@app.post("/items/")
async def create_item(item: Item):
    return {"item_name": item.name, "created": True}

このように、FastAPIとPydanticを組み合わせることで、APIのリクエストとレスポンスを自動的に検証できます。
このことからAPIの堅牢性が大幅に向上し、ドキュメント生成も自動化されます。

詳しくはこちらで取り扱っております。
xmart-techblog.hatenablog.com

まとめ:Pydanticがもたらす変革

Pydanticは、Pythonのデータバリデーションを簡単かつ強力に行うためのツールです。
初めて触れる際には型定義や型ヒントに慣れるのが難しいかもしれませんが、その価値は非常に大きいです。
このブログを参考にPydanticの活用を進めてみてください。Pydanticを使いこなすことで、より堅牢でメンテナブルなコードを書くことができるようになると思います!!

最後に

最後まで読んでいただき、ありがとうございます。

クロスマートではバックエンドエンジニアはもちろんフロントエンドエンジニア、PMさらにはBizDevなど一緒に働ける方を募集しています。
ご興味がある方は、是非こちらを御覧ください!

xorder.notion.site