Skip to content

第 3.6 章:错误处理与数据验证

学习时间: 50 分钟


1. 错误处理:“飞船的应急护盾”

即使是最完美的飞船,也可能发生意想不到的情况:

  • 来自任务控制中心的错误指令: 客户端发送了不正确的数据。
  • 与模块失去联系: 数据库中未找到资源。
  • 反应堆故障: 服务器内部错误。

正确的错误处理是一套“应急护盾”系统。它不会让飞船解体,而是向任务控制中心发送清晰的信号,告知哪里出了问题。

💡 太空类比:

一个优秀的机载电脑不会只向任务控制中心发送“紧急情况!”的信号,而是会发送一份结构化的报告:

{
  "error_code": "ENGINE_OVERHEAT",
  "message": "2号发动机温度超过正常值",
  "suggested_action": "启动冷却系统"
}
这使得地球上的工程师能够快速理解问题并采取措施。


2. Pydantic 数据验证:内置的“机载电脑”

我们已经见识过 Pydantic 的魔力。如果你尝试创建一个包含错误数据类型(例如,将 launch_year 设为字符串)的飞船,FastAPI 会自动返回 422 Unprocessable Entity 错误,并附带详细说明哪个字段以及为何未通过验证。

POST /spaceships 请求示例:

{
  "name": "X-Wing",
  "type": "Истребитель",
  "launch_year": "давно",  // <-- 类型错误!
  "status": "В строю"
}

FastAPI 自动响应:

{
  "detail": [
    {
      "loc": [
        "body",
        "launch_year"
      ],
      "msg": "value is not a valid integer",
      "type": "type_error.integer"
    }
  ]
}
这非常强大!你无需编写类型检查代码——FastAPI 和 Pydantic 会为你完成。


3. 处理“资源未找到”:HTTPException 异常

我们已经在 CRUD 操作中使用了它。HTTPException 是 FastAPI 中止请求执行并立即向客户端返回错误响应的标准方式。

回顾 GET /spaceships/{ship_id} 中的代码:

# main.py
from fastapi import FastAPI, HTTPException # 确保已导入 HTTPException

# ...

@app.get("/spaceships/{ship_id}", response_model=Spaceship, tags=["航天器"])
def get_spaceship(ship_id: int):
    ship = db_spaceships.get(ship_id)
    if not ship:
        # 如果飞船未找到,则“抛出” 404 异常
        raise HTTPException(status_code=404, detail=f"ID 为 {ship_id} 的航天器未找到")
    return ship

  • raise HTTPException(...):此调用会停止函数执行。
  • status_code=404:设置 HTTP 响应状态码。
  • detail:将通过 JSON 响应体发送给客户端的消息。

4. 自定义验证器:“启动前的特殊检查”

如果我们想添加自己的、更复杂的业务逻辑怎么办?例如,禁止发射名为“死星”的飞船。

为此,Pydantic 提供了一个强大的工具——验证器

步骤 1:在 SpaceshipCreate 模型中添加验证器

# main.py
from pydantic import BaseModel, Field, validator

class SpaceshipCreate(BaseModel):
    name: str = Field(..., min_length=3, max_length=50)
    type: str
    launch_year: int = Field(..., gt=1950)
    status: str

    @validator('name')
    def name_must_not_be_forbidden(cls, v):
        """检查飞船名称是否在禁止列表中。"""
        if 'Death Star' in v:
            raise ValueError('根据帝国法令,禁止使用“死星”之类的名称!')
        return v.title() # 同时将名称转换为首字母大写

  • @validator('name'):一个将此函数“绑定”到 name 字段的装饰器。
  • cls, v:该方法接收类本身(cls)和字段的值(v)。
  • raise ValueError(...):如果验证未通过,我们将引发标准的 Python 异常。FastAPI 会捕获它并将其转换为一个漂亮的 422 错误。
  • return v.title():如果一切正常,我们必须返回一个值。我们甚至可以在运行时修改它(例如,转换为标准格式)。

步骤 2:测试 重启 uvicorn 并尝试通过 /docs 创建一个带有禁用名称的飞船。你将收到一个带有你的自定义消息的 422 错误!


5. 全局错误处理:“站点的应急协议”

有时需要捕获意外错误(例如,连接到真实数据库失败)并返回统一、标准化的响应格式。

为此,可以使用 @app.exception_handler 装饰器。

示例:捕获所有 ValueError 错误

# main.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

# ...

@app.exception_handler(ValueError)
async def value_error_exception_handler(request: Request, exc: ValueError):
    """
    所有 ValueError 错误的全局处理程序,
    用于返回标准化的 JSON。
    """
    return JSONResponse(
        status_code=400,
        content={"message": f"数据错误:{str(exc)}"},
    )

  • @app.exception_handler(ValueError):告诉 FastAPI,此函数应处理所有之前未被捕获的 ValueError
  • async def ...:异常处理程序必须是异步的(async)。
  • JSONResponse:允许完全控制响应体和状态。

现在,当我们的自定义验证器触发时,响应将采用我们定义的更友好的格式。


巩固测验

1. 如果客户端发送了错误类型的数据(字符串而不是数字),FastAPI 将自动返回状态码...

2. `raise HTTPException(status_code=404)` 是为了...

3. Pydantic 中的 `@validator('field_name')` 装饰器用于:

4. 如果数据正确,Pydantic 中的验证器函数应该做什么?

5. `@app.exception_handler()` 允许...


🚀 本章总结:

你已为你的 API 飞船安装了强大的防护系统和应急协议。现在它能够:

  • 🛡️ 借助 Pydantic 自动抵御“不正确数据”的攻击。
  • 🚨 通过 HTTPException 妥善报告资源缺失(404 Not Found)。
  • ⚙️ 使用自定义验证器进行“特殊检查”。
  • 🧯 全局捕获意外故障并给出标准化响应。

你的“超光速引擎”不仅速度快,而且极其可靠!

📌 检查:

  • 尝试创建一个名为“死星”的飞船,并确认你收到了带有你的自定义消息的 400 错误。
  • 尝试请求 GET /spaceships/999,并确认你收到了 404 错误。
  • 尝试发送一个 POST 请求,将 launch_year 设为字符串,并确认你收到了 422 错误。

⚠️ 如果有错误:

  • 确保所有必需的模块(HTTPExceptionvalidatorRequestJSONResponse)都已导入。
  • 检查 @validator@app.exception_handler 装饰器是否没有拼写错误。

恭喜你完成第 3 章! 你已经从零开始构建并启动了一个强大、有文档记录且受保护的 FastAPI API。你已准备好执行真正的太空任务。