在许多情况下,您需要将错误通知给正在使用API的客户端。
该客户端可以是带有前端的浏览器,其他人的代码,IoT设备等。
你需要告知客户端:

  • 客户端没有足够的权限进行该操作。
  • 客户端无权访问该资源。
  • 客户端尝试访问的项目不存在。

在这些情况下,您通常会返回400(从400到499)范围内的HTTP状态代码
这类似于200的HTTP状态代码(从200到299)。这些“200”状态代码意味着请求中某种程度上存在“成功”。
400范围内的状态代码表示客户端出现错误。
还记得所有那些“ 404 Not Found”错误(和笑话)吗?

使用HTTPException

返回有错误的http响应给客户端使用HTTPException.

导入HTTPException

  1. from fastapi import FastAPI, HTTPException

在你的代码中抛出HTTPException

HTTPException是普通的Python异常,其中包含与API相关的其他数据。
因为它是Python异常,所以您不返回它,而是raise它。
这也意味着,如果您在要在路径操作函数内部调用的实用程序函数内部,并且从该实用程序函数内部抛出HTTPException, 它将不会运行路径操作函数中的其余代码,它将立即终止该请求,并将HTTP错误从HTTPException发送到客户端。
在有关依赖关系和安全性的部分中,抛出异常而不是返回值的好处将更加明显。
在此示例中,当客户端通过不存在的ID请求商品时,引发状态代码为404的异常:

  1. from fastapi import FastAPI, HTTPException
  2. app = FastAPI()
  3. items = {"foo": "The Foo Wrestlers"}
  4. @app.get("/items/{item_id}")
  5. async def read_item(item_id: str):
  6. if item_id not in items:
  7. raise HTTPException(status_code=404, detail="Item not found")
  8. return {"item": items[item_id]}

结果响应

如果客户端请求[http://example.com/items/foo](http://example.com/items/foo)item_id为“bar”),则他将收到HTTP状态代码200和JSON响应:

  1. {
  2. "item": "The Foo Wrestlers"
  3. }

但是,如果客户端请求[http://example.com/items/bar](http://example.com/items/bar)(不存在的item_id“ bar”),则他将收到HTTP状态码404(“找不到”错误)和JSON响应:

  1. {
  2. "detail": "Item not found"
  3. }

提示 引发HTTPException时,您可以传递任何可以转换为JSON的值作为参数详细信息,而不仅限于str。 您可以传递字典,列表等。 它们由FastAPI自动处理并转换为JSON。

添加自定义头

在某些情况下,能够将自定义标头添加到HTTP错误很有用。例如,对于某些类型的安全性。
您可能不需要直接在代码中使用它。
但是,如果需要高级方案,可以添加自定义头:

  1. from fastapi import FastAPI, HTTPException
  2. app = FastAPI()
  3. items = {"foo": "The Foo Wrestlers"}
  4. @app.get("/items-header/{item_id}")
  5. async def read_item_header(item_id: str):
  6. if item_id not in items:
  7. raise HTTPException(
  8. status_code=404,
  9. detail="Item not found",
  10. headers={"X-Error": "There goes my error"},
  11. )
  12. return {"item": items[item_id]}

安装自定义异常处理

您可以使用Starlette中的相同异常实用程序添加自定义异常处理程序。
假设您有一个自定义异常UnicornException,您(或您使用的库)可能会引发该异常。
您想使用FastAPI全局处理此异常。您可以使用@app.exception_handler()添加自定义异常处理程序.

  1. from fastapi import FastAPI, Request
  2. from fastapi.responses import JSONResponse
  3. class UnicornException(Exception):
  4. def __init__(self, name: str):
  5. self.name = name
  6. app = FastAPI()
  7. @app.exception_handler(UnicornException)
  8. async def unicorn_exception_handler(request: Request, exc: UnicornException):
  9. return JSONResponse(
  10. status_code=418,
  11. content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
  12. )
  13. @app.get("/unicorns/{name}")
  14. async def read_unicorn(name: str):
  15. if name == "yolo":
  16. raise UnicornException(name=name)
  17. return {"unicorn_name": name}

在这里,如果您请求/unicorns/yolo,则路径操作将引发UnicornException
但是它将由unicorn_exception_handler处理。
因此,您将收到一个干净的错误,其HTTP状态代码为418,JSON内容为:

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

详细教学 您还可以使用from starlette.requests import Requestfrom starlette.responses import JSONResponse。 FastAPI提供与starlette.responses相同的fastapi.responses,只是为开发人员提供了便利。但是大多数可用的响应直接来自Starlette。与请求相同。

复写默认的异常处理

FastAPI有一些默认的异常处理。
这些处理程序负责在引发HTTPException以及请求包含无效数据时返回默认的JSON响应。
你可以自己复写这些异常处理。

覆盖请求验证异常

当请求包含无效数据时,FastAPI在内部引发RequestValidationError。
它还包括一个默认的异常处理程序。
要覆盖它,请导入RequestValidationError并将其与@app.exception_handler(RequestValidationError)配合使用以装饰异常处理程序。
异常处理程序将接收请求和异常。

  1. from fastapi import FastAPI, HTTPException
  2. from fastapi.exceptions import RequestValidationError
  3. from fastapi.responses import PlainTextResponse
  4. from starlette.exceptions import HTTPException as StarletteHTTPException
  5. app = FastAPI()
  6. @app.exception_handler(StarletteHTTPException)
  7. async def http_exception_handler(request, exc):
  8. return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
  9. @app.exception_handler(RequestValidationError)
  10. async def validation_exception_handler(request, exc):
  11. return PlainTextResponse(str(exc), status_code=400)
  12. @app.get("/items/{item_id}")
  13. async def read_item(item_id: int):
  14. if item_id == 3:
  15. raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
  16. return {"item_id": item_id}

现在,如果您转到/items/foo,而不是使用以下命令获取默认的JSON错误:

  1. {
  2. "detail": [
  3. {
  4. "loc": [
  5. "path",
  6. "item_id"
  7. ],
  8. "msg": "value is not a valid integer",
  9. "type": "type_error.integer"
  10. }
  11. ]
  12. }

您将获得一个文本版本,其中包含:

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

RequestValidationError vs ValidationError

警告 这些技术细节如果现在对您不重要,则可以跳过。

RequestValidationError是 Pydantic’s 中ValidationError的一个子类。
FastAPI使用它的目的是,如果您在response_model中使用Pydantic模型,并且您的数据有错误,您将在日志中看到该错误。
但是客户端/用户将看不到它。而是,客户端将收到带有HTTP状态代码500的“内部服务器错误”。
之所以应该这样,是因为如果您的响应中或代码中的任何地方(而不是客户的请求中)都存在Pydantic ValidationError,则实际上是代码中的错误。
而且,在修复该错误时,您的客户/用户不应访问有关该错误的内部信息,因为这可能会暴露一个安全漏洞。

复写HTTPException错误处理

同样的方式,你可以复写HTTPException处理。
例如,对于这些错误,您可能希望返回纯文本响应而不是JSON:

  1. from fastapi import FastAPI, HTTPException
  2. from fastapi.exceptions import RequestValidationError
  3. from fastapi.responses import PlainTextResponse
  4. from starlette.exceptions import HTTPException as StarletteHTTPException
  5. app = FastAPI()
  6. @app.exception_handler(StarletteHTTPException)
  7. async def http_exception_handler(request, exc):
  8. return PlainTextResponse(str(exc.detail), status_code=exc.status_code)
  9. @app.exception_handler(RequestValidationError)
  10. async def validation_exception_handler(request, exc):
  11. return PlainTextResponse(str(exc), status_code=400)
  12. @app.get("/items/{item_id}")
  13. async def read_item(item_id: int):
  14. if item_id == 3:
  15. raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
  16. return {"item_id": item_id}

详细说明 你也可以使用from starlette.responses import PlainTextResponse FastAPI提供与starlette.responses相同的fastapi.responses,只是为开发人员提供了便利。但是大多数可用的响应直接来自Starlette。


使用RequestValidationError

RequestValidationError包含接收到的带有无效数据的body
您可以在开发应用程序时使用它来记录主体并对其进行调试,然后将其返回给用户,等等。

  1. from fastapi import FastAPI, Request, status
  2. from fastapi.encoders import jsonable_encoder
  3. from fastapi.exceptions import RequestValidationError
  4. from fastapi.responses import JSONResponse
  5. from pydantic import BaseModel
  6. app = FastAPI()
  7. @app.exception_handler(RequestValidationError)
  8. async def validation_exception_handler(request: Request, exc: RequestValidationError):
  9. return JSONResponse(
  10. status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
  11. content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),
  12. )
  13. class Item(BaseModel):
  14. title: str
  15. size: int
  16. @app.post("/items/")
  17. async def create_item(item: Item):
  18. return item

现在尝试发送无效的item,例如:

  1. {
  2. "title": "towel",
  3. "size": "XL"
  4. }

您将收到一条响应,告知您包含接收到的正文的数据无效:

  1. {
  2. "detail": [
  3. {
  4. "loc": [
  5. "body",
  6. "item",
  7. "size"
  8. ],
  9. "msg": "value is not a valid integer",
  10. "type": "type_error.integer"
  11. }
  12. ],
  13. "body": {
  14. "title": "towel",
  15. "size": "XL"
  16. }
  17. }

FastAPI的HTTPException vs Starlette的HTTPException

FastAPI有自己的HTTPException。
FastAPIHTTPException错误类继承自Starlette的HTTPException错误类。
唯一的区别是,FastAPI的HTTPException允许您添加要包含在响应中的标头。
OAuth 2.0和某些安全实用程序在内部需要/使用此功能。
因此,您可以像平常一样在代码中继续提高FastAPI的HTTPException。
但是,当您注册异常处理程序时,应该为Starlette的HTTPException注册它。
这样,如果Starlette内部代码的任何部分或Starlette扩展或插件引发了Starlette的HTTPException,则您的处理程序将能够捕获并处理它。
在此示例中,为了能够将两个HTTPExceptions包含在同一代码中,Starlette的异常被重命名为StarletteHTTPException:

  1. from starlette.exceptions import HTTPException as StarletteHTTPException

重新使用FastAPI的异常处理

您也可能只想以某种方式使用该异常,然后使用FastAPI中相同的默认异常处理程序。
您可以从fastapi.exception_handlers导入并重新使用默认的异常处理程序:

  1. from fastapi import FastAPI, HTTPException
  2. from fastapi.exception_handlers import (
  3. http_exception_handler,
  4. request_validation_exception_handler,
  5. )
  6. from fastapi.exceptions import RequestValidationError
  7. from starlette.exceptions import HTTPException as StarletteHTTPException
  8. app = FastAPI()
  9. @app.exception_handler(StarletteHTTPException)
  10. async def custom_http_exception_handler(request, exc):
  11. print(f"OMG! An HTTP error!: {exc}")
  12. return await http_exception_handler(request, exc)
  13. @app.exception_handler(RequestValidationError)
  14. async def validation_exception_handler(request, exc):
  15. print(f"OMG! The client sent invalid data!: {exc}")
  16. return await request_validation_exception_handler(request, exc)
  17. @app.get("/items/{item_id}")
  18. async def read_item(item_id: int):
  19. if item_id == 3:
  20. raise HTTPException(status_code=418, detail="Nope! I don't like 3.")
  21. return {"item_id": item_id}

在此示例中,您仅使用非常具有表现力的消息来print错误。
但是您知道了,可以使用异常,然后重新使用默认的异常处理程序。