处理错误¶
在许多情况下,您需要将错误通知给使用您的 API 的客户端。
此客户端可以是带有前端的浏览器、其他人的代码、IoT 设备等。
您可能需要告诉客户端
- 客户端没有足够的权限执行该操作。
- 客户端无权访问该资源。
- 客户端尝试访问的项目不存在。
- 等等。
在这些情况下,您通常会在 400 范围内(从 400 到 499)返回 HTTP 状态码。
这类似于 200 HTTP 状态码(从 200 到 299)。这些“200”状态码意味着请求中以某种方式“成功”了。
400 范围内的状态码表示客户端发生错误。
还记得所有那些 “404 未找到” 错误(和笑话)吗?
使用 HTTPException
¶
要向客户端返回带有错误的 HTTP 响应,请使用 HTTPException
。
导入 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]}
在代码中引发 HTTPException
¶
HTTPException
是一个普通的 Python 异常,其中包含与 API 相关的一些额外数据。
因为它是一个 Python 异常,所以您不会 return
它,而是 raise
它。
这也意味着,如果您在路径操作函数内部调用的实用程序函数内部,并且从该实用程序函数内部引发了 HTTPException
,它将不会运行路径操作函数中的其余代码,它将立即终止该请求并将来自 HTTPException
的 HTTP 错误发送到客户端。
在关于依赖项和安全性的部分中,引发异常而不是return
值的优势将更加明显。
在此示例中,当客户端请求不存在 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]}
结果响应¶
如果客户端请求 http://example.com/items/foo
(item_id
为 "foo"
),则该客户端将收到 200 的 HTTP 状态码和以下 JSON 响应:
{
"item": "The Foo Wrestlers"
}
但是,如果客户端请求 http://example.com/items/bar
(不存在的 item_id
为 "bar"
),则该客户端将收到 404 的 HTTP 状态码(“未找到”错误)和以下 JSON 响应:
{
"detail": "Item not found"
}
提示
引发 HTTPException
时,您可以将任何可以转换为 JSON 的值作为参数 detail
传递,而不仅仅是 str
。
您可以传递 dict
、list
等。
它们由 FastAPI 自动处理并转换为 JSON。
添加自定义头部¶
在某些情况下,能够向 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]}
安装自定义异常处理程序¶
您可以使用 Starlette 中相同的异常实用程序 添加自定义异常处理程序。
假设您有一个自定义异常 UnicornException
,您(或您使用的库)可能会raise
它。
并且您想使用 FastAPI 全局处理此异常。
您可以使用 @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
,则路径操作将raise
一个UnicornException
。
但它将由unicorn_exception_handler
处理。
因此,您将收到一个清晰的错误,HTTP 状态代码为418
,JSON 内容为
{"message": "Oops! yolo did something. There goes a rainbow..."}
"技术细节"
您还可以使用from starlette.requests import Request
和from starlette.responses import JSONResponse
。
FastAPI提供与fastapi.responses
相同的starlette.responses
,仅为了方便您,开发人员。但大多数可用的响应都直接来自 Starlette。Request
也是如此。
覆盖默认异常处理程序¶
FastAPI有一些默认的异常处理程序。
这些处理程序负责在您raise
一个HTTPException
以及请求数据无效时返回默认的 JSON 响应。
您可以使用自己的处理程序覆盖这些异常处理程序。
覆盖请求验证异常¶
当请求包含无效数据时,FastAPI会在内部引发RequestValidationError
。
并且它还包含一个默认的异常处理程序。
要覆盖它,请导入RequestValidationError
并使用@app.exception_handler(RequestValidationError)
来装饰异常处理程序。
异常处理程序将接收一个Request
和异常。
from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
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.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
,而不是获取包含以下内容的默认 JSON 错误:
{
"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
与ValidationError
¶
警告
如果您现在不重要,这些是您可以跳过的技术细节。
RequestValidationError
是Pydantic的ValidationError
的子类。
FastAPI使用它,以便如果您在response_model
中使用Pydantic模型,并且您的数据存在错误,您将在日志中看到该错误。
但客户端/用户将看不到它。相反,客户端将收到一个HTTP状态代码为500
的“内部服务器错误”。
应该是这样,因为如果您在响应或代码中的任何位置(不在客户端的请求中)都存在Pydantic ValidationError
,那么这实际上是您代码中的错误。
在您修复它时,您的客户端/用户不应访问有关错误的内部信息,因为这可能会暴露安全漏洞。
覆盖HTTPException
错误处理程序¶
同样的方式,您可以覆盖HTTPException
处理程序。
例如,您可能希望为这些错误返回纯文本响应而不是 JSON
from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
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.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}
"技术细节"
您还可以使用from starlette.responses import PlainTextResponse
。
FastAPI提供与fastapi.responses
相同的starlette.responses
,仅为了方便您,开发人员。但大多数可用的响应都直接来自 Starlette。
使用RequestValidationError
主体¶
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",
"size"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
],
"body": {
"title": "towel",
"size": "XL"
}
}
FastAPI的HTTPException
与Starlette的HTTPException
¶
FastAPI有自己的HTTPException
。
并且FastAPI的HTTPException
错误类继承自Starlette的HTTPException
错误类。
唯一的区别是FastAPI的HTTPException
接受任何可JSON化的数据作为detail
字段,而Starlette的HTTPException
仅接受字符串。
因此,您可以在代码中像往常一样继续引发FastAPI的HTTPException
。
但是,当您注册异常处理程序时,应将其注册为Starlette的HTTPException
。
这样,如果Starlette的内部代码的任何部分或Starlette扩展或插件引发了Starlette HTTPException
,您的处理程序将能够捕获并处理它。
在此示例中,为了能够在同一代码中同时拥有两个HTTPException
,Starlette的异常被重命名为StarletteHTTPException
from starlette.exceptions import HTTPException as StarletteHTTPException
重用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!: {repr(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}
在此示例中,您只是用非常有表现力的消息print
错误,但您明白了。您可以使用异常,然后只需重用默认的异常处理程序。