FastAPIのGetメソッドパラメータをBaseModelで受け取れるようにしてみる
はじめに
FastAPI で API 開発をしていて Get メソッドを叩く時にhttp://lcalhost:8080/items?name=A
のようにクエリパラメータに対応した Get メソッドはよく出でくると思います
ただ、検索ページの API のようにクエリパラメータが5個 10 個と必要になった時にどうしていますか
メソッドの引数に5個 10 個と指定するのはあまりに不自然だと思いましたので現状これが良さそうだなというのをこちらに記載していきたい思います
前提
商品のリストを Get メソッドで検索する、という想定です
検索項目としては
- 商品の ID
- 商品名
- 商品値段
- 商品の在庫数
- 商品が現在販売中かどうか
データはダミーで用意した JSON を用意しました(ChatGPT で適当に用意してもらいます)
{
"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 については公式ドキュメントに説明があるので参考にしてみてください
検索機能追加
検索のコア処理(雑です、、)
Item
1つに対してパラメータと一致していなければ 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 にもしたくない、と思った時にこちらの方法は有効かと思いました。意外と情報が少なかったのでこちらを読んで参考にして頂けたらと思います
Discussion