前提
FastAPIは通常はただのdictやPydanticで定義したモデルをもとにレスポンスをJSONに変換して返却するが、他の形式のレスポンスを返す方法も存在する。
例えば、GET
リクエストで形式を指定してJSONなら通常通りJSONを返却し、Excelならxlsx形式のレスポンスを返却するようなエンドポイントがあるとする。この場合、既にファイルが作成済みであればFileResponse
を利用して直接レスポンスを返却することはドキュメントにある通り簡単にできるが、問題はリクエストを受けてからOpenPyXLでxlsx形式データを作成しそのデータを保存せずにそのまま返却するにはどうすればいいかである。
確認したバージョンは以下の通り。
- FastAPI: 0.68.1
- OpenPyXL: 2.6.2
方法
通常、OpenPyXLでは次のようにワークブックを作って、ファイル名を指定して保存する。
workbook = Workbook()
# ...ここでシートをつくって色々書きこむ...
workbook.save("xlsxファイル名")
save()
メソッドにはファイルパスを指定するのが普通だが、ここにfile-likeオブジェクトを指定するとそのオブジェクトに対してデータを書き込むことができる。
そこで、次のようにBytesIO
オブジェクトを指定してデータを保存する。
xlsx = BytesIO()
workbook = Workbook()
# ...ここでシートをつくって色々書きこむ...
workbook.save(xlsx)
一方、FastAPIではStreamingResponse
を使ってfile-likeオブジェクトをレスポンスに返却することができるため、組み合わせると次のようになる。
from urllib.parse import quote
# ...中略...
@app.get("/hoge", response_class=StreamingResponse)
def get_hoge_as_xlsx():
xlsx = BytesIO()
workbook = Workbook()
# ...ここでシートをつくって色々書きこむ...
workbook.save(xlsx)
xlsx.seek(0) # !!! 注意 !!!
filename = quote("ほげほげ.xlsx")
return StreamingResponse(
content=xlsx,
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
StreamingResponseは現在のシーク位置からレスポンスを作成するので、OpenPyXLでsaveした後は先頭に戻す(seek(0))必要がある。