FastAPI 檔案上傳 (Request Files)

FastAPI 處理檔案上傳非常簡單且強大。它基於 multipart/form-data 編碼。

同樣地,你需要先安裝 python-multipartpip install python-multipart

使用 File

你可以使用 bytes 來讀取檔案內容。這適合小檔案,因為整個檔案內容會被讀入記憶體。

from typing import Annotated
from fastapi import FastAPI, File

app = FastAPI()

@app.post("/files/")
async def create_file(file: Annotated[bytes, File()]):
    return {"file_size": len(file)}

但通常我們更推薦使用 UploadFile

使用 UploadFile (推薦)

UploadFile 有以下優點:

  1. 使用 SpooledTemporaryFile:檔案會暫存在記憶體中,直到超過大小限制 (預設 1MB) 才會寫入磁碟。這意味著它可以處理大檔案而不會吃光記憶體。
  2. 檔案資訊:你可以取得檔名 (filename)、Content-Type (content_type) 等資訊。
  3. 非同步介面:它提供 read(), write(), seek(), close() 等類似 Python 檔案操作的 async 方法。
from fastapi import FastAPI, File, UploadFile

app = FastAPI()

@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    return {"filename": file.filename, "content_type": file.content_type}
UploadFile 本身不需要像 File(...) 那樣宣告預設值 (雖然你可以寫 file: UploadFile = File(...) 來添加額外驗證),直接寫型別 UploadFile,FastAPI 就知道這是要接收檔案。

讀取與儲存檔案

這是一個將上傳的檔案存到伺服器的範例:

import shutil
from pathlib import Path

@app.post("/save-file/")
async def save_file(file: UploadFile):
    # 定義儲存路徑
    destination_file_path = Path("uploaded_files") / file.filename

    # 確保目錄存在
    destination_file_path.parent.mkdir(parents=True, exist_ok=True)

    # 將上傳的檔案內容寫入目的檔案
    with open(destination_file_path, "wb") as buffer:
        shutil.copyfileobj(file.file, buffer)

    return {"filename": file.filename, "saved_at": str(destination_file_path)}

選填檔案上傳 (Optional File Upload)

如果你想讓檔案上傳變成選填的,只需將型別設為 Optional (或型別聯集 | None) 並給預設值 None

from typing import Optional

@app.post("/optional-upload/")
async def update_profile(file: Optional[UploadFile] = None):
    if not file:
        return {"message": "No file sent"}
    return {"filename": file.filename}

多個檔案上傳

要接收多個檔案,可以使用 List[UploadFile]

from typing import Annotated

@app.post("/upload-multiple/")
async def upload_multiple_files(files: Annotated[list[UploadFile], File()]):
    return {"filenames": [file.filename for file in files]}

HTML 表單範例:

<form action="/upload-multiple/" enctype="multipart/form-data" method="post">
  <input name="files" type="file" multiple />
  <input type="submit" />
</form>

File + Form 混用

你可以同時接收檔案 (File) 和表單欄位 (Form)。

from fastapi import File, Form, UploadFile

@app.post("/files-and-form/")
async def handle_file_and_data(
    file: UploadFile,
    notes: str = Form(...)
):
    return {
        "filename": file.filename,
        "notes": notes,
        "file_content_type": file.content_type
    }

這在實務上非常常見,例如「上傳頭像並更新使用者名稱」。

總結

  • 使用 File 讀取小檔案 (bytes)。
  • 使用 UploadFile 處理一般檔案與大檔案 (推薦)。
  • 支援多檔案上傳 (List[UploadFile])。
  • 可以與 Form 資料同時使用。