【FastAPI】日本語を含むファイル名のファイルをダウンロードする際のエラーと対処【備忘録】

お疲れ様です。

今回はFastAPIのファイルダウンロードで日本語のファイル名をダウンロードする際の注意点についてまとめました。

ちょうど1年前くらいにFastAPIでファイルダウンロードをするAPIを作成していました。 その際はファイル名を半角英数字のみで作成していたため気づかなかったのですが、この時のコードだと日本語を含むファイル名のファイルをダウンロードしようとするとエラーが起こります。

fallpoke-tech.hatenadiary.jp

ソースコード

今回もエラー再現のためにデモページとAPIを作成しました。
以下GitHubに残してありますので必要があればご確認ください。

github.com

実行してページを開くとこんな感じです。 demo_page

エラー内容と対処方法

エラーはFastAPIで作成したAPIエンドポイントのレスポンスの内容によるものです。
以下エラーが起こるパターンとエラーを解消したパターンを記載します。

エラーが発生する書き方

import io
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()


@app.get("/download")
def download_from_df() -> StreamingResponse:
    """DataFrameを指定ファイル形式でダウンロードするAPI
       (ローカルにファイル保存せず、データをファイル化して返す)
    """
    stream = io.StringIO()
    sample_df.to_csv(stream, encoding='utf-8', index=False)
    stream.seek(0)

    filename = "日本語ファイルサンプル.csv"
    media_type = "text/csv"
    
    return StreamingResponse(
        content=stream, 
        media_type=media_type,
        headers={"Content-Disposition": f"attachment; filename={filename}"}
    )

ページ上のボタンを押してダウンロードしようとするとこのように「Internal Server Error」で表示されます。

error_app

Python側の表示を見ると下記のエラーが出ています。 これはStreamingResponseの処理中に起こっており、ファイル名に日本語を含む場合に再現します。
非ASCII文字が含まれていることが原因のようです。

UnicodeEncodeError: 'latin-1' codec can't encode characters in position 21-31: ordinal not in range(256)

error_fastapi

正しい書き方

import io
from urllib.parse import quote # 追加
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()


@app.get("/download")
def download_from_df() -> StreamingResponse:
    """DataFrameを指定ファイル形式でダウンロードするAPI
       (ローカルにファイル保存せず、データをファイル化して返す)
    """
    stream = io.StringIO()
    sample_df.to_csv(stream, encoding='utf-8', index=False)
    stream.seek(0)

    filename = "日本語ファイルサンプル.csv"
    media_type = "text/csv"
    
    return StreamingResponse(
        content=stream, 
        media_type=media_type,
        headers={"Content-Disposition": f"attachment; filename*=UTF-8''{quote(filename)}"} # ここを修正
    )

urllib.parse.quote()を使用します。 こちらを適用させることで日本語を含む文字列(非ASCII文字を含む文字列)をURLエンコーディングしてファイル名に設定しています。

実際に実行するとこんな感じ。
日本語の部分が%から始まる特殊な文字に置き換えられています。

urllib.parse.quote

これで正常にダウンロードできます。実際にダウンロードすると変換する前の元の日本語ファイル名なっていることがわかります。

download

参考

下記を参考にしました。併せてご参照ください。
ありがとうございました。

qiita.com