FastAPI框架默认集成了Pydantic用于请求数据的校验工作,当客户端传递的参数不符合模型定义的规则时,Pydantic会抛出验证错误,FastAPI会默认返回对应的错误响应。但在实际生产场景中,默认的错误处理方式往往无法满足需求,我们需要更高效的策略来处理这类错误。

默认Pydantic验证错误的行为
我们先看一个基础的Pydantic模型示例,了解默认的验证错误返回格式:
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class UserCreate(BaseModel):
username: str = Field(min_length=3, max_length=10, description="用户名长度3-10位")
age: int = Field(gt=0, lt=120, description="年龄范围1-119")
@app.post("/users")
def create_user(user: UserCreate):
return {"msg": "用户创建成功", "user": user.dict()}
当我们传递不符合规则的参数时,比如username长度为2,age为-1,FastAPI会返回如下默认响应:
{
"detail": [
{
"loc": ["body", "username"],
"msg": "ensure this value has at least 3 characters",
"type": "value_error.any_str.min_length"
},
{
"loc": ["body", "age"],
"msg": "ensure this value is greater than 0",
"type": "value_error.number.not_gt"
}
]
}
默认的响应虽然包含了错误信息,但存在两个问题:一是错误信息是英文的,对前端用户不够友好;二是响应格式可能和项目统一约定的接口格式不一致,需要额外处理。
策略一:自定义Pydantic校验器返回友好错误信息
我们可以在Pydantic模型中自定义校验器,在校验失败时直接抛出符合我们要求的错误信息,避免返回默认的英文提示。
from fastapi import FastAPI
from pydantic import BaseModel, Field, validator
app = FastAPI()
class UserCreate(BaseModel):
username: str = Field(min_length=3, max_length=10, description="用户名长度3-10位")
age: int = Field(gt=0, lt=120, description="年龄范围1-119")
@validator("username")
def check_username(cls, v):
if len(v) < 3 or len(v) > 10:
raise ValueError("用户名长度必须在3到10位之间")
return v
@validator("age")
def check_age(cls, v):
if v <= 0 or v >= 120:
raise ValueError("年龄必须在1到119之间")
return v
@app.post("/users")
def create_user(user: UserCreate):
return {"msg": "用户创建成功", "user": user.dict()}
此时传递错误参数,返回的错误信息会变成我们自定义的中文提示,更便于前端展示给用户。
策略二:全局捕获Pydantic验证异常统一处理
如果项目中多个接口都需要处理Pydantic验证错误,逐个模型自定义校验器会比较冗余,我们可以通过全局异常捕获的方式统一处理所有Pydantic验证错误。
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field, ValidationError
app = FastAPI()
# 自定义统一的错误响应格式
def format_validation_error(errors):
result = []
for error in errors:
loc = " -> ".join([str(item) for item in error["loc"]])
result.append({
"字段位置": loc,
"错误提示": error["msg"],
"错误类型": error["type"]
})
return result
@app.exception_handler(ValidationError)
async def validation_exception_handler(request: Request, exc: ValidationError):
return JSONResponse(
status_code=422,
content={
"code": 422,
"msg": "请求参数校验失败",
"data": format_validation_error(exc.errors())
}
)
class UserCreate(BaseModel):
username: str = Field(min_length=3, max_length=10, description="用户名长度3-10位")
age: int = Field(gt=0, lt=120, description="年龄范围1-119")
class ArticleCreate(BaseModel):
title: str = Field(min_length=1, description="文章标题不能为空")
content: str = Field(min_length=10, description="文章内容至少10个字符")
@app.post("/users")
def create_user(user: UserCreate):
return {"msg": "用户创建成功", "user": user.dict()}
@app.post("/articles")
def create_article(article: ArticleCreate):
return {"msg": "文章创建成功", "article": article.dict()}
这样所有接口触发的Pydantic验证错误都会被这个全局处理器捕获,返回统一格式的错误响应,不需要在每个模型或者接口中重复编写错误处理逻辑。
策略三:针对特定场景定制错误响应结构
有些项目可能要求错误响应结构和默认的不一致,比如需要把错误信息平铺,或者只返回关键的错误提示,我们可以通过自定义异常类结合全局处理实现。
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field, ValidationError
app = FastAPI()
class CustomValidationError(HTTPException):
def __init__(self, errors):
self.errors = errors
super().__init__(status_code=422, detail="参数校验失败")
def parse_errors(errors):
error_msgs = []
for error in errors:
field = error["loc"][-1]
error_msgs.append(f"{field}: {error['msg']}")
return ", ".join(error_msgs)
@app.exception_handler(CustomValidationError)
async def custom_validation_exception_handler(request: Request, exc: CustomValidationError):
return JSONResponse(
status_code=exc.status_code,
content={
"success": False,
"error_msg": parse_errors(exc.errors)
}
)
# 重写FastAPI默认的验证错误处理,抛出我们自定义的异常
@app.exception_handler(ValidationError)
async def validation_exception_handler(request: Request, exc: ValidationError):
raise CustomValidationError(exc.errors())
class ProductCreate(BaseModel):
name: str = Field(min_length=1, description="商品名称不能为空")
price: float = Field(gt=0, description="商品价格必须大于0")
@app.post("/products")
def create_product(product: ProductCreate):
return {"msg": "商品创建成功", "product": product.dict()}
这种方式可以灵活适配不同项目的错误响应规范,只需要调整parse_errors函数和响应结构即可,不需要修改业务逻辑代码。
不同策略的适用场景对比
我们可以通过下面的表格快速选择适合自己项目的处理策略:
| 处理策略 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| 自定义Pydantic校验器 | 单个模型有特殊校验规则,需要定制化错误提示 | 针对性强,错误提示精准 | 多个模型需要重复编写,冗余度高 |
| 全局异常捕获统一处理 | 项目所有接口需要统一的错误响应格式 | 代码复用性高,维护方便 | 灵活性稍弱,特殊场景需要额外适配 |
| 定制错误响应结构 | 项目有特殊的接口响应规范,和默认格式差异大 | 完全适配项目规范,灵活度高 | 需要额外编写异常类和解析逻辑 |
注意事项
- 自定义校验器时,
ValueError会被Pydantic自动捕获并转换为验证错误,不需要额外抛出ValidationError - 全局异常处理的优先级低于接口内定义的异常处理,如果某个接口需要特殊的处理逻辑,可以在接口内部单独捕获
- 错误响应中的敏感信息不要暴露,比如不要返回内部的字段映射规则或者服务器相关信息
- 如果项目同时使用了其他类型的参数校验,比如查询参数、路径参数的校验,全局的
ValidationError处理器也会生效,不需要额外配置