在 FastAPI 中处理表单数据(Form Data,通常用于 HTML <form> 提交,内容类型为 application/x-www-form-urlencoded 或 multipart/form-data)非常直观。
但在此之前,有一个极其重要的前置条件:
⚠️ 0. 必须安装 python-multipart
FastAPI 处理表单数据依赖于第三方库 python-multipart。如果你没有安装它,尝试接收表单数据时会报错。
pip install python-multipart1. 基础用法:接收表单字段
使用 fastapi.Form 来声明表单字段。强烈建议使用 Annotated(FastAPI 0.95.0+ 推荐写法),以保持类型提示的清晰。
from typing import Annotated
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login/")
async def login(
username: Annotated[str, Form()],
password: Annotated[str, Form(min_length=6, max_length=50)] # 支持验证规则
):
return {"message": f"Login successful for {username}"}注意:表单字段可以包含描述、默认值和验证规则(与 Query/Path 参数类似)。
2. 处理文件上传
当表单包含文件时(multipart/form-data),需要使用 File 和 UploadFile。
File:继承自Form,用于声明这是一个文件类型的表单字段。UploadFile:FastAPI 提供的文件对象,支持异步读取,且不会将整个文件加载到内存中(适合大文件)。
from typing import Annotated
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/upload/")
async def upload_file(
file: Annotated[UploadFile, File(description="A file to be uploaded")]
):
# 读取文件内容 (异步)
content = await file.read()
return {
"filename": file.filename,
"content_type": file.content_type,
"size_bytes": len(content)
}上传多个文件
只需将类型提示改为列表 list[UploadFile]:
from typing import Annotated, List
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/upload-multiple/")
async def upload_multiple_files(
files: Annotated[List[UploadFile], File()]
):
return {
"filenames": [file.filename for file in files],
"count": len(files)
each file in files]
}3. 表单字段 + 文件混合提交
你可以同时在一个路径操作函数中声明 Form 和 File,FastAPI 会自动处理 multipart/form-data 请求。
from typing import Annotated
from fastapi import FastAPI, Form, File, UploadFile
app = FastAPI()
@app.post("/profile/")
async def create_profile(
name: Annotated[str, Form()],
age: Annotated[int, Form()],
avatar: Annotated[UploadFile, File()]
):
return {
"name": name,
"age": age,
"avatar_filename": avatar.filename
}4. 进阶:如何优雅地结合 Pydantic 模型?(常见痛点)
FastAPI 原生不支持直接将 Pydantic 模型用于 Form 数据(Body 默认解析为 JSON)。如果你有一个复杂的表单,手动写一堆 Form() 会很繁琐。
最佳解决方案:结合“依赖注入”(正如我们上一个话题讨论的)。我们可以写一个依赖函数,将表单字段映射到 Pydantic 模型。
from typing import Annotated
from pydantic import BaseModel, Field
from fastapi import FastAPI, Form, Depends, HTTPException
app = FastAPI()
# 1. 定义你的 Pydantic 模型
class UserCreateForm(BaseModel):
username: str = Field(min_length=3)
email: str
age: int = Field(ge=18, description="Must be 18 or older")
# 2. 创建一个依赖函数,负责接收 Form 并组装成 Pydantic 模型
def user_form_dependency(
username: Annotated[str, Form()],
email: Annotated[str, Form()],
age: Annotated[int, Form()]
) -> UserCreateForm:
# 这里可以进行额外的自定义逻辑,或者直接返回模型实例
# Pydantic 会自动进行数据验证
return UserCreateForm(username=username, email=email, age=age)
# 3. 在路径操作中使用 Depends 注入
@app.post("/users/")
async def create_user(
user: Annotated[UserCreateForm, Depends(user_form_dependency)]
):
# 此时 user 已经是一个验证通过的 UserCreateForm 实例
return {
"message": "User created",
"data": user.model_dump() # Pydantic V2 的字典转换方法
}这种方法既保留了 Pydantic 强大的验证和文档生成能力,又完美支持了 HTML 表单提交。
5. 可选的表单字段
如果某个表单字段不是必填的,只需提供默认值(通常是 None),并指定类型。
from typing import Annotated
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/items/")
async def create_item(
name: Annotated[str, Form()],
description: Annotated[str | None, Form()] = None # 可选字段
):
return {"name": name, "description": description}⚠️ 重要注意事项
- 不能混用 JSON Body 和 Form:
在一个请求中,你不能同时期望接收 JSON Body (Body()) 和表单数据 (Form())。HTTP 协议的Content-Type只能是其中之一。如果你需要同时接收复杂对象和文件,必须全部使用Form/File(即multipart/form-data)。 - Swagger UI 测试:
FastAPI 的自动文档(/docs)完全支持表单和文件上传测试。对于UploadFile,Swagger UI 会自动渲染出一个文件选择按钮。 - 大文件处理:
始终使用UploadFile而不是直接读取bytes,因为UploadFile使用 Python 的SpooledTemporaryFile,在文件超过一定大小(默认 1MB)时会自动写入磁盘,防止内存溢出 (OOM)。
如果你有特定的表单场景(例如:动态表单字段、与数据库 ORM 结合保存表单数据),可以告诉我,我可以提供更具体的代码!