アップロードされたファイルのデータをFastAPIで処理するコードのメモ

お疲れ様です。

以前FastAPIでファイルダウンロードをするAPIを作成しました。 fallpoke-tech.hatenadiary.jp

逆にフロントエンドからアップロードしたものをFastAPIでデータとして受け取るというのも使いどころが多そうに感じたので、今回はそのAPIを作成してみました。

作成コード(GitHub

github.com

前回同様main.pyを実行するとFastAPIから下記のようなUIが表示されます。
上側のUIが今回作成のファイルアップロードの部分になります。ファイル選択のボタンでエクスプローラーからファイルを選択し、アップロードボタンでFastAPIと通信します。
下側のダウンロードボタンは機能としては前回と同じものですが、アップロードしたファイルと同じものがダウンロードされます。

実装

フロントエンドからアップロードされたファイルはFastAPIのUploadFileクラスを使って取得します。 fastapi.tiangolo.com UploadFileクラスを使うとき場合によっては追加でpython-multipartをインストールしないといけないです。pip installで入れておきましょう。
ちなみに私の場合インストールせずに進めるとエラーが出ました。

バックエンド

import io

from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import HTMLResponse, StreamingResponse
import pandas as pd

app = FastAPI()

@app.post("/upload")
async def upload(upload_file: UploadFile = File(...)) -> str:
    """frontendからアップロードされたデータを読み込むAPI
    """
    # バイナリデータで読み込み
    upload_data = await upload_file.read()
    # csvをpandasで読み込む
    try:
        df = pd.read_csv(io.StringIO(upload_data.decode("utf-8")))
    except:
        # csvのファイル形式でない場合はエラー
        raise HTTPException(status_code=400, detail="File format is invalid.")
    
    return "OK"

引数のupload_fileで受け取り、readメソッドでバイナリデータを読み込む形になっています。読み込んだバイナリデータをio.StringIOで変換してメモリストリームからPandasのread_csvで読み込むという流れになっています。(前回のダウンロードではPandasのto_csvでメモリストリームに書き込み、そのデータをresponseしてダウンロードという流れだったのでほぼほぼ逆をやっているイメージですかね。)

フロントエンド

<h4>csvファイルをアップロード</h4>
<iframe name="hidden_frame" style="display:none;"></iframe>
<form 
  action="http://localhost:8000/upload/" method="post" 
  enctype="multipart/form-data"  target="hidden_frame"
>
  <input type="file" name="upload_file">
  <button type="submit">アップロード</button>
</form>

HTMLで書いたものですがフロンドエンドのコードも載せておきます。JavaScriptでfetchやaxiosを使用を使用して本格的に作成する場合は書き方が変わるかもしれません。
inputタグのname属性に指定する文字列は、バックエンドの引数の名前と一致させないといけません。ちょっとしたつまづきポイントかも。