fastapi教程翻译(十九):Handling Errors(
在许多情况下,您需要将错误通知给使用API的客户端。
该客户端可以是带有前端的浏览器,其他人的代码,IoT设备等。
你需要告诉客户端:
- 没有足够的权限进行该操作。
- 无权访问该资源。
- 尝试访问的项目不存在。
等等
这种情况下,你通常需要返回一个400-499之间的 HTTP状态码。
400-499的状态码说明客户端出现了错误。
一、使用 HTTPException
要将带有错误的HTTP响应返回给客户端,请使用HTTPException
。
1.1. Import HTTPException
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]}
1.2. 抛出 HTTPException
错误
HTTPException
一个普通的Python异常,其中包含与API相关的其他数据。
因为它是Python的异常,你不是return
,而是raise
它。
这也意味着,如果您在要在path操作函数内部调用的实用程序函数内部,并且从该实用程序函数内部引发HTTPException,它将不会在path操作中运行其余代码 函数,它将立即终止该请求并将HTTP错误从HTTPException发送到客户端。
下面这个例子,客户端请求一个不存在的ID的内容,将会抛出一个404
异常:
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]}
1.3. 产生的响应
客户端请求 http://example.com/items/foo
(an item_id
"foo"
), 客户端将接受到一个200的状态码,然后返回的JSON如下:
{
"item": "The Foo Wrestlers"
}
但是,如果客户端请求 http://example.com/items/bar
(a non-existent item_id
"bar"
), 它将会返回一个404的状态码,返回的JSON数据如下:
{
"detail": "Item not found"
}
提示
当抛出 HTTPException
异常时, 你可以传递任何能转换为JSON的值,作为异常的详细描述,不仅仅是`str,可以传递一个字典、列表等。
二、添加自定义的headers
在某些情况下,能够将自定义标头添加到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]}
三、使用自定义的异常handlers
You can add custom exception handlers with the same exception utilities from Starlette.
假如,你需要抛出自定义的异常UnicornException
您想使用FastAPI全局处理此异常。
你可以添加一个自定义异常handler
通过 @app.exception_handler()
:
from fastapi import FastAPI
from starlette.requests import Request
from starlette.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
处理.
因此,你将收到一个明显的提示,状态码是418,并且JSON的内容如下:
{"message": "Oops! yolo did something. There goes a rainbow..."}
四、覆盖默认的异常处理程序
FastAPI 有一些默认的异常处理程序。
当您引发HTTPException且请求中包含无效数据时,这些处理程序将负责或返回默认的JSON响应。
您可以使用自己的异常处理程序覆盖它们。
4.1. 覆盖请求验证的异常
当请求包含无效数据时,** FastAPI **内部会引发RequestValidationError
。
并且它也包含了默认的异常处理。
想要覆盖它,需要导入 RequestValidationError
并且使用 @app.exception_handler(RequestValidationError)
去装饰这个异常处理器.
异常处理程序将收到一响应和异常。
from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.responses import PlainTextResponse
app = FastAPI()
@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
@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)
4.1.1 RequestValidationError
vs ValidationError
这些技术细节如果现在对您不重要,则可以跳过。
RequestValidationError是Pydantic的[ValidationError`](https://pydantic-docs.helpmanual.io/#error-handling)的子类。
** FastAPI **的用法是,如果您在response_model
中使用Pydantic模型,并且您的数据有错误,您将在日志中看到该错误。
但是客户端/用户将看不到它。 相反,客户端将收到带有HTTP状态代码“ 500”的“内部服务器错误”。
之所以应该这样,是因为如果您的响应或代码中任何地方(而不是客户端的请求)中都有PydanticValidationError
,则实际上是代码中的错误。
而且,在修复该错误时,您的客户/用户不应访问有关该错误的内部信息,因为这可能会暴露一个安全漏洞。
4.2. 覆盖 HTTPException
异常处理程序
同样你也可以覆盖HTTPException
异常:
from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.responses import PlainTextResponse
app = FastAPI()
@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
@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}
4.2.1 FastAPI's HTTPException
vs Starlette's HTTPException
FastAPI 有自己的 HTTPException
.
FastAPI **的HTTPException错误类继承自Starlette的HTTPException错误类。
唯一的区别是** FastAPI **的HTTPException允许您添加要包含在响应中的标头。
OAuth 2.0和某些安全实用程序在内部需要/使用此功能。
因此,您可以像平常在代码中一样继续提高** FastAPI **的HTTPException。
但是当您注册异常处理程序时,应该为Starlette的HTTPException注册它。
这样,如果Starlette内部代码的任何部分,或者Starlette扩展或插件引发了HTTPException,则您的处理程序将能够捕获该异常。
在此示例中,为了能够将两个HTTPException都包含在同一代码中,Starlette的异常被重命名为StarletteHTTPException:
from starlette.exceptions import HTTPException as StarletteHTTPException
4.3. 重用** FastAPI **的异常处理程序
您也可能只想以某种方式使用该异常,然后使用** FastAPI **中相同的默认异常处理程序。
您可以从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}
在此示例中,您只是使用非常明确的通知打印错误。
但是您知道了,可以使用异常,然后重新使用默认的异常处理程序。