概要
リクエストでいうと以下のようなリクエストが来ることを想定して作ります
curl -XPOST -H "Action: Hoge" localhost:8000/submit/ -H "content-type: application/x-www-form-urlencoded" -d "data[0][lang]=ruby&data[0][framework]=sinatra&data[1][lang]=python&data[1][framework]=fastapi"
data はリストになっていてその中に lang, framework というデータ構造を持つオブジェクトが入っているイメージです
環境
- Python 3.11.3
- fastapi 0.100.1
- pydantic 2.1.1
サンプルコード
from fastapi import Depends, FastAPI, Request
from pydantic import BaseModel, field_validator
app = FastAPI()
# リスト内のデータ構造を管理するクラス
class Profile(BaseModel):
lang: str
framework: str
@field_validator("lang")
@classmethod
def validate_lang(cls, v: str):
if v not in ["ruby", "python"]:
raise ValueError()
return v
# リクエストのリスト部分を管理するクラス
class RequestData(BaseModel):
profile: list[Profile]
# 独自のフォームパーサ
async def parse_form_data(request: Request) -> RequestData:
form_data = await request.form()
print(form_data)
request_data: list[Profile] = []
# とりあえず 100 リストまで対応
for i in range(100):
# lang が含まれているか確認、0から始まっていないもしくは指定のデータ構造がない場合などは終了
lang = form_data.get(f"data[{i}][lang]")
if lang is None:
break
# framework が含まれているか確認、0から始まっていないもしくは指定のデータ構造がない場合などは終了
framework = form_data.get(f"data[{i}][framework]")
if framework is None:
break
# データ構造の作成
profile = Profile(lang=str(lang), framework=str(framework))
# リストへの追加
request_data.append(profile)
# Depends の返り値を指定
return RequestData(profile=request_data)
@app.post("/submit/")
async def submit_form(form_data: RequestData = Depends(parse_form_data)):
# Depends で parse_form_data を指定することでリクエストデータを独自でパースする
return form_data
動作確認
curl -XPOST -H "Action: Hoge" localhost:8000/submit/ -H "content-type: application/x-www-form-urlencoded" -d "data[0][lang]=ruby&data[0][framework]=sinatra&data[1][lang]=python&data[1][framework]=fastapi"
は通ります
curl -XPOST -H "Action: Hoge" localhost:8000/submit/ -H "content-type: application/x-www-form-urlencoded" -d "data[0][lang]=ruby&data[0][framework]=sinatra&data[1][lang]=python3&data[1][framework]=fastapi"
はエラーになります
最後に
そもそもこういったリクエストは form ではなく json で受け取るべきだと思います
ファイルを送信したくどうしても form を使わざるを得ない場合は仕方ないかなと思います