FastAPI 表单数据
FastAPI 表单数据(Form Data)完全指南
一、什么是表单数据?
表单数据 = application/x-www-form-urlencoded 或 multipart/form-data
常见于:
- 登录表单
- 搜索框
- 文件上传 + 文本字段
FastAPI 使用 Form() 和 UploadFile 处理。
二、安装依赖
pip install "fastapi[all]"
# 包含 python-multipart(处理表单和文件)
三、基本表单:Form()
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login/")
def login(username: str = Form(...), password: str = Form(...)):
return {"username": username}
...表示必填字段
四、完整登录表单示例
from fastapi import FastAPI, Form, HTTPException
from fastapi.responses import HTMLResponse
from pydantic import BaseModel
app = FastAPI()
# 模拟用户数据库
fake_users_db = {
"alice": {"username": "alice", "password": "wonderland"},
"bob": {"username": "bob", "password": "builder"}
}
# HTML 登录页面
@app.get("/", response_class=HTMLResponse)
def login_page():
return """
<html>
<head><title>登录</title></head>
<body>
<h2>用户登录</h2>
<form method="post" action="/login/">
<p>
<label>用户名: <input type="text" name="username" required /></label>
</p>
<p>
<label>密码: <input type="password" name="password" required /></label>
</p>
<p>
<button type="submit">登录</button>
</p>
</form>
</body>
</html>
"""
# 登录处理
@app.post("/login/")
def login(username: str = Form(...), password: str = Form(...)):
user = fake_users_db.get(username)
if not user or user["password"] != password:
raise HTTPException(status_code=400, detail="用户名或密码错误")
return {"message": f"欢迎, {username}!", "token": "fake-jwt-token"}
五、表单 + 文件上传(混合)
from fastapi import File, UploadFile, Form
from typing import List
@app.post("/upload/")
def upload_file(
file: UploadFile = File(...),
description: str = Form(...),
tags: List[str] = Form([])
):
return {
"filename": file.filename,
"description": description,
"tags": tags
}
HTML 表单示例:
<form method="post" action="/upload/" enctype="multipart/form-data">
<p>
<label>文件: <input type="file" name="file" required /></label>
</p>
<p>
<label>描述: <input type="text" name="description" /></label>
</p>
<p>
<label>标签: <input type="text" name="tags" /></label>
<small>多个标签用逗号分隔</small>
</p>
<button type="submit">上传</button>
</form>
注意:
enctype="multipart/form-data"必须有!
六、多个文件 + 表单字段
@app.post("/upload-multiple/")
def upload_files(
files: List[UploadFile] = File(...),
category: str = Form(...),
public: bool = Form(False)
):
return {
"filenames": [f.filename for f in files],
"category": category,
"public": public
}
七、Pydantic 模型 + 表单(推荐)
from pydantic import BaseModel, EmailStr, Field
from typing import List
class UploadForm(BaseModel):
title: str = Field(..., min_length=1, max_length=100)
email: EmailStr
tags: List[str] = []
@app.post("/submit-form/")
def submit_form(
form_data: UploadForm = Form(...),
file: UploadFile = File(...)
):
return {
"title": form_data.title,
"email": form_data.email,
"tags": form_data.tags,
"filename": file.filename
}
注意:
Form(...)替代Body(...)
八、保存上传文件到磁盘
import shutil
import os
UPLOAD_DIR = "./uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)
@app.post("/save-file/")
async def save_file(
file: UploadFile = File(...),
note: str = Form("")
):
file_path = f"{UPLOAD_DIR}/{file.filename}"
with open(file_path, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
return {
"saved": file_path,
"note": note,
"size": os.path.getsize(file_path)
}
九、文件类型限制
from fastapi import HTTPException
ALLOWED_TYPES = {"image/png", "image/jpeg", "application/pdf"}
@app.post("/upload-image/")
async def upload_image(file: UploadFile = File(...)):
if file.content_type not in ALLOWED_TYPES:
raise HTTPException(
status_code=400,
detail=f"不支持的文件类型: {file.content_type}"
)
# 保存文件...
return {"filename": file.filename}
十、文件大小限制
from fastapi import Request
@app.middleware("http")
async def limit_upload_size(request: Request, call_next):
if request.method == "POST":
if "multipart/form-data" in request.headers.get("content-type", ""):
# 限制 10MB
if int(request.headers.get("content-length", 0)) > 10 * 1024 * 1024:
return JSONResponse(
status_code=413,
content={"detail": "文件太大,最大 10MB"}
)
return await call_next(request)
十一、表单验证错误(422)
class LoginForm(BaseModel):
username: str = Field(..., min_length=3)
password: str = Field(..., min_length=6)
@app.post("/login-model/")
def login(form: LoginForm = Form(...)):
return form
错误响应:
{
"detail": [
{
"loc": ["body", "username"],
"msg": "ensure this value has at least 3 characters",
"type": "value_error.any_str.min_length"
}
]
}
十二、完整实战:用户注册(表单 + 头像上传)
from fastapi import FastAPI, Form, File, UploadFile, HTTPException
from fastapi.responses import HTMLResponse, JSONResponse
from pydantic import BaseModel, EmailStr, Field
import shutil
import os
app = FastAPI()
UPLOAD_DIR = "./avatars"
os.makedirs(UPLOAD_DIR, exist_ok=True)
class RegisterForm(BaseModel):
username: str = Field(..., min_length=3, max_length=20)
email: EmailStr
password: str = Field(..., min_length=8)
bio: str = Field("", max_length=500)
@app.get("/register/", response_class=HTMLResponse)
def register_page():
return """
<form method="post" action="/register/" enctype="multipart/form-data">
<p><label>用户名: <input name="username" required /></label></p>
<p><label>邮箱: <input type="email" name="email" required /></label></p>
<p><label>密码: <input type="password" name="password" required /></label></p>
<p><label>简介: <textarea name="bio"></textarea></label></p>
<p><label>头像: <input type="file" name="avatar" accept="image/*" /></label></p>
<button type="submit">注册</button>
</form>
"""
@app.post("/register/")
async def register(
username: str = Form(...),
email: EmailStr = Form(...),
password: str = Form(...),
bio: str = Form(""),
avatar: UploadFile = File(None)
):
# 模拟验证
if len(password) < 8:
raise HTTPException(400, "密码至少8位")
# 保存头像
avatar_url = None
if avatar and avatar.filename:
ext = avatar.filename.split(".")[-1]
avatar_path = f"{UPLOAD_DIR}/{username}.{ext}"
with open(avatar_path, "wb") as f:
shutil.copyfileobj(avatar.file, f)
avatar_url = f"/avatars/{username}.{ext}"
return {
"message": "注册成功",
"username": username,
"email": email,
"avatar": avatar_url
}
# 提供头像静态文件
from fastapi.staticfiles import StaticFiles
app.mount("/avatars", StaticFiles(directory=UPLOAD_DIR), name="avatars")
十三、cURL 测试表单
# 登录
curl -X POST "http://127.0.0.1:8000/login/" \
-d "username=alice" \
-d "password=wonderland"
# 上传文件 + 表单
curl -X POST "http://127.0.0.1:8000/upload/" \
-F "file=@photo.jpg" \
-F "description=我的头像" \
-F "tags=portrait,selfie"
总结:表单数据速查表
| 场景 | 写法 |
|---|---|
| 文本字段 | username: str = Form(...) |
| 可选字段 | note: str = Form(None) |
| 多选/数组 | tags: List[str] = Form([]) |
| 文件上传 | file: UploadFile = File(...) |
| 多文件 | files: List[UploadFile] = File(...) |
| Pydantic + Form | form: MyForm = Form(...) |
| HTML 表单 | enctype="multipart/form-data" |
| 保存文件 | shutil.copyfileobj() |
| 静态文件 | app.mount("/static", StaticFiles(...)) |
下一步学习
| 主题 | 关键词 |
|---|---|
| OAuth2 登录表单 | oauth2 |
| 文件分片上传 | chunk |
| 表单 + JWT 认证 | jwt form |
| 富文本编辑器上传 | tinymce |
| 表单验证前端 | zod |
现在就运行你的表单,打开浏览器测试登录和上传吧!
需要我生成一个 完整用户中心模板(注册、登录、头像上传、资料修改)吗?
回复 表单模板 立刻获取!