FastAPI 表单数据处理详解

举报
时光不写 发表于 2026/07/23 15:58:59 2026/07/23
【摘要】 详解 FastAPI 中表单数据的接收与处理,包括 Form 字段声明、文件与表单混合上传、数据校验、表单编码类型以及与 Pydantic 模型的配合使用。

在 Web 开发中,表单(Form)是最传统也是使用最广泛的客户端提交数据方式。无论是登录注册、搜索筛选,还是传统的 HTML 页面提交,表单数据都无处不在。FastAPI 通过 Form 提供了对 application/x-www-form-urlencodedmultipart/form-data 两种表单编码的原生支持。本文带你系统掌握 FastAPI 中的表单处理技巧。

基础表单字段

FastAPI 使用 Form 来声明表单字段,用法与 QueryPath 非常相似:

from fastapi import FastAPI, Form

app = FastAPI()


@app.post("/login/")
async def login(
    username: str = Form(..., description="用户名"),
    password: str = Form(..., description="密码"),
):
    return {"username": username, "status": "ok"}

几个关键点:

  • Form(...) 中的 ...(Ellipsis)表示该字段是必填的,等同于 Form() 不带默认值。
  • 如果给默认值(如 Form("guest")),则字段变为可选。
  • 表单字段支持 min_lengthmax_lengthregex 等校验参数,和 QueryPath 完全一致。
  • 使用 Form 时需要在路由装饰器中不要指定 response_model 为 Pydantic 模型时混合使用 body 参数,否则会冲突(下文详解)。

可选字段与默认值

对于可选字段,提供默认值即可:

@app.post("/search/")
async def search(
    keyword: str = Form("", description="搜索关键词"),
    page: int = Form(1, ge=1, description="页码"),
    page_size: int = Form(20, ge=1, le=100, description="每页数量"),
    sort: str | None = Form(None, description="排序字段"),
):
    return {
        "keyword": keyword,
        "page": page,
        "page_size": page_size,
        "sort": sort,
    }

这里用 str | None = Form(None) 表示该字段可以为空——客户端不传该字段时,值为 None

多选值与列表字段

HTML 表单中常有 select multiple 或同名的 checkbox 组,传入的是一个值列表。FastAPI 通过 typing.List 来接收:

from typing import List


@app.post("/subscribe/")
async def subscribe(
    email: str = Form(...),
    topics: List[str] = Form([], description="订阅主题"),
):
    return {"email": email, "topics": topics}

客户端发送 topics=python&topics=fastapi&topics=ai 时,服务端收到的 topics 就是 ["python", "fastapi", "ai"]

文件与表单混合上传

最常见的高级场景是同时上传文件和其他表单字段(如用户头像 + 昵称)。此时需要用到 FileForm 的组合:

from fastapi import FastAPI, File, Form, UploadFile

app = FastAPI()


@app.post("/upload-avatar/")
async def upload_avatar(
    nickname: str = Form(..., description="用户昵称"),
    avatar: UploadFile = File(..., description="头像文件"),
):
    file_content = await avatar.read()
    file_size = len(file_content)
    return {
        "nickname": nickname,
        "filename": avatar.filename,
        "size": file_size,
    }

注意: 当路由中同时存在 FormFile 参数时,请求的 Content-Type 自动变为 multipart/form-data。如果只有 Form 参数,默认使用 application/x-www-form-urlencoded

表单数据与 Pydantic 模型

你可能会想把 Form 字段用 Pydantic 模型封装起来。但需要注意的是,表单数据和 JSON 请求体的处理路径不同——Pydantic 默认从 JSON body 读取数据,而 Form 从表单字段读取,二者不可直接互换。

推荐的模式是用一个独立的依赖函数来组织表单字段:

from fastapi import Depends


class RegistrationForm:
    def __init__(
        self,
        username: str = Form(..., min_length=3, max_length=50),
        email: str = Form(..., regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"),
        password: str = Form(..., min_length=8),
        confirm_password: str = Form(..., min_length=8),
    ):
        self.username = username
        self.email = email
        self.password = password
        self.confirm_password = confirm_password


@app.post("/register/")
async def register(form: RegistrationForm = Depends()):
    if form.password != form.confirm_password:
        return {"error": "两次密码不一致"}
    # 后续注册逻辑...
    return {"username": form.username, "email": form.email, "status": "registered"}

这样表单字段的定义被封装在 RegistrationForm 类中,路由函数通过 Depends() 获取实例,代码清晰且可复用。

手动处理表单的原始请求

在某些特殊场景下(如需要访问表单的键值对而不预先定义结构),可以直接从 Request 对象获取原始表单数据:

from fastapi import Request


@app.post("/raw-form/")
async def raw_form(request: Request):
    form_data = await request.form()
    result = {}
    for key, value in form_data.items():
        result[key] = value
    return {"fields": result, "count": len(result)}

request.form() 返回一个 FormData 对象(类似字典),可以迭代所有字段,每个值都是 FormDatastrUploadFile 类型。

表单编码的类型选择

FastAPI 根据路由参数自动推断合适的 Content-Type:

路由参数类型 自动 Content-Type
仅有 Form application/x-www-form-urlencoded
File / UploadFile multipart/form-data
Form + File 混合 multipart/form-data

对于简单的文本字段(如登录表单),x-www-form-urlencoded 足够且开销更小;涉及文件上传或大量数据时,multipart/form-data 是唯一选择。

小结

FastAPI 的 Form 让表单数据处理和查询参数一样直观——声明式字段定义、内建校验、自动文档生成。总结几个要点:

  1. Form(...) 声明必填字段,Form(default) 声明可选字段。
  2. List[str] 类型注解接收同名多值字段。
  3. Form + File 混合时走 multipart/form-data,仅 Form 时走 x-www-form-urlencoded
  4. 表单字段可用 Depends + 类封装实现结构化组织。
  5. 可通过 request.form() 获取原始表单数据做自由解析。

掌握这些,你就能从容应对各种表单提交场景了。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。