前言
通常我们可以通过 raise 抛出一个 HTTPException
异常,请求参数不合法会抛出RequestValidationError
异常,这是最常见的2种异常。
HTTPException
异常
向客户端返回 HTTP 错误响应,可以使用 raise
触发 HTTPException
。
from fastapi import FastAPI, HTTPExceptionapp = FastAPI()@app.get("/path/{name}")
async def read_unicorn(name: str): if name == "yoyo": raise HTTPException(404, detail=f"name: {name} not found") return {"path_name": name}
默认情况下返回json格式
HTTP/1.1 404 Not Found
date: Wed, 27 Sep 2023 02:07:07 GMT
server: uvicorn
content-length: 22
content-type: application/json{"detail":"Not Found"}
覆盖默认的HTTPException
异常
查看HTTPException
异常相关源码
from starlette.exceptions import HTTPException as StarletteHTTPException class HTTPException(StarletteHTTPException): def __init__( self, status_code: int, detail: Any = None, headers: Optional[Dict[str, Any]] = None, ) -> None: super().__init__(status_code=status_code, detail=detail, headers=headers)
HTTPException 异常是继承的 starlette 包里面的 HTTPException
覆盖默认异常处理器时需要导入 from starlette.exceptions import HTTPException as StarletteHTTPException
,并用 @app.excption_handler(StarletteHTTPException)
装饰异常处理器。
from fastapi import FastAPI, Request
from fastapi.exceptions import HTTPException
from fastapi.responses import PlainTextResponse, JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException app = FastAPI() # # 捕获 HTTPException 异常
@app.exception_handler(StarletteHTTPException)
def http_error(request, exc): print(exc.status_code) print(exc.detail) # return JSONResponse({'error_msg': exc.detail}, status_code=exc.status_code) return PlainTextResponse(content=exc.detail, status_code=exc.status_code) @app.get("/path/{name}")
async def read_unicorn(name: str): if name == "yoyo": raise HTTPException(404, detail=f"name: {name} not found") return {"path_name": name}
这样原来的 HTTPException 返回 json 格式,现在改成返回text/plain
文本格式了。
HTTP/1.1 404 Not Found
date: Wed, 27 Sep 2023 07:24:58 GMT
server: uvicorn
content-length: 20
content-type: text/plain; charset=utf-8name: yoyo not found
覆盖请求验证异常
请求中包含无效数据时,FastAPI 内部会触发 RequestValidationError
。
该异常也内置了默认异常处理器。
覆盖默认异常处理器时需要导入 RequestValidationError
,并用 @app.excption_handler(RequestValidationError)
装饰异常处理器。
这样,异常处理器就可以接收 Request
与异常。
from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.responses import PlainTextResponse
from starlette.exceptions import HTTPException as StarletteHTTPExceptionapp = 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
,可以看到以下内容替换了默认 JSON 错误信息:
{"detail": [{"loc": ["path","item_id"],"msg": "value is not a valid integer","type": "type_error.integer"}]
}
以下是文本格式的错误信息:
HTTP/1.1 400 Bad Request
date: Wed, 27 Sep 2023 07:30:38 GMT
server: uvicorn
content-length: 103
content-type: text/plain; charset=utf-81 validation error for Request
path -> item_idvalue is not a valid integer (type=type_error.integer)
RequestValidationError 源码分析
RequestValidationError 相关源码
class RequestValidationError(ValidationError): def __init__(self, errors: Sequence[ErrorList], *, body: Any = None) -> None: self.body = body super().__init__(errors, RequestErrorModel)
使用示例
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 BaseModelapp = FastAPI()@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError): print(exc.json()) print(exc.errors()) print(exc.body) # 请求body return JSONResponse( status_code=400, 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
现在试着发送一个无效的 item
,例如:
{"title": "towel","size": "XL"
}
运行结果
HTTP/1.1 400 Bad Request
date: Wed, 27 Sep 2023 07:51:36 GMT
server: uvicorn
content-length: 138
content-type: application/json{"detail":[{"loc":["body","size"],"msg":"value is not a valid integer","type":"type_error.integer"}],"body":{"title":"towel","size":"XL"}}
RequestValidationError
和 ValidationError
如果您觉得现在还用不到以下技术细节,可以先跳过下面的内容。
RequestValidationError
是 Pydantic 的 ValidationError
的子类。
FastAPI 调用的就是 RequestValidationError
类,因此,如果在 response_model
中使用 Pydantic 模型,且数据有错误时,在日志中就会看到这个错误。
但客户端或用户看不到这个错误。反之,客户端接收到的是 HTTP 状态码为 500
的「内部服务器错误」。
这是因为在_响应_或代码(不是在客户端的请求里)中出现的 Pydantic ValidationError
是代码的 bug。
修复错误时,客户端或用户不能访问错误的内部信息,否则会造成安全隐患。