2023年8月8日火曜日

fastapi で AWS のような Action 名ベースの API をどう構築するか考える (バリデーション編)

fastapi で AWS のような Action 名ベースの API をどう構築するか考える (バリデーション編)

概要

クエリストリングで来る値をどうやってルーティングしバリデーションするか考えます

環境

  • Python 3.11.3
  • fastapi 0.100.1
  • pypdantic 2.1.1

方針

  • ルーティングのパラメータでは Action のみバリデーションする
  • ルーティング内で Request オブジェクトを参照して Action 名に応じた各種バリデーションをコールする (mode_validate)

上記方針の理由としては BaseModel に Request オブジェクトが渡せないためです

サンプルコード

from enum import Enum

from fastapi import Depends, FastAPI, Query, Request
from fastapi.datastructures import QueryParams
from pydantic import BaseModel, Field, field_validator

app = FastAPI()


# 各種アクション名を管理する列挙型のクラス
class ActionName(Enum):
    CREATE_INSTANCE = "CreateInstance"


# CreateInstance(例) という名前のアクションのバリデーションを管理するモデル
class CreateInstance(BaseModel):
    instance_name: str = Field(alias="InstanceName")

    @field_validator("instance_name")
    @classmethod
    def validate_instance_name(cls, v: str) -> str:
        if v != "ins01":
            raise ValueError()
        return v


# 起点となる Action 名のバリデーションを管理するモデル
class Action(BaseModel):
    action: str = Field(Query(alias="Action"))

    @field_validator("action")
    @classmethod
    def validate_action(cls, v: str) -> str:
        # ここでは許可するバリデーション名など基本的なバリデーションのみを行う
        if v not in [ActionName.CREATE_INSTANCE.value]:
            raise ValueError()
        return v

    # アクション名に応じて各種バリデーションをコールする
    def validate(self, query_params: QueryParams):
        if self.action == ActionName.CREATE_INSTANCE.value:
            CreateInstance.model_validate(query_params)


@app.get("/")
async def root(request: Request, action: Action = Depends()):
    # ルーティングの引数で Request オブジェクトを参照できるのでこれを使って各種アクションのバリデーションをコールする
    action.validate(request.query_params)
    return action

解説

クエリストリングはキャメルケースで来ることを想定しているので alias プロパティを使ってクエリストリング側のキー名を指定します

BaseModel を使って起点となる Action 名を管理するクラスと Action 名に基づく各種属性を管理するクラスを作成します
モデルクラスは必ず Field を使って定義しましょう、そうしないとうまくバリデーションメソッドがコールされません

Action クラスでは単純な名前のチェックだけを行います
細かいバリデーションはそれぞれで定義したアクション用のクラスを使います

pydantic は V2 を使っているので model_validate や field_validator を使っています

最後に

とりあえずこれをベースに更にデータベースとの連携やバリデーション用のオブジェクトの受け渡しなども考えてみたいと思います

参考サイト

0 件のコメント:

コメントを投稿