【Python开发】FastAPI 06:处理错误

某些情况下,有必要向客户端(包括前端浏览器、其他应用程序、物联网设备等)返回错误提示,以便客户端能够了解错误的类型,从而做出应对。

目录

1 默认处理

1.1 错误介绍

1.2 使用 HTTPException

2 自定义处理

2.1 自定义响应头

2.2 自定义异常处理器

2.3 覆盖默认异常处理器

① 覆盖请求验证异常

② 覆盖 HTTPException 错误处理器

③ 使用 RequestValidationError 的请求体


📌源码地址:

https://gitee.com/yinyuu/fast-api_study_yinyu

1 默认处理

1.1 错误介绍

以下场景需要向客户端返回错误提示:

  • 客户端没有执行操作的权限
  • 客户端没有访问资源的权限
  • 客户端要访问的项目不存在
  • 等等 ...

遇到这些情况时,通常要返回 4XX(400 至 499)HTTP 状态码。 4XX 状态码与表示请求成功的 2XX(200 至 299) HTTP 状态码类似。

4XX 状态码表示客户端发生的错误,大家都知道「404 Not Found」错误吧~

1.2 使用 HTTPException

向客户端返回 HTTP 错误响应,可以使用 HTTPException

📌代码

from fastapi import FastAPI, HTTPException #导入 HTTPException

app = FastAPI()

items = {"yinyu": "The Foo Wrestlers"}

@app.get("/items/{item_id}")
async def read_item(item_id: str):
    if item_id not in items:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item": items[item_id]}

HTTPException 是额外包含了和 API 有关的常规 Python 异常,因为是 Python 异常,所以不能 return,只能 raise

📌响应结果

本例中,客户端用 ID 请求的 item 不存在时,触发状态码为 404 的异常:

比如,此时请求为 127.0.0.1:8000/items/yinyuitem_id 为 「yinyu」)时,客户端会接收到 HTTP 状态码 - 200 及如下 JSON 响应结果:

{
    "item": "The Foo Wrestlers"
}

但如果客户端请求 127.0.0.1:8000/items/baritem_id bar」 不存在时),则会接收到 HTTP 状态码 - 404(「未找到」错误)及如下 JSON 响应结果:

{
    "detail": "Item not found"
}

触发 HTTPException 时,可以用参数 detail 传递任何能转换为 JSON 的值,不仅限于 str。 还支持传递 dictlist 等数据结构。FastAPI 能自动处理这些数据,并将之转换为 JSON

2 自定义处理

2.1 自定义响应头

一般情况下不会需要在代码中直接使用响应头,但是有时出于某些方面的安全需要,可能需要添加自定义响应头:

...

@app.get("/items-header/{item_id}")
async def read_item_header(item_id: str):
    if item_id not in items:
        raise HTTPException(
            status_code=404,
            detail="Item not found",
            headers={"X-Error": "There goes my error"} #添加自定义响应头
        )
    return {"item": items[item_id]}

也就是说,触发 HTTPException 时,响应头也会自定义返回~

2.2 自定义异常处理器

假设要触发的自定义异常叫作 SelfDefinedException, 且需要 FastAPI 实现全局处理该异常。

此时,可以用 @app.exception_handler() 添加自定义异常控制器:

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

class SelfDefinedException(Exception):
    def __init__(self, name: str):
        self.name = name

@app.exception_handler(SelfDefinedException)
async def defined_exception_handler(request: Request, exc: SelfDefinedException):
    return JSONResponse(
        status_code=418,
        content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
    )

@app.get("/self_defined/{name}")
async def read_defined(name: str):
    if name == "yolo":
        raise SelfDefinedException(name=name)
    return {"unicorn_name": name}

请求 /self_defined/yolo 时,路径操作会触发 SelfDefinedException,且该异常将会被 defined_exception_handler处理。

接收到的错误信息清晰明了,HTTP 状态码为 418JSON 内容如下:

{"message": "Oops! yolo did something. There goes a rainbow..."}

2.3 覆盖默认异常处理器

FastAPI 自带了一些默认异常处理器,触发 HTTPException 或请求无效数据时,这些处理器返回默认的 JSON 响应结果, 当然你也可以使用自定义处理器覆盖默认异常处理器。

① 覆盖请求验证异常

请求过程中,若请求包含无效数据则 FastAPI 内部会触发 RequestValidationError。 该异常内置了默认异常处理器,覆盖默认异常处理器时需要导入 RequestValidationError,并用 @app.excption_handler(RequestValidationError) 装饰异常处理器。

这样,异常处理器就可以接收 Request 与异常。

from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.responses import PlainTextResponse

app = FastAPI()

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
    return PlainTextResponse(str(exc), status_code=400)

@app.get("/items2/{item_id}")
async def read_item(item_id: int):
    if item_id == 3:
        raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
    return {"item_id": item_id}

访问 http://127.0.0.1:8000/items2/yinyu,可以看到以下文本格式的错误信息:

并且替换了以下默认 JSON 错误信息:

{
    "detail": [
        {
            "loc": [
                "path",
                "item_id"
            ],
            "msg": "value is not a valid integer",
            "type": "type_error.integer"
        }
    ]
}

② 覆盖 HTTPException 错误处理器

同理,也可以覆盖 HTTPException 处理器。

例如,只为错误返回纯文本响应,而不是返回 JSON 格式的内容:

from fastapi import FastAPI, HTTPException
from fastapi.responses import PlainTextResponse
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()

@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
    return PlainTextResponse(str(exc.detail), status_code=exc.status_code)

@app.get("/items3/{item_id}")
async def read_item(item_id: int):
    if item_id == 3:
        raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
    return {"item_id": item_id}

③ 使用 RequestValidationError 的请求体

RequestValidationError 包含其接收到的无效数据请求的 body 。

开发时,可以用这个请求体生成日志、调试错误,并返回给用户!!

from fastapi import FastAPI, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel

app = FastAPI()

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),
    )

class Item(BaseModel):
    title: str
    size: int

@app.post("/items4/")
async def create_item(item: Item):
    return item

现在试着发送一个无效的 item,例如:

{
  "title": "towel",
  "size": "XL"
}

收到的响应包含 body 信息,并说明数据是无效的:

{
  "detail": [
    {
      "loc": [
        "body",
        "size"
      ],
      "msg": "value is not a valid integer",
      "type": "type_error.integer"
    }
  ],
  "body": {
    "title": "towel",
    "size": "XL"
  }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尹煜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值