处理错误¶
某些情况下,需要向使用你的 API 的客户端返回错误提示。
这里所谓的客户端包括前端浏览器、他人的代码、物联网设备等。
你可能需要告诉客户端:
- 客户端没有执行该操作的权限
- 客户端没有访问该资源的权限
- 客户端要访问的项目不存在
- 等等
遇到这些情况时,通常要返回4XX(400 至 499)HTTP 状态码。
这与表示请求成功的2XX(200 至 299)HTTP 状态码类似。那些“200”状态码表示某种程度上的“成功”。
而4XX 状态码表示客户端发生了错误。
大家都知道「404 Not Found」错误,还有调侃这个错误的笑话吧?
使用HTTPException¶
向客户端返回 HTTP 错误响应,可以使用HTTPException。
导入HTTPException¶
fromfastapiimportFastAPI,HTTPExceptionapp=FastAPI()items={"foo":"The Foo Wrestlers"}@app.get("/items/{item_id}")asyncdefread_item(item_id:str):ifitem_idnotinitems:raiseHTTPException(status_code=404,detail="Item not found")return{"item":items[item_id]}在代码中触发HTTPException¶
HTTPException 是额外包含了和 API 有关数据的常规 Python 异常。
因为是 Python 异常,所以不能return,只能raise。
这也意味着,如果你在路径操作函数里调用的某个工具函数内部触发了HTTPException,那么路径操作函数中后续的代码将不会继续执行,请求会立刻终止,并把HTTPException 的 HTTP 错误发送给客户端。
在介绍依赖项与安全的章节中,你可以更直观地看到用raise 异常代替return 值的优势。
本例中,客户端用不存在的ID 请求item 时,触发状态码为404 的异常:
fromfastapiimportFastAPI,HTTPExceptionapp=FastAPI()items={"foo":"The Foo Wrestlers"}@app.get("/items/{item_id}")asyncdefread_item(item_id:str):ifitem_idnotinitems:raiseHTTPException(status_code=404,detail="Item not found")return{"item":items[item_id]}响应结果¶
请求为http://example.com/items/foo(item_id 为"foo")时,客户端会接收到 HTTP 状态码 200 及如下 JSON 响应结果:
{"item":"The Foo Wrestlers"}但如果客户端请求http://example.com/items/bar(不存在的item_id"bar"),则会接收到 HTTP 状态码 404(“未找到”错误)及如下 JSON 响应结果:
{"detail":"Item not found"}提示
触发HTTPException 时,可以用参数detail 传递任何能转换为 JSON 的值,不仅限于str。
还支持传递dict、list 等数据结构。
FastAPI 能自动处理这些数据,并将之转换为 JSON。
添加自定义响应头¶
有些场景下要为 HTTP 错误添加自定义响应头。例如,出于某些类型的安全需要。
一般情况下你可能不会在代码中直接使用它。
但在某些高级场景中需要时,你可以添加自定义响应头:
fromfastapiimportFastAPI,HTTPExceptionapp=FastAPI()items={"foo":"The Foo Wrestlers"}@app.get("/items-header/{item_id}")asyncdefread_item_header(item_id:str):ifitem_idnotinitems:raiseHTTPException(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() 添加自定义异常处理器:
fromfastapiimportFastAPI,Requestfromfastapi.responsesimportJSONResponseclassUnicornException(Exception):def__init__(self,name:str):self.name=nameapp=FastAPI()@app.exception_handler(UnicornException)asyncdefunicorn_exception_handler(request:Request,exc:UnicornException):returnJSONResponse(status_code=418,content={"message":f"Oops!{exc.name} did something. There goes a rainbow..."},)@app.get("/unicorns/{name}")asyncdefread_unicorn(name:str):ifname=="yolo":raiseUnicornException(name=name)return{"unicorn_name":name}这里,请求/unicorns/yolo 时,路径操作会触发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 提供了与starlette.responses 相同的fastapi.responses 作为便捷方式,但大多数可用的响应都直接来自 Starlette。Request 也是如此。
覆盖默认异常处理器¶
FastAPI 自带了一些默认异常处理器。
当你触发HTTPException,或者请求中包含无效数据时,这些处理器负责返回默认的 JSON 响应。
你也可以用自己的处理器覆盖它们。
覆盖请求验证异常¶
请求中包含无效数据时,FastAPI 内部会触发RequestValidationError。
它也内置了该异常的默认处理器。
要覆盖它,导入RequestValidationError,并用@app.exception_handler(RequestValidationError) 装饰你的异常处理器。
异常处理器会接收Request 和该异常。
fromfastapiimportFastAPI,HTTPExceptionfromfastapi.exceptionsimportRequestValidationErrorfromfastapi.responsesimportPlainTextResponsefromstarlette.exceptionsimportHTTPExceptionasStarletteHTTPExceptionapp=FastAPI()@app.exception_handler(StarletteHTTPException)asyncdefhttp_exception_handler(request,exc):returnPlainTextResponse(str(exc.detail),status_code=exc.status_code)@app.exception_handler(RequestValidationError)asyncdefvalidation_exception_handler(request,exc:RequestValidationError):message="Validation errors:"forerrorinexc.errors():message+=f"\nField:{error['loc']}, Error:{error['msg']}"returnPlainTextResponse(message,status_code=400)@app.get("/items/{item_id}")asyncdefread_item(item_id:int):ifitem_id==3:raiseHTTPException(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"}]}将得到如下文本内容:
Validation errors:Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer覆盖HTTPException 错误处理器¶
同理,也可以覆盖HTTPException 的处理器。
例如,只为这些错误返回纯文本响应,而不是 JSON:
fromfastapiimportFastAPI,HTTPExceptionfromfastapi.exceptionsimportRequestValidationErrorfromfastapi.responsesimportPlainTextResponsefromstarlette.exceptionsimportHTTPExceptionasStarletteHTTPExceptionapp=FastAPI()@app.exception_handler(StarletteHTTPException)asyncdefhttp_exception_handler(request,exc):returnPlainTextResponse(str(exc.detail),status_code=exc.status_code)@app.exception_handler(RequestValidationError)asyncdefvalidation_exception_handler(request,exc:RequestValidationError):message="Validation errors:"forerrorinexc.errors():message+=f"\nField:{error['loc']}, Error:{error['msg']}"returnPlainTextResponse(message,status_code=400)@app.get("/items/{item_id}")asyncdefread_item(item_id:int):ifitem_id==3:raiseHTTPException(status_code=418,detail="Nope! I don't like 3.")return{"item_id":item_id}技术细节
还可以使用from starlette.responses import PlainTextResponse。
FastAPI 提供了与starlette.responses 相同的fastapi.responses 作为便捷方式,但大多数可用的响应都直接来自 Starlette。
警告
请注意,RequestValidationError 包含发生验证错误的文件名和行号信息,你可以在需要时将其记录到日志中以提供相关信息。
但这也意味着,如果你只是将其直接转换为字符串并返回,可能会泄露一些关于系统的细节信息。因此,这里的代码会提取并分别显示每个错误。
使用RequestValidationError 的请求体¶
RequestValidationError 包含其接收到的带有无效数据的请求体body。
开发时,你可以用它来记录请求体、调试错误,或返回给用户等。
fromfastapiimportFastAPI,Requestfromfastapi.encodersimportjsonable_encoderfromfastapi.exceptionsimportRequestValidationErrorfromfastapi.responsesimportJSONResponsefrompydanticimportBaseModelapp=FastAPI()@app.exception_handler(RequestValidationError)asyncdefvalidation_exception_handler(request:Request,exc:RequestValidationError):returnJSONResponse(status_code=422,content=jsonable_encoder({"detail":exc.errors(),"body":exc.body}),)classItem(BaseModel):title:strsize:int@app.post("/items/")asyncdefcreate_item(item:Item):returnitem现在试着发送一个无效的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 vs Starlette 的HTTPException¶
FastAPI 也提供了自有的HTTPException。
FastAPI 的HTTPException 错误类继承自 Starlette 的HTTPException 错误类。
它们之间的唯一区别是,FastAPI 的HTTPException 在detail 字段中接受任意可转换为 JSON 的数据,而 Starlette 的HTTPException 只接受字符串。
因此,你可以继续像平常一样在代码中触发FastAPI 的HTTPException。
但注册异常处理器时,应该注册到来自 Starlette 的HTTPException。
这样做是为了,当 Starlette 的内部代码、扩展或插件触发 StarletteHTTPException 时,你的处理器能够捕获并处理它。
本例中,为了在同一份代码中同时使用两个HTTPException,将 Starlette 的异常重命名为StarletteHTTPException:
fromstarlette.exceptionsimportHTTPExceptionasStarletteHTTPException复用FastAPI 的异常处理器¶
如果你想在自定义处理后仍复用FastAPI 的默认异常处理器,可以从fastapi.exception_handlers 导入并复用这些默认处理器:
fromfastapiimportFastAPI,HTTPExceptionfromfastapi.exception_handlersimport(http_exception_handler,request_validation_exception_handler,)fromfastapi.exceptionsimportRequestValidationErrorfromstarlette.exceptionsimportHTTPExceptionasStarletteHTTPExceptionapp=FastAPI()@app.exception_handler(StarletteHTTPException)asyncdefcustom_http_exception_handler(request,exc):print(f"OMG! An HTTP error!:{repr(exc)}")returnawaithttp_exception_handler(request,exc)@app.exception_handler(RequestValidationError)asyncdefvalidation_exception_handler(request,exc):print(f"OMG! The client sent invalid data!:{exc}")returnawaitrequest_validation_exception_handler(request,exc)@app.get("/items/{item_id}")asyncdefread_item(item_id:int):ifitem_id==3:raiseHTTPException(status_code=418,detail="Nope! I don't like 3.")return{"item_id":item_id}虽然本例只是用非常夸张的信息打印了错误,但足以说明:你可以先处理异常,然后再复用默认的异常处理器。







