2024年1月11日木曜日

Flask で pydantic を使う方法を考える

Flask で pydantic を使う方法を考える

概要

過去にflask-pydanticを使った方法を紹介しました
しかし flask-pydantic は V2 に対応していません
今回は flask-pydantic なしで Flask + pydantic を連携する方法を考えます

環境

  • macOS 11.7.10
  • Python 3.11.6
    • flask 3.0.0
    • pydantic 2.5.3

案1: ルーティング内の先頭でモデルを使用する

from flask import Flask, jsonify, request
from pydantic import BaseModel, field_validator

app = Flask(__name__)


class User(BaseModel):
    name: str

    @field_validator("name")
    @classmethod
    def validate_name(cls, v):
        if v != "hawk":
            raise ValueError()
        return v


@app.route("/", methods=["POST"])
def test():
    data = request.json
    if data is None:
        raise ValueError()
    user = User(**data)
    return jsonify(user.model_dump())

メリット

  • 簡単、明瞭、特に何も考えず先頭で変換するだけなので楽

デメリット

  • 変換する部分の実装を忘れそう
  • request.json から変換しているが他にも query や path などをモデルに変換する部分の考慮しなければならない

案2: デコレータを使い引数で受け取る

from flask import Flask, jsonify, request
from pydantic import BaseModel, field_validator

app = Flask(__name__)


class User(BaseModel):
    name: str

    @field_validator("name")
    @classmethod
    def validate_name(cls, v):
        if v != "hawk":
            raise ValueError()
        return v


def validate(func):
    def wrapper(*args, **kwargs):
        data = request.json
        if data is None:
            raise ValueError()
        user = User(**data)
        return func(user, *args, **kwargs)

    return wrapper


@app.route("/", methods=["POST"])
@validate
def test(user: User):
    return jsonify(user.model_dump())

メリット

  • ルーティング側での変換が不要になるのでコードがきれいになる
  • 引数で受け取れてかつ検証済みのオブジェクトなので安全に扱える

デメリット

  • デコレータが肥大化しないように気をつける必要がある
    • モデルごとにデコレータを追加する必要がないようにする必要がある
  • request.json から変換しているが他にも query や path などをモデルに変換する部分の考慮しなければならない

最後に

頑張れば何とかなりそうだけどコードが複雑化しないように工夫する必要がありそうです
そのあたりを吸収してくれてくれているのが flask-pydantic なので最悪ライブラリを自分でメンテするのもありなのかもしれません

0 件のコメント:

コメントを投稿