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

简单絮叨一些

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

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

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



处理错误

使用HTTPException

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

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

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

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

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

from fastapi import FastAPI
from fastapi import HTTPException


app = 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 FastAPI
from fastapi import HTTPException

app = 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 FastAPI
from fastapi import Request
from fastapi.responses import JSONResponse


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


app = 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 FastAPI
from fastapi import HTTPException
from fastapi.responses import PlainTextResponse
from fastapi.exceptions import RequestValidationError

app = 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 Request
path -> cover_id
  value is not a valid integer (type=type_error.integer)

2 - 覆盖HTTPException错误处理器

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

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

from fastapi import FastAPI
from fastapi import 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("/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 FastAPI
from fastapi import Request
from fastapi import status
from pydantic import BaseModel
from fastapi import HTTPException
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError

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("/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 FastAPI
from fastapi import HTTPException
from fastapi.exception_handlers import (
    http_exception_handler,
    request_validation_exception_handler
)
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException

app = 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 Teapot
333: <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 Teapot
555: <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.in
teger'
}])
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 FastAPI
from pydantic import BaseModel
from typing import Optional
from fastapi import status

app = 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 FastAPI
from pydantic import BaseModel
from typing import Optional
from fastapi import status
from fastapi.encoders import jsonable_encoder

results_dict = {}


class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float


app = 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

posted @ 2022-03-01 17:13  一名小测试  阅读(613)  评论(0编辑  收藏  举报