You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

遇到两种矛盾情况:

  1. Case 1:能正常抛出自定义CustomException,但Swagger UI中字段显示为「非必填」,对应代码:
required_field: str = Query(None, alias='requiredField', description='description')
# 或者
required_field: str | None = Query(None, alias='requiredField', description='description')
  1. 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可专注于业务逻辑层面的多字段校验

最佳实践
  1. 分层处理校验逻辑:
    • 基础的必填性、格式校验交给FastAPI的Query/Path/Body直接处理,保证Swagger展示准确
    • 多字段联合校验、业务规则校验放在Pydantic模型的root_validator中实现
  2. 统一自定义异常处理:
    • 定义统一的自定义异常类,配合FastAPI的exception_handler,确保所有自定义异常返回格式一致的响应
  3. 避免在BaseModel中混用Query/Body:
    • BaseModel更适合封装请求体或复杂参数组合,单个查询参数直接定义在路由函数中,可避免Swagger展示异常和校验顺序冲突问题

内容的提问来源于stack exchange,提问作者양정훈

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 14:43:12