FastAPIのGetメソッドパラメータをBaseModelで受け取れるようにしてみる

2024/02/04に公開

はじめに

FastAPI で API 開発をしていて Get メソッドを叩く時にhttp://lcalhost:8080/items?name=Aのようにクエリパラメータに対応した Get メソッドはよく出でくると思います
ただ、検索ページの API のようにクエリパラメータが5個 10 個と必要になった時にどうしていますか
メソッドの引数に5個 10 個と指定するのはあまりに不自然だと思いましたので現状これが良さそうだなというのをこちらに記載していきたい思います

前提

商品のリストを Get メソッドで検索する、という想定です
検索項目としては

  • 商品の ID
  • 商品名
  • 商品値段
  • 商品の在庫数
  • 商品が現在販売中かどうか

データはダミーで用意した JSON を用意しました(ChatGPT で適当に用意してもらいます)

items.json
{
  "items": [
    {
      "id": 1,
      "name": "商品A",
      "price": 1000.0,
      "stock": 20,
      "is_on_sale": true
    },
    {
      "id": 2,
      "name": "商品B",
      "price": 1500.0,
      "stock": 15,
      "is_on_sale": false
    },
    ...(以下続く)
  ]
}

商品の BaseModel を作成

JSON のデータに倣って Python 側で商品データを扱うデータクラスItemを作成します

class Item(BaseModel):
    id: int
    name: str
    price: float
    stock: int
    is_on_sale: bool

クエリパラメータの BaseModel を作成

この記事の主役である Get メソッドのクエリパラメータを受け取るBaseModelです
Optionalにしてデフォルト値をNoneにしているのは ID だけで検索、名前と値段だけで検索されることを想定して何も指定されない項目をNoneにする対応です

class ItemSearchParameter (BaseModel):
    id: Optional[int] = None
    name: Optional[str] = None
    min_price: Optional[float] = None
    max_price: Optional[float] = None
    min_stock: Optional[int] = None
    max_stock: Optional[int] = None
    is_on_sale: Optional[bool] = None

BaseModel を受け取れる Get メソッドを作成

先ほど用意してJSONデータを読みとってlist[Item]として全て返却するという一端シンプルな実装です

@app.get('/items', response_model=list[Item])
async def get_items(parameter: ItemSearchParameter = Depends(ItemSearchParameter)) -> list[Item]:
    with open('items.json') as f:
        json_data = json.load(f)
        return json_data['items']

重要なのはget_itemsの引数の部分で

get_items(parameter: ItemSearchParameter = Depends(ItemSearchParameter))

Depends に callback を渡すことでその API が呼ばれたときに FastAPI 側で callback(ここでは ItemSearchParameter)を呼び出してくれるので以下のように解釈してくれるわけです。

get_items(id: Optional[int] = None, name: Optional[str] = None, ..., is_on_sale: Optional[bool] = None)

Depends については公式ドキュメントに説明があるので参考にしてみてください
https://fastapi.tiangolo.com/tutorial/dependencies/

検索機能追加

検索のコア処理(雑です、、)
Item1つに対してパラメータと一致していなければ None を返却するようにしています

def filter_item(item: Item, parameter: ItemSearchParameter) -> Union[Item, None]:
    if parameter.id is not None and parameter.id != item.id:
        return None
    if parameter.name is not None and parameter.name != item.name:
        return None
    if parameter.min_price is not None and parameter.min_price > item.price:
        return None
    if parameter.max_price is not None and parameter.max_price < item.price:
        return None
    if parameter.min_stock is not None and parameter.min_stock > item.stock:
        return None
    if parameter.max_stock is not None and parameter.max_stock < item.stock:
        return None
    if parameter.is_on_sale is not None and parameter.is_on_sale != item.is_on_sale:
        return
    return item

先ほどの Get メソッドに検索した結果を返却する処理を追加します

@app.get('/items', response_model=list[Item])
async def get_items(parameter: ItemSearchParameter = Depends(ItemSearchParameter)) -> list[Item]:
    with open('items.json') as f:
        json_data = json.load(f)
        res: list[Item] = []
        for x in json_data['items']:
            item = filter_item(Item(**x), parameter)
            if not item:
                continue
            res.append(item)
        return res

これでhttp://lcalhost:8080/items?name=商品Aなどで API を叩くと商品 A のみが返ってくるはずなので検索機能が完成しました

まとめ

FastAPI で開発していて沢山のクエリパラメータをしたい時、Get メソッドに 1 つずつ指定はしたくない、ただ POST にもしたくない、と思った時にこちらの方法は有効かと思いました。意外と情報が少なかったのでこちらを読んで参考にして頂けたらと思います

GitHubで編集を提案

Discussion