zoukankan      html  css  js  c++  java
  • FastAPI 进阶知识(三) 错误处理

    作者:麦克煎蛋   出处:https://www.cnblogs.com/mazhiyong/ 转载请保留这段声明,谢谢!

    如果使用API时有错误发生,你需要通知给客户端(Web端或者API使用者)这个错误信息。

    常见的错误信息为:

    • 客户端没有权限进行相关的操作。
    • 客户端找不到对应的路径操作。
    • 客户端找不到对应的资源。
    • 其他。

    这些错误信息的HTTP状态码一般为400错误(400~499)。

    一、HTTPException

    我们用HTTPException模块返回带错误信息的Response。

    HTTPException是一个普通的Python异常,同时带有与API访问有关的附加数据。

    1、导入模块

    from fastapi import HTTPException

    2、抛出异常

    在代码中抛出HTTPException

    raise HTTPException(status_code=404, detail="Item not found")

    这里, 参数detail除了可以传递字符串,还可以传递任何可以转换成JSON格式的数据,如dict、list等。

    代码示例:

    from fastapi import FastAPI, HTTPException
    
    app = FastAPI()
    
    items = {"foo": "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]}

    二、添加自定义头信息

    有时候针对HTTP错误,在一些场景下,我们需要添加自定义头信息。

    from fastapi import FastAPI, HTTPException
    
    app = FastAPI()
    
    items = {"foo": "The Foo Wrestlers"}
    
    
    @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]}

    三、自定义异常处理器

    借助 the same exception utilities from Starlette,我们可以添加自定义异常处理器。

    假设我们有个自定义异常 UnicornException,我们想在全局范围内处理这个异常。

    借助 @app.exception_handler(),就可以实现我们的目标。

    from fastapi import FastAPI, Request
    from fastapi.responses import JSONResponse
    
    
    class UnicornException(Exception):
        def __init__(self, name: str):
            self.name = name
    
    
    app = FastAPI()
    
    
    @app.exception_handler(UnicornException)
    async def unicorn_exception_handler(request: Request, exc: UnicornException):
        return JSONResponse(
            status_code=418,
            content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
        )
    
    
    @app.get("/unicorns/{name}")
    async def read_unicorn(name: str):
        if name == "yolo":
            raise UnicornException(name=name)
        return {"unicorn_name": name}

    这里如果我们请求 /unicorns/yolo,路径操作函数就会抛出异常 UnicornException,这个异常会被我们的异常处理器unicorn_exception_handler捕获到。

    最后我们收到的HTTP错误码就是418,并且错误内容为:

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

    四、重写缺省异常处理器

    FastAPI有一些缺省的异常处理器。当我们抛出HTTPException异常或者当请求有非法数据的时候,这些处理器负责返回默认的JSON结果。

    我们可以重写这些异常处理器。

    1、重写请求校验异常处理器

    当一个请求包含非法数据的时候,FastAPI内部会抛出RequestValidationError异常,并且有默认的异常处理器来处理。

    我们可以用 @app.exception_handler(RequestValidationError) 来重写这个异常处理器。

    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("/items/{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}

    如果我们请求 /items/foo,那么返回结果就不是默认的:

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

    而是:

    1 validation error
    path -> item_id
      value is not a valid integer (type=type_error.integer)

    同时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("/items/")
    async def create_item(item: Item):
        return item

    如果我们传递了不合法的数据:

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

    那么我们收到的返回结果如下:

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

    2、重写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("/items/{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}

    如果请求 /items/3,这时候返回的错误信息为:

    Nope! I don't like 3.

    如果没有自定义异常处理器http_exception_handler,返回的错误信息为:

    {
        "detail": "Nope! I don't like 3."
    }

    五、重用缺省异常处理器 

    我们可以导入并且重用缺省的异常处理器。

    我们从fastapi.exception_handlers导入缺省异常处理器。

    from fastapi import FastAPI, 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"OMG! An HTTP error!: {exc}")
        return await http_exception_handler(request, exc)
    
    
    @app.exception_handler(RequestValidationError)
    async def validation_exception_handler(request, exc):
        print(f"OMG! The client sent invalid data!: {exc}")
        return await request_validation_exception_handler(request, exc)
    
    
    @app.get("/items/{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}

    在示例中,我们在抛出异常之前添加了一条日志输出。我们可以根据业务需求灵活的重用缺省异常处理器。

    六、FastAPI HTTPException 对比 Starlette HTTPException

    FastAPI HTTPException 继承自 Starlette's HTTPException

    唯一的区别是,FastAPI HTTPException允许你在response添加头信息。主要在内部用于OAuth 2.0以及一些安全相关的功能。

    因此,通常我们在代码中抛出FastAPI HTTPException异常。

    但是,当我们注册异常处理器的时候,我们应该注册为Starlette HTTPException

    这样,当Starlette的内部代码或者Starlette扩展插件抛出Starlette HTTPException时,我们的处理器才能正常捕获和处理这个异常。

    如果我们要在代码中同时使用这两个类,为了避免命名冲突,我们可以重命名其中一个类。

    from fastapi import HTTPException
    from starlette.exceptions import HTTPException as StarletteHTTPException
  • 相关阅读:
    LeetCode 275. H-Index II
    LeetCode 274. H-Index
    LeetCode Gray Code
    LeetCode 260. Single Number III
    LeetCode Word Pattern
    LeetCode Nim Game
    LeetCode 128. Longest Consecutive Sequence
    LeetCode 208. Implement Trie (Prefix Tree)
    LeetCode 130. Surrounded Regions
    LeetCode 200. Number of Islands
  • 原文地址:https://www.cnblogs.com/mazhiyong/p/12965973.html
Copyright © 2011-2022 走看看