FastAPI x Pydantic による入出力定義とValidation

クロスマート バックエンドエンジニアの武(タケ)(@pouhiroshi)です。

前回は5分で作れる!FastAPI入門編 ということで、簡単なFastAPIのご紹介をしました。

今回はWebフレームワークには欠かせない「入出力定義とValidation」をFastAPIではどのようにやるかをご紹介します。

FastAPIでこれらを実装する際に推奨されているライブラリが Pydantic です。
https://pydantic-docs.helpmanual.io/

Pydanticの読み方ですが、パッと検索した感じ見当たらなかったので勝手に「パイダンティック」と読んでいます。
もし正解をご存知の方がいらっしゃいましたら、ぜひ弊社のカジュアル面談にお越しいただき、教えてください・・・

使い方ですが、JavaのBeanValidatonに近い感じです。
フィールドに型ヒントをつければそれに沿ってバリデーションなどをおこなってくれます。

インストール方法

pip install pydantic

基本的な使い方

まず、人間(Person)データをidで1件抽出するエンドポイントを作ってみたいと思います。
出力データの定義はPersonクラスに定義し、APIにてインスタンスを作成して返そうと思います。
response_modelに返すpydanticのクラス型を指定できます。Personインスタンスを返すように定義します。

from fastapi import FastAPI

from person import Person

app = FastAPI()

@app.get("/person/{person_id:int}",
         response_model=Person)
def get_person(person_id: int):
    """
    idによる人間データ取得
    """
    return Person(
        id=person_id,
        name="たけじい",
        job="会社員")

PersonクラスはPydanticのBaseModelを継承して作成します。
Field定義はなくても動作しますが、後ほどバリデーションの定義などに使用します。
Fieldのtitleに説明を書くと、swaggerに出力されますので便利です。

from pydantic import BaseModel, Field


class Person(BaseModel):
    """
    人間クラス
    """
    id: int = Field(title="id")
    """id"""
    name: str = Field(title="名前")
    """名前"""
    job: str | None = Field(title="職業")
    """職業"""

こちらでFastAPIを実行します。

swaggerのAPIレスポンス内 Schemaの欄に先ほど定義したtitleが表示されていますね。わかりやすくなりました。

実際にperson_idに値をいれて実行してみましょう。

Personインスタンスのデータがjsonで取得できましたね。idはintで定義しているので、ちゃんと数字型で表現されているようです。
出力に関してはこのような流れです。

Pydanticでフォームバリデーションを行う

次に、Validationについてやっていきたいと思います。
Personデータを登録するエンドポイントを例にしてやってみたいと思います。

idはAPI側で自動発番するものとして、入力としては要求しません。
なので、idフィールドは定義していません。
nameを必須とし、jobは無いことがありえるので任意入力とします。(str | None にあたるOptional表記です)

データ登録用のPersonクラスをPersonCreateクラスとして定義します。

from pydantic import BaseModel, Field


class PersonCreate(BaseModel):
    """
    人間作成クラス
    """
    name: str = Field(title="名前")
    """名前"""
    job: str | None = Field(title="職業")
    """職業"""

登録用エンドポイントは以下のようにします。
引数にPOSTされるPersonCreateのインスタンスを受け取ります。
本来はデータベースに登録して、idはデータベースに発番させますが、仮で999番をいれています。
発番したidをつかって先ほどGET /person/{person_id} で使っていたPersonインスタンスの形で返すようにしています。

@app.post("/person",
          response_model=Person)
def create_person(person_create: PersonCreate):
    return Person(
        id=999,
        name=person_create.name,
        job=person_create.job)


swaggerを見てみましょう。

POST /person が作成されました。Request bodyの Schemaを見ると、赤の*がnameについていて、jobにはついていません。
ちゃんと定義した通り、nameが必須、jobが任意のフィールドになりました。

では実際に動かしてみましょう。
まずはサッカー選手のたけじいを登録してみます。

pycharmのデバッガーで、nameとjobが渡ってきているのを確認できました!

swaggerのレスポンスもちゃんとidが999で出力されましたね!

では、バリデーションの確認をしたいと思います。
nameが必須なので、nameをnullで登録してみます。

実行結果は以下のようになります。

http statusは422 で、response bodyにエラー内容が出力されます。

{
  "detail": [
    {
      "loc": [
        "body",
        "name"
      ],
      "msg": "none is not an allowed value",
      "type": "type_error.none.not_allowed"
    }
  ]
}


必須だけではなく、文字列長の制限もいれてみましょう。jobのほうに最大文字長10を設定してみましょう。

class PersonCreate(BaseModel):
    """
    人間作成クラス
    """
    name: str = Field(title="名前")
    """名前"""
    job: str | None = Field(title="職業", max_length=10)
    """職業"""

Field属性のmax_lengthに最大文字数を定義するだけです。
swaggerを見てみましょう。

maxLength:10 が表示されていますね。

実際に11文字いれてみましょう。

12345678901と11文字いれたら、maxLengthのエラーになりました!!

複雑なvalidationを行うときは
@validator(プロパティ名) をつけて実装します。

ためしに名前が「たけじい」の場合はエラーにしてしまいましょう。値はvalue引数で受け取れます。
エラーにするにはValueError(エラーメッセージ)をraiseし、エラーがなければvalueをそのままreturnします。

from pydantic import BaseModel, Field, validator


class PersonCreate(BaseModel):
    """
    人間作成クラス
    """
    name: str = Field(title="名前")
    """名前"""
    job: str | None = Field(title="職業", max_length=10)
    """職業"""

    @validator("name")
    def validate_name(cls, value):
        if value == "たけじい":
            raise ValueError("たけじいは入力できません")
        return value

これで試してみます。

ちゃんとバリデーションエラーになりました!(ちょっと悲しい・・・・


以上、FastAPIとPydanticを連携して、入出力関連の定義やバリデーションをスマートに実装できることが
実感できたと思います。

今後は、FastAPIのデータベース連携(SQLAlchemy) について記事を書きたいと思っています。

最後に

クロスマートでは絶賛エンジニア(バックエンド・フロントエンド)募集中です。

このお話を読んでちょっとでも気になった方がいたら

「話を聞きに行きたい」を押してみてください!

www.wantedly.com