FastAPI:如何从空字段请求抛出自定义异常及验证问题
问题
使用FastAPI 0.87.0版本,有以下需求:
- 定义可同时验证多个字段的函数(比如判断
name和number同时为空的情况),因此使用root_validator - 空请求时抛出自定义
Exception而非Pydantic默认错误,因此使用root_validator(pre=True) - 在Swagger UI中标记字段为必填,因此不能用
Query(None),必须用Query(...) - 字段需定义自定义别名与描述,因此使用FastAPI的
Query、Body
现有代码如下:
@router.get("/foo") async def foo(req: FooReq): ... class FooReq(BaseModel): required_field: str = Query(..., alias='requiredField', description='description') not_required_field: int | None = Query(None, alias='notRequiredField', description='description') @root_validator(pre=True) def my_validation(cls, values): if values.get('requiredField') is None: raise CustomException
遇到两种矛盾情况:
- Case 1:能正常抛出自定义
CustomException,但Swagger UI中字段显示为「非必填」,对应代码:
required_field: str = Query(None, alias='requiredField', description='description') # 或者 required_field: str | None = Query(None, alias='requiredField', description='description')
- Case 2:Swagger UI正常显示字段为「必填」,但抛出的是Pydantic默认错误,对应代码:
required_field: str = Query(..., alias='requiredField', description='description') # 或者 required_field: str | None = Query(..., alias='requiredField', description='description')
请问该如何解决?是否有处理这类验证并抛出自定义异常的最佳实践?
解决方案
核心问题在于:当使用Query(...)标记必填字段时,FastAPI会在请求进入Pydantic验证前就完成参数校验,此时如果缺失字段,会直接返回FastAPI自带的错误,根本走不到你定义的root_validator。
要同时满足所有需求,可按以下方式调整:
步骤1:拆分参数定义与模型验证
把单个查询参数的Query定义移到路由函数中,将多字段联合验证放在BaseModel的root_validator里,通过Depends注入模型实现校验衔接:
from fastapi import Query, Depends from pydantic import BaseModel, root_validator class FooReq(BaseModel): required_field: str not_required_field: int | None = None @root_validator(pre=True) def my_validation(cls, values): # 这里可实现多字段联合校验逻辑,比如判断多个字段是否同时为空 if values.get('required_field') is None and values.get('not_required_field') is None: raise CustomException(detail="至少需要提供一个字段") return values @router.get("/foo") async def foo( required_field: str = Query(..., alias='requiredField', description='必填字段描述'), not_required_field: int | None = Query(None, alias='notRequiredField', description='可选字段描述'), req: FooReq = Depends() ): # req为已完成验证的模型实例 ...
步骤2:配置自定义异常处理器
确保CustomException能被FastAPI捕获并返回自定义格式的响应:
from fastapi import Request from fastapi.responses import JSONResponse class CustomException(Exception): def __init__(self, detail: str): self.detail = detail @app.exception_handler(CustomException) async def custom_exception_handler(request: Request, exc: CustomException): return JSONResponse( status_code=400, content={"detail": exc.detail}, )
方案可行性说明
- 路由参数中的
Query(...)会让Swagger正确标记字段为必填 - 通过
Depends()将参数注入FooReq模型,触发root_validator执行多字段联合校验,可正常抛出自定义异常 - 路由参数已确保
required_field存在,root_validator可专注于业务逻辑层面的多字段校验
最佳实践
- 分层处理校验逻辑:
- 基础的必填性、格式校验交给FastAPI的
Query/Path/Body直接处理,保证Swagger展示准确 - 多字段联合校验、业务规则校验放在Pydantic模型的
root_validator中实现
- 基础的必填性、格式校验交给FastAPI的
- 统一自定义异常处理:
- 定义统一的自定义异常类,配合FastAPI的
exception_handler,确保所有自定义异常返回格式一致的响应
- 定义统一的自定义异常类,配合FastAPI的
- 避免在BaseModel中混用Query/Body:
BaseModel更适合封装请求体或复杂参数组合,单个查询参数直接定义在路由函数中,可避免Swagger展示异常和校验顺序冲突问题
内容的提问来源于stack exchange,提问作者양정훈
相关产品推荐
相关产品推荐

