在 FastAPI 中编写接口时,响应模型(Response Model) 和 状态码(Status Code) 是控制接口返回内容的两个核心机制。合理使用它们可以让 API 返回的数据既安全又规范。
一、response_model 基本用法
response_model 是整个 FastAPI 响应体系的核心参数。它告诉 FastAPI 使用指定的 Pydantic 模型来对返回数据进行序列化、校验和过滤。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| from fastapi import FastAPI from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserCreate(BaseModel): username: str password: str email: EmailStr
class UserResponse(BaseModel): id: int username: str email: EmailStr
@app.post("/users", response_model=UserResponse) async def create_user(user: UserCreate): saved_user = { "id": 1, "username": user.username, "password": user.password, "email": user.email, } return saved_user
|
关键点:即使函数内部返回了 password 字段,response_model=UserResponse 也会将其过滤掉,保证敏感数据不泄露。
二、字段筛选:include 与 exclude
除了定义专门的响应模型,FastAPI 还支持动态控制返回字段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| from fastapi import FastAPI from pydantic import BaseModel
app = FastAPI()
class UserOut(BaseModel): id: int username: str email: str phone: str is_active: bool created_at: str
@app.get("/users/public/{user_id}", response_model=UserOut, response_model_include={"id", "username"}) async def get_public_user(user_id: int): return { "id": user_id, "username": "zhangsan", "email": "zhangsan@example.com", "phone": "13800000000", "is_active": True, "created_at": "2026-07-01" }
@app.get("/users/internal/{user_id}", response_model=UserOut, response_model_exclude={"phone"}) async def get_internal_user(user_id: int): return { "id": user_id, "username": "zhangsan", "email": "zhangsan@example.com", "phone": "13800000000", "is_active": True, "created_at": "2026-07-01" }
|
还有一个实用参数 response_model_exclude_unset=True,它只返回实际被赋值的字段,未被显式赋值的字段会被忽略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class ItemUpdate(BaseModel): name: str | None = None price: float | None = None desc: str | None = None
@app.patch("/items/{item_id}", response_model=ItemUpdate, response_model_exclude_unset=True) async def update_item(item_id: int, item: ItemUpdate): update_data = item.model_dump(exclude_unset=True) return update_data
|
三、状态码控制
FastAPI 通过 status_code 参数控制响应状态码,推荐使用 starlette.status 提供的内置枚举,代码更可读。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| from fastapi import FastAPI from starlette import status
app = FastAPI()
@app.post("/items", status_code=status.HTTP_201_CREATED) async def create_item(name: str): return {"id": 1, "name": name}
@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_item(item_id: int): return None
|
也可以在路由函数内部动态返回状态码,使用 Response 对象:
1 2 3 4 5 6 7 8 9 10 11 12 13
| from fastapi import FastAPI, Response from starlette import status
@app.post("/login") async def login(username: str, password: str): if username == "admin" and password == "123456": return {"token": "xxx"} return Response( content='{"detail": "Invalid credentials"}', status_code=status.HTTP_401_UNAUTHORIZED, media_type="application/json" )
|
四、响应模型继承与组合
通过模型继承可以避免重复定义,同时保持类型安全。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class UserBase(BaseModel): username: str email: str
class UserPublic(UserBase): """公开信息""" id: int
class UserPrivate(UserBase): """内部信息(含敏感字段)""" id: int phone: str is_active: bool
@app.get("/users/{user_id}", response_model=UserPublic) async def get_user(user_id: int): user = {"id": 1, "username": "zhangsan", "email": "z@example.com", "phone": "13800000000", "is_active": True} return user
|
五、返回不同类型的响应
一个接口可能需要返回不同结构的响应,使用 Union 配合 response_model:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| from typing import Union from fastapi import FastAPI, HTTPException
class ItemResponse(BaseModel): id: int name: str price: float
class ErrorResponse(BaseModel): detail: str
@app.get("/items/{item_id}", response_model=Union[ItemResponse, ErrorResponse], responses={ 404: {"model": ErrorResponse, "description": "Item not found"}, 200: {"model": ItemResponse, "description": "Success"}, }) async def get_item(item_id: int): if item_id == 0: return ErrorResponse(detail="Item not found") return ItemResponse(id=item_id, name="Widget", price=9.99)
|
六、实战:统一响应格式封装
实际项目中通常需要统一的响应格式,通过响应模型封装很容易实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| from typing import Generic, TypeVar from pydantic import BaseModel
T = TypeVar("T")
class APIResponse(BaseModel, Generic[T]): """统一响应格式""" code: int = 200 message: str = "success" data: T | None = None
@app.get("/users/{user_id}", response_model=APIResponse[UserPublic]) async def get_user(user_id: int): user = query_user_by_id(user_id) return APIResponse(data=user)
@app.get("/users/{user_id}", response_model=APIResponse[None]) async def get_user_not_found(user_id: int): return APIResponse(code=404, message="User not found", data=None)
|
总结
response_model 是安全返回数据的核心,自动过滤未声明的字段,建议每个接口都显式指定
response_model_include/exclude 提供动态字段控制,但优先使用专门的响应模型类,更清晰
status_code 使用 starlette.status 枚举,避免魔法数字
- 通过模型继承和泛型封装可以构建统一规范的响应体系,大幅提升 API 的可维护性