FastAPI 表单数据

在 FastAPI 中,默认情况下它期望客户端发送的是 JSON 格式的请求体。如果你需要接收 HTML 表单提交的数据(application/x-www-form-urlencodedmultipart/form-data),或者需要处理文件上传,你需要使用 FastAPI 提供的 FormFileUploadFile 依赖。

下面我将从环境准备基础表单文件上传进阶技巧,为你系统梳理 FastAPI 处理表单数据的完整指南。


⚠️ 第一步:安装必备依赖(新手最常踩的坑)

FastAPI 本身不包含解析表单数据的库。在处理任何表单或文件上传之前,必须安装 python-multipart

纯文本
pip install python-multipart

如果不安装,当你尝试访问带有 FormFile 参数的路由时,FastAPI 会抛出 ImportError


二、 处理普通表单字段 (Form)

使用 Form 可以接收 HTML 表单中的文本字段。它的用法与 QueryPath 非常相似。

纯文本
from fastapi import FastAPI, Form

app = FastAPI()

@app.post("/login/")
def login(
    username: str = Form(..., description="用户名"), 
    password: str = Form(..., description="密码"),
    remember_me: bool = Form(default=False)
):
    # Form(...) 中的 ... 表示该字段是必填项 (Required)
    return {
        "username": username,
        "password_length": len(password),
        "remember_me": remember_me
    }

💡 提示:在 Swagger UI (/docs) 中,FastAPI 会自动将此接口渲染为一个真实的 HTML 表单,你可以直接在浏览器中填写并测试提交。


三、 处理文件上传 (FileUploadFile)

处理文件时,FastAPI 提供了两个选项:FileUploadFile强烈推荐使用 UploadFile

特性File (bytes)UploadFile (推荐 🌟)
返回值整个文件的字节流 (bytes)类似 Python 文件对象的接口
内存占用整个文件加载到内存,大文件会导致 OOM存储在临时文件中,内存占用极小
异步支持否 (同步读取) (await file.read())
元数据包含 filename, content_type, size

1. 单文件上传示例

纯文本
from fastapi import FastAPI, File, UploadFile
import shutil

app = FastAPI()

@app.post("/upload/")
async def upload_file(file: UploadFile = File(..., description="请选择要上传的文件")):
    # 1. 获取文件元数据
    filename = file.filename
    content_type = file.content_type

    # 2. 读取文件内容 (异步操作,不会阻塞事件循环)
    contents = await file.read()

    # 3. 保存文件到本地 (示例)
    # with open(f"uploads/{filename}", "wb") as buffer:
    #     shutil.copyfileobj(file.file, buffer)

    return {
        "filename": filename,
        "content_type": content_type,
        "size_bytes": len(contents)
    }

2. 多文件上传示例

只需将类型提示改为列表 list[UploadFile] 即可。

纯文本
from typing import List

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

四、 混合场景:表单字段 + 文件上传

在实际业务中(如用户修改资料、发布文章),通常需要同时提交文本字段和文件。FastAPI 完美支持这种混合模式,它会自动将请求识别为 multipart/form-data

纯文本
from fastapi import FastAPI, Form, File, UploadFile

app = FastAPI()

@app.post("/create-post/")
async def create_post(
    title: str = Form(..., max_length=100),
    content: str = Form(...),
    category: str = Form(default="general"),
    cover_image: UploadFile = File(default=None) # 文件是可选的
):
    post_data = {
        "title": title,
        "content": content,
        "category": category,
    }

    if cover_image:
        post_data["cover_image_name"] = cover_image.filename
        # 可以在这里处理图片保存逻辑

    return {"status": "success", "data": post_data}

五、 进阶技巧:如何让表单数据支持 Pydantic 模型?

痛点:FastAPI 原生不支持直接使用 Pydantic BaseModel 来接收表单数据(BaseModel 默认只解析 JSON)。如果你有一个包含 10 个字段的表单,写 10 个 Form(...) 会非常繁琐。

解决方案:使用自定义依赖,手动将表单数据映射到 Pydantic 模型。

纯文本
from fastapi import FastAPI, Form, Depends
from pydantic import BaseModel, Field

# 1. 定义你的 Pydantic 模型 (和接收 JSON 时一样)
class UserRegistration(BaseModel):
    username: str = Field(..., min_length=3)
    email: str
    age: int | None = None

# 2. 创建一个依赖函数,负责从 Form 中提取数据并构建模型
def parse_user_form(
    username: str = Form(...),
    email: str = Form(...),
    age: int | None = Form(default=None)
) -> UserRegistration:
    # 将表单数据组装成 Pydantic 模型,享受自动验证
    return UserRegistration(username=username, email=email, age=age)

# 3. 在路由中使用
@app.post("/register-form/")
def register_user(user: UserRegistration = Depends(parse_user_form)):
    # 此时 user 已经是一个验证通过的 UserRegistration 对象
    return {"message": f"用户 {user.username} 注册成功", "email": user.email}

(注:对于更复杂的项目,也可以使用第三方库如 fastapi-formpydantic-form 来简化这个过程)


六、 最佳实践与避坑指南

  1. 始终使用 async def 处理文件UploadFile.read(), .write(), .seek() 等方法都是异步的。使用 async def 可以让 FastAPI 在处理大文件上传时,依然能够响应其他客户端的请求,保持高并发性能。
  2. 限制文件大小:为了防止恶意上传超大文件耗尽服务器内存或磁盘,建议在 Nginx 层面或代码层面限制大小。
纯文本
   MAX_FILE_SIZE = 5 * 1024 * 1024  # 5MB

   @app.post("/upload/")
   async def upload(file: UploadFile = File(...)):
       contents = await file.read()
       if len(contents) > MAX_FILE_SIZE:
           raise HTTPException(status_code=413, detail="文件过大")
       # ...
  1. 验证文件类型:不要只信任 file.content_type(客户端可以伪造),最好检查文件扩展名或使用 python-magic 库检查文件的真实 MIME 类型。
  2. 清理临时文件UploadFile 会在请求结束后自动关闭,但如果你的逻辑中创建了临时文件,请确保在 finally 块或依赖项的 yield 中清理它们。

总结:表单处理速查表

需求推荐用法示例
接收普通文本表单str = Form(...)username: str = Form(...)
接收单个文件UploadFile = File(...)file: UploadFile = File(...)
接收多个文件list[UploadFile] = File(...)files: list[UploadFile] = File(...)
可选的表单字段赋予默认值bio: str = Form(default="")
优雅处理复杂表单自定义 Depends 映射到 Pydanticuser: User = Depends(parse_form)

你现在是否有一个具体的表单上传需求(例如:如何接收前端传来的 Base64 图片,或者如何结合 SQLAlchemy 保存带有图片的记录)?告诉我,我可以为你提供针对性的代码模板!