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