04 响应模型与状态码
本章目标:学会控制接口返回什么数据、什么结构、什么状态码。
1. response_model:声明返回结构
上一章说到,返回用户信息时不能带密码。response_model 就是干这个的:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UserCreate(BaseModel):
username: str
email: str
password: str
class UserOut(BaseModel):
id: int
username: str
email: str
# 模拟数据库
fake_db: dict[int, dict] = {}
next_id = 1
@app.post("/users", response_model=UserOut)
def create_user(user: UserCreate):
global next_id
saved = {"id": next_id, **user.model_dump()} # 包含 password
fake_db[next_id] = saved
next_id += 1
return saved # 虽然返回了含 password 的字典……
关键点:函数返回的字典里明明有 password,但因为声明了 response_model=UserOut,FastAPI 会按 UserOut 过滤输出,实际响应是:
{"id": 1, "username": "xiaoming", "email": "a@b.com"}
response_model 的三大作用:
- 过滤字段:模型里没有的字段不会返回(防止泄露敏感数据)
- 校验输出:返回的数据不符合模型会直接报错,帮你尽早发现 bug
- 生成文档:
/docs里能看到这个接口的响应结构
也可以直接用返回值类型注解代替 response_model,效果相同:
@app.post("/users")def create_user(user: UserCreate) -> UserOut: ...两种写法选一种即可,本教程统一用
response_model=,因为后面接数据库时它更灵活。
2. 返回列表
@app.get("/users", response_model=list[UserOut])
def list_users():
return list(fake_db.values())
3. 自定义状态码
HTTP 语义上,「创建成功」应该返回 201 而不是 200:
from fastapi import status
@app.post("/users", response_model=UserOut, status_code=status.HTTP_201_CREATED)
def create_user(user: UserCreate):
...
status 模块里是所有状态码的常量(status.HTTP_404_NOT_FOUND 等),比直接写数字 201 可读性好。
常见约定:
| 操作 | 状态码 |
|---|---|
| 查询成功 | 200(默认) |
| 创建成功 | 201 |
| 删除成功(无返回内容) | 204 |
| 客户端参数错误 | 400 / 422 |
| 未登录 | 401 |
| 无权限 | 403 |
| 资源不存在 | 404 |
删除接口的典型写法(204 表示成功但无内容,函数应返回 None):
@app.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_user(user_id: int):
fake_db.pop(user_id, None)
# 不 return 任何东西
4. 完整 CRUD 示例(内存版)
把前几章的知识串起来,写一个完整的「用户管理」——数据先存在字典里,第四部分会无缝换成数据库:
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field
app = FastAPI(title="用户管理 API(内存版)")
class UserCreate(BaseModel):
username: str = Field(min_length=3, max_length=20)
email: str
password: str = Field(min_length=6)
class UserUpdate(BaseModel):
# 更新时所有字段都可选:只改传了的字段
username: str | None = Field(default=None, min_length=3, max_length=20)
email: str | None = None
class UserOut(BaseModel):
id: int
username: str
email: str
fake_db: dict[int, dict] = {}
next_id = 1
@app.post("/users", response_model=UserOut, status_code=status.HTTP_201_CREATED)
def create_user(user: UserCreate):
global next_id
saved = {"id": next_id, **user.model_dump()}
fake_db[next_id] = saved
next_id += 1
return saved
@app.get("/users", response_model=list[UserOut])
def list_users():
return list(fake_db.values())
@app.get("/users/{user_id}", response_model=UserOut)
def get_user(user_id: int):
if user_id not in fake_db:
raise HTTPException(status_code=404, detail="用户不存在")
return fake_db[user_id]
@app.patch("/users/{user_id}", response_model=UserOut)
def update_user(user_id: int, user: UserUpdate):
if user_id not in fake_db:
raise HTTPException(status_code=404, detail="用户不存在")
# exclude_unset=True:只取调用方真正传了的字段
updates = user.model_dump(exclude_unset=True)
fake_db[user_id].update(updates)
return fake_db[user_id]
@app.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_user(user_id: int):
if user_id not in fake_db:
raise HTTPException(status_code=404, detail="用户不存在")
del fake_db[user_id]
请把这段代码完整跑一遍,在 /docs 里按顺序操作:创建两个用户 → 查列表 → 查单个 → 改名字 → 删除 → 再查(应该 404)。
这里出现的 HTTPException 用于返回错误,第 06 章会详细讲;PATCH + exclude_unset 的部分更新模式是实际项目的标准做法。
5. 补充:直接返回自定义 Response(了解即可)
绝大多数情况返回字典/模型就够了。特殊场景可用:
from fastapi.responses import PlainTextResponse, RedirectResponse, FileResponse
@app.get("/txt", response_class=PlainTextResponse)
def get_txt():
return "纯文本内容"
@app.get("/old-url")
def old():
return RedirectResponse("/new-url") # 重定向
@app.get("/download")
def download():
return FileResponse("report.pdf", filename="报告.pdf") # 文件下载
本章小结
response_model声明返回结构:过滤敏感字段、校验输出、生成文档status_code=status.HTTP_201_CREATED自定义状态码;删除用 204- 更新接口用「全可选模型 +
model_dump(exclude_unset=True)」实现部分更新 - 本章的内存版 CRUD 是后面数据库版的骨架,务必亲手跑通
下一章:05-表单和文件上传