FastAPI中Pydantic根验证失败如何返回422而非500错误
如何让FastAPI在模型验证失败时返回422 Unprocessable Entity错误
问题复现
当前使用FastAPI和Pydantic编写接口时,自定义验证逻辑抛出ValueError或使用assert失败后,接口返回500内部服务器错误,而非预期的422验证错误。示例代码如下:
import uvicorn from fastapi import FastAPI, Depends from pydantic import BaseModel, root_validator app = FastAPI(docs_url="/docs") class MyClass(BaseModel): my_string_field: str @root_validator def anything_not_named_just_validate(cls, values): my_string_field = values.get('my_string_field') if my_string_field != 'good': raise ValueError('not good') return values @app.post("/myendpoint") def scope(variable: MyClass = Depends()): return 'ok' uvicorn.run(app, host="0.0.0.0", port=8000)
执行curl -X POST http://localhost:8000/myendpoint?my_string_field=bad时返回500错误,不符合预期。
原因分析
FastAPI只会自动捕获**Pydantic原生的ValidationError**并返回422错误,普通的ValueError或AssertionError会被判定为服务器内部异常,返回500。另外,代码中用Depends()注入查询参数模型的写法可以简化,FastAPI支持直接将模型作为查询参数解析。
解决方法
方法1:改用Pydantic字段验证器(单个字段验证推荐)
如果只是验证单个字段,用field_validator更简洁,它会自动将内部抛出的普通异常包装成ValidationError,让FastAPI返回422错误:
import uvicorn from fastapi import FastAPI from pydantic import BaseModel, field_validator app = FastAPI(docs_url="/docs") class MyClass(BaseModel): my_string_field: str @field_validator('my_string_field') def validate_my_string(cls, value): if value != 'good': raise ValueError('not good') return value @app.post("/myendpoint") def scope(variable: MyClass): # 无需Depends,直接声明模型即可解析查询参数 return 'ok' uvicorn.run(app, host="0.0.0.0", port=8000)
方法2:在根验证器中抛出Pydantic原生ValidationError
如果必须使用根验证器(比如需要跨字段验证),要明确抛出Pydantic的ValidationError:
import uvicorn from fastapi import FastAPI from pydantic import BaseModel, root_validator, ValidationError app = FastAPI(docs_url="/docs") class MyClass(BaseModel): my_string_field: str @root_validator(skip_on_failure=True) def validate_all_fields(cls, values): my_string_field = values.get('my_string_field') if my_string_field != 'good': # 构造并抛出ValidationError raise ValidationError.from_exception_data( cls, [ { "loc": ("my_string_field",), "msg": "not good", "type": "value_error" } ] ) return values @app.post("/myendpoint") def scope(variable: MyClass): return 'ok' uvicorn.run(app, host="0.0.0.0", port=8000)
方法3:Pydantic v2+推荐:用Annotated结合验证函数
如果使用Pydantic v2版本,推荐用Annotated搭配BeforeValidator的写法,更符合新版规范:
import uvicorn from fastapi import FastAPI from pydantic import BaseModel, BeforeValidator, Annotated # 定义单独的验证函数 def check_good_string(value): if value != 'good': raise ValueError('not good') return value app = FastAPI(docs_url="/docs") class MyClass(BaseModel): my_string_field: Annotated[str, BeforeValidator(check_good_string)] @app.post("/myendpoint") def scope(variable: MyClass): return 'ok' uvicorn.run(app, host="0.0.0.0", port=8000)
验证结果
修改后执行curl -X POST http://localhost:8000/myendpoint?my_string_field=bad,会返回422错误,响应体包含清晰的验证错误信息:
{ "detail": [ { "loc": ["query", "my_string_field"], "msg": "not good", "type": "value_error" } ] }
内容的提问来源于stack exchange,提问作者mrc
相关产品推荐
相关产品推荐

