3、FastAPI核心基础:路径操作、HTTP方法、请求与响应模型、状态码与异常处理
FastAPI 之所以能在短时间内成为 Python 后端开发的主流选择,核心在于它将现代类型系统与 Web 协议规范进行了深度整合。本章将围绕构建 API 的四个基础支柱展开:路径操作(如何定义 URL 端点)、HTTP 方法(如何表达语义动作)、请求与响应模型(如何用 Pydantic 保证数据契约)以及状态码与异常处理(如何让错误信息标准化)。理解这四者的协同关系,是写出健壮、可维护、高性能 API 的前提。
3.1 路径操作与HTTP方法:定义API的骨架
核心原理
路径操作(Path Operation)是 FastAPI 处理请求的基本单元。它由三部分组成:路径(Path)、HTTP 方法(Method)和操作函数(Operation Function)。FastAPI 通过装饰器将 URL 路径与 Python 函数绑定,当客户端发起匹配的请求时,框架自动调用对应函数并返回结果。HTTP 方法定义了请求的语义:GET 用于获取资源,POST 用于创建资源,PUT 用于整体更新,PATCH 用于部分更新,DELETE 用于删除资源。
FastAPI 的路径参数使用花括号语法声明,例如 /items/{item_id}。框架会自动从 URL 中提取该参数,并根据类型注解进行转换。路径参数默认是字符串类型,但可以声明为 int、float 等,FastAPI 会自动完成类型转换和校验。
操作流程与判断方法
- 定义路径:使用装饰器
@app.get()、@app.post()等指定 HTTP 方法和 URL 路径。 - 声明参数:在函数签名中声明路径参数、查询参数或请求体参数。
- 设置响应:通过
response_model参数定义返回数据的结构。 - 指定状态码:使用
status_code参数设置默认成功状态码。
from fastapi import FastAPI, Path, Query
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(
item_id: int = Path(..., ge=1, description="物品ID"),
q: str | None = Query(None, max_length=50)
):
return {"item_id": item_id, "q": q}
案例拆解:商品查询接口
假设我们要构建一个电商 API,需要支持按 ID 查询商品详情。初始需求是:客户端传入商品 ID,返回商品名称、价格和库存。约束条件包括:ID 必须为正整数,价格保留两位小数,库存不能为负数。
| 参数 | 类型 | 约束 | 说明 |
|---|---|---|---|
| item_id | int | ge=1 | 商品唯一标识 |
| verbose | bool | 默认 False | 是否返回详细描述 |
实现时,我们使用 Path 和 Query 组件声明约束条件。FastAPI 会在请求到达时自动校验参数,如果校验失败,返回 422 错误并附带详细的错误信息。这避免了在业务代码中手动编写大量 if-else 判断。
常见误区与复盘
误区2:忽略 HTTP 方法的语义。例如,使用 GET 方法执行删除操作,虽然技术上可行,但违反了 RESTful 规范,容易造成安全隐患。
误区3:路径参数类型声明不完整。如果声明为
int 但客户端传入非数字,FastAPI 会返回 422 错误,而不是 500 错误。
3.2 请求与响应模型:用Pydantic定义数据契约
核心原理
Pydantic 是 FastAPI 的数据验证核心。通过定义继承自 BaseModel 的类,我们可以声明请求体的结构、字段类型和约束条件。FastAPI 在接收请求时,会自动将 JSON 数据解析为 Pydantic 模型实例,并执行类型校验。如果数据不合法,返回 422 错误并列出所有字段的错误信息。
响应模型(response_model)则用于控制返回数据的结构。它有两个关键作用:过滤敏感字段(如密码、内部 ID)和保证接口契约稳定。即使内部数据结构发生变化,只要响应模型不变,客户端就不会受影响。
操作流程
- 定义请求模型:创建 Pydantic 类,声明字段类型和默认值。
- 定义响应模型:创建另一个 Pydantic 类,只包含需要返回的字段。
- 在路径操作中使用:将请求模型作为函数参数,将响应模型传给
response_model。
from pydantic import BaseModel, Field
from typing import Optional
class ItemCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=50)
price: float = Field(..., gt=0, le=10000)
description: Optional[str] = None
class ItemResponse(BaseModel):
id: int
name: str
price: float
@app.post("/items/", response_model=ItemResponse, status_code=201)
async def create_item(item: ItemCreate):
# 模拟数据库操作
return {"id": 1, **item.dict()}
案例拆解:用户注册接口
考虑一个用户注册场景。客户端提交用户名、邮箱和密码。约束条件:用户名 3-20 个字符,邮箱格式合法,密码至少 8 位且包含数字和字母。响应中不能返回密码字段。
| 字段 | 请求模型 | 响应模型 |
|---|---|---|
| username | str, 3-20字符 | str |
| EmailStr | str | |
| password | str, 8-32位 | 不返回 |
| created_at | 不接收 | datetime |
通过分离请求和响应模型,我们确保了密码永远不会出现在响应中。即使开发者在业务逻辑中不小心返回了密码字段,FastAPI 也会根据响应模型自动过滤掉。
常见误区与复盘
注意:不要将数据库模型直接作为响应模型。数据库模型通常包含内部字段(如外键、时间戳),直接暴露会增加安全风险。
3.3 状态码与异常处理:让错误信息标准化
核心原理
HTTP 状态码是客户端理解请求结果的第一层信息。FastAPI 允许在路径操作中通过 status_code 参数设置默认状态码,也可以在运行时动态返回状态码。异常处理则通过 HTTPException 类实现,开发者可以指定状态码和错误详情。FastAPI 还支持自定义异常处理器,用于处理业务逻辑中的特定异常类型。
操作流程与判断方法
- 设置默认状态码:在装饰器中指定
status_code=201等。 - 抛出 HTTPException:在业务逻辑中检测到错误条件时,抛出包含状态码和详细信息的异常。
- 自定义异常处理器:使用
@app.exception_handler()注册自定义异常类型。
from fastapi import HTTPException, Request
from fastapi.responses import JSONResponse
class ItemNotFoundError(Exception):
pass
@app.exception_handler(ItemNotFoundError)
async def item_not_found_handler(request: Request, exc: ItemNotFoundError):
return JSONResponse(
status_code=404,
content={"message": "物品不存在", "detail": str(exc)}
)
@app.get("/items/{item_id}")
async def get_item(item_id: int):
if item_id > 100:
raise ItemNotFoundError(f"ID {item_id} 超出范围")
return {"id": item_id, "name": "示例物品"}
案例拆解:订单状态流转
假设订单系统需要处理三种异常:订单不存在(404)、订单已取消(400)、库存不足(409)。初始现象是:客户端请求取消订单时,如果订单状态为"已发货",系统返回 500 错误。分析发现,业务代码没有区分不同错误场景,统一抛出了通用异常。
| 场景 | 状态码 | 错误信息 |
|---|---|---|
| 订单 ID 不存在 | 404 | 订单不存在 |
| 订单已取消 | 400 | 订单已取消,无法重复操作 |
| 库存不足 | 409 | 库存不足,剩余 3 件 |
修复方案:定义三个自定义异常类,分别对应不同场景。在业务逻辑中根据订单状态抛出对应异常。通过自定义异常处理器统一返回 JSON 格式的错误信息。修复后,客户端可以根据状态码和错误信息做出精确处理,例如提示用户刷新库存或联系客服。
常见误区与复盘
误区2:错误信息过于详细。在生产环境中,避免返回堆栈跟踪或 SQL 语句,防止信息泄露。
误区3:忽略 422 校验错误。FastAPI 自动生成的 422 错误已经包含字段级错误信息,前端可以直接展示,无需额外处理。
3.4 综合实战:构建一个带完整错误处理的图书管理API
场景与约束
我们需要为图书馆构建一个图书管理 API,支持图书的增删改查。约束条件:图书 ID 为自动生成的整数,ISBN 必须唯一,价格在 0-1000 元之间。初始目标:所有接口返回标准化的 JSON 格式,错误信息包含错误码和可读消息。
实现步骤
- 定义数据模型:创建
BookCreate(请求模型)和BookResponse(响应模型)。 - 实现 CRUD 操作:使用内存字典模拟数据库。
- 添加异常处理:定义
BookNotFoundError和DuplicateISBNError。 - 注册异常处理器:为每个自定义异常返回对应的状态码和错误信息。
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, Field
from typing import Dict, Optional
app = FastAPI()
class BookCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=100)
isbn: str = Field(..., pattern="^[0-9]{13}$")
price: float = Field(..., gt=0, le=1000)
class BookResponse(BookCreate):
id: int
books: Dict[int, BookResponse] = {}
next_id = 1
@app.post("/books/", response_model=BookResponse, status_code=status.HTTP_201_CREATED)
async def create_book(book: BookCreate):
global next_id
# 检查 ISBN 唯一性
for existing in books.values():
if existing.isbn == book.isbn:
raise HTTPException(status_code=409, detail="ISBN 已存在")
new_book = BookResponse(id=next_id, **book.dict())
books[next_id] = new_book
next_id += 1
return new_book
@app.get("/books/{book_id}", response_model=BookResponse)
async def get_book(book_id: int):
if book_id not in books:
raise HTTPException(status_code=404, detail="图书不存在")
return books[book_id]
结果与复盘
通过上述实现,我们获得了三个关键收益:类型安全——所有输入输出都经过 Pydantic 校验;错误标准化——客户端可以通过状态码和 detail 字段快速定位问题;文档自动生成——FastAPI 自动生成 Swagger UI,包含所有接口的请求/响应示例。代价是需要额外编写模型类和异常处理代码,但相比手动解析 JSON 和编写 if-else 校验,维护成本显著降低。