FastAPI 教程

FastAPI 表单数据

在 FastAPI 中处理表单数据(Form Data,通常用于 HTML <form> 提交,内容类型为 application/x-www-form-urlencodedmultipart/form-data)非常直观。

但在此之前,有一个极其重要的前置条件:

⚠️ 0. 必须安装 python-multipart

FastAPI 处理表单数据依赖于第三方库 python-multipart。如果你没有安装它,尝试接收表单数据时会报错。

纯文本
plaintext
pip install python-multipart

1. 基础用法:接收表单字段

使用 fastapi.Form 来声明表单字段。强烈建议使用 Annotated(FastAPI 0.95.0+ 推荐写法),以保持类型提示的清晰。

纯文本
plaintext
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),需要使用 FileUploadFile

  • File:继承自 Form,用于声明这是一个文件类型的表单字段。
  • UploadFile:FastAPI 提供的文件对象,支持异步读取,且不会将整个文件加载到内存中(适合大文件)。
纯文本
plaintext
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]

纯文本
plaintext
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. 表单字段 + 文件混合提交

你可以同时在一个路径操作函数中声明 FormFile,FastAPI 会自动处理 multipart/form-data 请求。

纯文本
plaintext
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 模型。

纯文本
plaintext
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),并指定类型。

纯文本
plaintext
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}

⚠️ 重要注意事项

  1. 不能混用 JSON Body 和 Form
    在一个请求中,你不能同时期望接收 JSON Body (Body()) 和表单数据 (Form())。HTTP 协议的 Content-Type 只能是其中之一。如果你需要同时接收复杂对象和文件,必须全部使用 Form / File(即 multipart/form-data)。
  2. Swagger UI 测试
    FastAPI 的自动文档(/docs)完全支持表单和文件上传测试。对于 UploadFile,Swagger UI 会自动渲染出一个文件选择按钮。
  3. 大文件处理
    始终使用 UploadFile 而不是直接读取 bytes,因为 UploadFile 使用 Python 的 SpooledTemporaryFile,在文件超过一定大小(默认 1MB)时会自动写入磁盘,防止内存溢出 (OOM)。

如果你有特定的表单场景(例如:动态表单字段、与数据库 ORM 结合保存表单数据),可以告诉我,我可以提供更具体的代码!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注