python:Fastapi - 异常处理、路径配置及JSON编码器

发布一下 0 0

简单絮叨一些

上篇文章主要唠了表单和文件上传等功能,这次主要是唠下异常处理、路径操作配置和JSON兼容编码器等。异常处理就是针对

某些情况下,需要向客户端返回错误提示。路径操作配置就是路径操作装饰器支持多种配置参数。JSON兼容器是在某些情况

下,您可能需要将数据类型转换为与 JSON 兼容的类型等,然后存储起来。



处理错误

使用HTTPException

某些情况下,需要向客户端返回错误提示。

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

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

以上情况通常返回4xxHTTP状态码,4xx的状态码通常是指客户端发生的错误。

向客户端返回 HTTP 错误响应,可以使用 HTTPException,这也是fastapi默认。

from fastapi import FastAPIfrom fastapi import HTTPExceptionapp = FastAPI()items = {    "foo": "333_2222_666"}@app.get("/items/{item_id}")async def read_item(item_id: str):    """    初次使用HTTPException    :param item_id:    :return:    """    if item_id not in items:        raise HTTPException(status_code=404, detail="Item not found")    return {"item": items[item_id]}

注释信息:

  • raise HTTPException(status_code=404, detail="Item not found")是触发异常并抛出
  • detail="Item not found"参数 detail 传递任何能转换为 JSON 的值,不仅限于 str,还支持传递 dictlist 等数据结构

注意点:

  • Python 异常,不能 return,只能 raise
  • 使用return虽然不会报错,但牵扯到多层调用时,异常信息不一定会全部抛出,而raise是专门抛异常的关键字。

启动服务:

PS E:\git_code\python-code\fastapiProject> uvicorn handle_main:app --reload

请求接口:

GET http://127.0.0.1:8000/items/bee

请求结果:

{    "detail": "Item not found"}

因为bee不在items字典中,故触发异常并抛出异常信息。


添加自定义响应头

HTTP错误添加自定义信息头,暂时使用场景还没实操,可先了解下即可。

from fastapi import FastAPIfrom fastapi import HTTPExceptionapp = FastAPI()@app.get("/items-header/{item_id}")async def read_item_header(item_id: str):    """    自定义响应头    :param item_id:    :return:    """    if item_id not in items:        raise HTTPException(            status_code=404,            detail="Item not found",            headers={"H-Error": "This is an error message."}        )    return {"item": items[item_id]}

注释信息:

  • headers={"H-Error": "This is an error message."}添加自定义响应头

自定义异常处理器

自定义异常顾名思义就是自己去写一个异常处理器,不过也是继承python中异常的基类Exception

from fastapi import FastAPIfrom fastapi import Requestfrom fastapi.responses import JSONResponseclass UnicornException(Exception):    def __init__(self, name):        self.name = nameapp = FastAPI()@app.exception_handler(UnicornException)async def unicorn_exception_handle(request: Request, exc: UnicornException):    return JSONResponse(        status_code=418,        content={"message": f"{exc.name} 错误..."}    )@app.get("/unicorn/{name}")async def read_unicorn(name: str):    """    引用自定义异常UnicornException    :param name:     :return:    """    if name == "lifeng":        raise UnicornException(name=name)    return {"unicorn_name": name}

注释信息:

  • class UnicornException(Exception):定义一个继承Exception的类,并顶一个name属性
  • @app.exception_handler(UnicornException)是添加自定义异常控制器

启动服务:

PS E:\git_code\python-code\fastapiProject> uvicorn handle_main:app --reload

请求接口:

GET http://127.0.0.1:8000/unicorn/lifeng

请求 unicorn/lifeng 时,路径操作会触发 UnicornException

因该异常将会被 unicorn_exception_handler 处理。

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

{    "message": "lifeng 错误..."}

覆盖默认异常处理器

触发 HTTPException 或请求无效数据时,这些处理器返回默认的 JSON 响应结果。

不过,也可以使用自定义处理器覆盖默认异常处理器。

1 - 覆盖请求验证异常

覆盖默认异常处理器时需要导入 RequestValidationError,并用 @app.excption_handler(RequestValidationError) 装饰异常处理器。

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

from fastapi import FastAPIfrom fastapi import HTTPExceptionfrom fastapi.responses import PlainTextResponsefrom fastapi.exceptions import RequestValidationErrorapp = FastAPI()@app.exception_handler(RequestValidationError)async def validation_exception_handler(request, exc):    return PlainTextResponse(str(exc), status_code=400)@app.get("/cover/{cover_id}")async def read_cover(cover_id: int):    if cover_id == 3:        raise HTTPException(status_code=418, detail="hahahha")    return {"cover_id": cover_id}

注释信息:

  • @app.exception_handler(RequestValidationError)是添加自定义异常控制器
  • return PlainTextResponse(str(exc), status_code=400)是返回字符串类型的响应数据

启动服务:

PS E:\git_code\python-code\fastapiProject> uvicorn handle_main:app --reload

请求接口:

GET http://127.0.0.1:8000/cover/ttt

请求结果:

文本格式的错误信息

1 validation error for Requestpath -> cover_id  value is not a valid integer (type=type_error.integer)

2 - 覆盖HTTPException错误处理器

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

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

from fastapi import FastAPIfrom fastapi import HTTPExceptionfrom fastapi.responses import PlainTextResponsefrom starlette.exceptions import HTTPException as StarletteHTTPExceptionapp = FastAPI()@app.exception_handler(StarletteHTTPException)async def http_exception_handler(request, exc):    return PlainTextResponse(str(exc.detail), status_code=exc.status_code)@app.get("/cover/{cover_id}")async def read_cover(cover_id: int):    if cover_id == 3:        raise HTTPException(status_code=418, detail="hahahha")    return {"cover_id": cover_id}

注释信息:

  • from starlette.exceptions import HTTPException as StarletteHTTPException引包后起别名
  • @app.exception_handler(StarletteHTTPException)是添加自定义异常控制器
  • return PlainTextResponse(str(exc), status_code=400)是返回字符串类型的响应数据

注意点:

from fastapi import HTTPException

实际就是继承的

from starlette.exceptions import HTTPException as StarletteHTTPException

所以http_exception_handler实际就是重写了StarletteHTTPException基类,自然就改变了默认的HTTPException

启动服务:

PS E:\git_code\python-code\fastapiProject> uvicorn handle_main:app --reload

请求接口:

GET http://127.0.0.1:8000/cover/3

请求结果:

文本格式的错误信息

hahahha

使用RequestValidationError的请求体

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

from fastapi import FastAPIfrom fastapi import Requestfrom fastapi import statusfrom pydantic import BaseModelfrom fastapi import HTTPExceptionfrom fastapi.encoders import jsonable_encoderfrom fastapi.responses import JSONResponsefrom fastapi.exceptions import RequestValidationErrorapp = 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("/items/")async def create_item(item: Item):    return item

启动服务:

PS E:\git_code\python-code\fastapiProject> uvicorn handle_main:app --reload

请求接口:

POST http://127.0.0.1:8000/items

请求参数:

{    "title": "222",    "size": "####"}

请求结果:

{    "detail": [        {            "loc": [                "body",                "size"            ],            "msg": "value is not a valid integer",            "type": "type_error.integer"        }    ],    "body": {        "title": "222",        "size": "####"    }}

请求结果最后的body字典就是说明数据是无效的。


复用 FastAPI 异常处理器

FastAPI 支持先对异常进行某些处理,然后再使用 FastAPI 中处理该异常的默认异常处理器。

fastapi.exception_handlers 中导入要复用的默认异常处理器:

from fastapi import FastAPIfrom fastapi import HTTPExceptionfrom fastapi.exception_handlers import (    http_exception_handler,    request_validation_exception_handler)from fastapi.exceptions import RequestValidationErrorfrom starlette.exceptions import HTTPException as StarletteHTTPExceptionapp = FastAPI()@app.exception_handler(StarletteHTTPException)async def custom_http_exception_handler(request, exc):    print(f"333: {repr(request)}, 444: {repr(exc)}")    if exc.status_code == 418:        print(exc.detail)    return await http_exception_handler(request, exc)@app.exception_handler(RequestValidationError)async def validation_exception_handler(request, exc):    print(f"555: {repr(request)}, 666: {repr(exc)}")    return await request_validation_exception_handler(request, exc)@app.get("/cover/{cover_id}")async def read_cover(cover_id: int):    if cover_id == 3:        raise HTTPException(status_code=418, detail="hahahha")    return {"cover_id": cover_id}

上述代码中exc.status_code是后台读出来的,这样我们就可以对异常处理后再返回了,repr是可以看到源数据。

INFO:     127.0.0.1:51952 - "GET /cover/3 HTTP/1.1" 418 I'm a Teapot333: <starlette.requests.Request object at 0x00000160206E2560>, 444: HTTPException(status_code=418, detail='hahahha')hahahha                                                             INFO:     127.0.0.1:52027 - "GET /cover/3 HTTP/1.1" 418 I'm a Teapot555: <starlette.requests.Request object at 0x00000160206E2AD0>, 666: RequestValidationError(model='Request', errors=[{'loc': ('path', 'cover_id'), 'msg': 'value is not a valid integer', 'type': 'type_error.integer'}])INFO:     127.0.0.1:52032 - "GET /cover/tt HTTP/1.1" 422 Unprocessable Entity


路径操作配置

  • status_code 用于定义路径操作响应中的 HTTP 状态码。可以直接传递 int 代码, 比如 404。如果记不住数字码的涵义,也可以用 status 的快捷常量。
  • tags 参数的值是由 str 组成的 list (一般只有一个 str ),tags 用于为路径操作添加标签
  • 路径装饰器还支持 summarydescription 这两个参数,summary是文档中的一个标题,description是文档中接口的描述信息
  • response_description 参数用于定义响应的描述说明(注意,response_description 只用于描述响应,description 一般则用于描述路径操作)。
  • deprecated 参数可以把路径操作标记为弃用,无需直接删除,文档中显示为置灰
from fastapi import FastAPIfrom pydantic import BaseModelfrom typing import Optionalfrom fastapi import statusapp = FastAPI()class Item(BaseModel):    name: str    description: Optional[str] = None    price: float@app.post("/items/", response_model=Item,          tags=["itemss"], status_code=201 or status.HTTP_201_CREATED,          summary="Testing",          description="描述信息",          response_description="参数用于定义响应的描述说明",          deprecated=True          )def read_item(item: Item):    """    状态码可以是纯数字或者快捷常量都可以    :param item:    :return:    """    return item

启动服务:

PS E:\git_code\python-code\fastapiProject> uvicorn config_main:app --reload

浏览器请求接口:

http://127.0.0.1:8000/docs

即可看到对应参数对应的显示效果。



JSON兼容编码器

在某些情况下,您可能需要将数据类型转换为与 JSON 兼容的类型(如dictlist等)。

然后需要将其存储在数据库中,为此,FastAPI提供了一个jsonable_encoder()功能。

from fastapi import FastAPIfrom pydantic import BaseModelfrom typing import Optionalfrom fastapi import statusfrom fastapi.encoders import jsonable_encoderresults_dict = {}class Item(BaseModel):    name: str    description: Optional[str] = None    price: floatapp = FastAPI()@app.put("/items/{item_id}", status_code=201 or status.HTTP_201_CREATED)def read_item(item_id, item: Item):    """    jsonable_encoder实际上是FastAPI内部用来转换数据的。    :param item_id:    :param item:    :return:    """    data = jsonable_encoder(item)    results_dict[item_id] = data    return results_dict

启动服务:

PS E:\git_code\python-code\fastapiProject> uvicorn config_main:app --reload

请求接口:

PUT http://127.0.0.1:8000/items/foo

请求参数:

{    "name": "lifeng",    "price": 3.3}

请求参数:

{    "foo": {        "name": "lifeng",        "description": null,        "price": 3.3    }}

jsonable_encoder实际上是FastAPI内部用来转换数据的。但它在许多其他场景中很有用。


今天先聊到这里吧,以上总结或许能帮助到你,或许帮助不到你,但还是希望能帮助到你,如有疑问、歧义,直接私信留言会及时修正发布;非常期待你的一键 3 连【 点赞、收藏、分享 】哟,谢谢!

未完成,待续……

一直在努力,希望你也是!

微信搜索公众号:就用python

版权声明:内容来源于互联网和用户投稿 如有侵权请联系删除

本文地址:http://0561fc.cn/68527.html