在422 Unprocessable entity (FastAPI, Pydantic)的响应中显示无效详细信息的验证


class Foo(BaseModel):
template : str
body: FooBody
class Bar(BaseModel):
template : str
body: BarBody
class Xyz(BaseModel):
template : str
body: XyzBody
@router.post("/something", status_code=status.HTTP_200_OK)
async def get_pdf(
request: Request,
request_body: Union[Foo, Bar, Xyz],
):

在上面的代码片段中,我的主体可以是三种类型(任何一种),使用Union

对于给定的体类型,代码可以完美地工作。但是,如果缺少单个字段,即使只有一个字段缺少,422验证错误也会提供大量缺少的字段。

这可能是什么原因。还是我使用Union不正确?

我的目标是只允许提到的BaseModel (Foo, Bar, Xyz)如果我的请求检测到Foo并且请求中缺少某些字段,那么它应该只显示该字段,而不是显示Bar, Xyz和Foo

中缺少的所有字段最小可复制示例

from typing import Union
from fastapi import FastAPI
app = FastAPI(debug=True)
from fastapi import APIRouter, status
from pydantic import BaseModel

class FooBody(BaseModel):
foo1: str
foo2: int
foo3: str
class Foo(BaseModel):
temp: str
body: FooBody
class BarBody(BaseModel):
bar1: str
bar2: int
bar3: str
class Bar(BaseModel):
temp: str
body: BarBody
class XyzBody(BaseModel):
xyz1: str
xyz2: int
xyz3: str
class Xyz(BaseModel):
temp: str
body: XyzBody
@app.get("/type", status_code=status.HTTP_200_OK)
def health(response_body: Union[Foo, Bar, Xyz]):
return response_body

所以如果我使用

{
"temp": "xyz",
"body": {
"foo1": "ok",
"foo2": 1,
"foo3": "2"
}
}

按预期工作,但如果我在请求体中缺少一个参数,比如foo3,我不会得到验证错误,说缺少foo3,而是得到

{
"detail": [
{
"loc": [
"body",
"body",
"foo3"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"body",
"body",
"bar1"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"body",
"body",
"bar2"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"body",
"body",
"bar3"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"body",
"body",
"xyz1"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"body",
"body",
"xyz2"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"body",
"body",
"xyz3"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}

在Union中提到的所有类参数。

我是否使用Union错误?

我需要的是,它应该只接受我添加的类的主体,如果它检测到它的类Foo,那么它应该只检查类Foo中的验证,而不是整个东西。

我将尝试重新措辞并精简您的问题,因为它包含了许多与您遇到的验证的实际潜在问题完全无关的代码。


绝笔

问题实际上归结为:

from pydantic import BaseModel, ValidationError

class Foo(BaseModel):
foo1: str
foo2: int

class Bar(BaseModel):
bar1: bool
bar2: bytes

class Model(BaseModel):
data: Foo | Bar

def test(model: type[BaseModel], data: dict[str, object]) -> None:
try:
instance = model.parse_obj({"data": data})
except ValidationError as error:
print(error.json(indent=4))
else:
print(instance.json(indent=4))

if __name__ == "__main__":
incomplete_test_data = {"foo1": "a"}
valid_test_data = incomplete_test_data | {"foo2": 1}
test(Model, valid_test_data)
test(Model, incomplete_test_data)

第一个test调用的输出如预期:

{
"data": {
"foo1": "a",
"foo2": 1
}
}

但是第二个给了我们以下内容:

[
{
"loc": [
"data",
"foo2"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"data",
"bar1"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"data",
"bar2"
],
"msg": "field required",
"type": "value_error.missing"
}
]

这是而不是我们想要的。我们希望由第二次调用引起的验证错误能够识别应该通过Foo模型和进行验证。缺少foo2,因此它只包含一个实际错误:

[
{
"loc": [
"data",
"foo2"
],
"msg": "field required",
"type": "value_error.missing"
}
]

如何做到这一点?


这正是有歧视的联合的作用。它们也是OpenAPI规范的一部分。然而,正如文档所示,一个有区别的联合需要一个鉴别符字段,以添加到该联合中的每个模型。

如下图所示:

from typing import Literal
from pydantic import BaseModel, Field, ValidationError

...

class FooDisc(BaseModel):
data_type: Literal["foo"]
foo1: str
foo2: int

class BarDisc(BaseModel):
data_type: Literal["bar"]
bar1: bool
bar2: bytes

class ModelDisc(BaseModel):
data: FooDisc | BarDisc = Field(..., discriminator="data_type")

if __name__ == "__main__":
...
incomplete_test_data = {
"data_type": "foo",
"foo1": "a",
}
valid_test_data = incomplete_test_data | {"foo2": 1}
test(ModelDisc, valid_test_data)
test(ModelDisc, incomplete_test_data)
现在第一个test调用的输出是:
{
"data": {
"data_type": "foo",
"foo1": "a",
"foo2": 1
}
}

第二个调用给出如下内容:

[
{
"loc": [
"data",
"FooDisc",
"foo2"
],
"msg": "field required",
"type": "value_error.missing"
}
]

正如链接的Pydantic文档所示,使用区分联合的多个模型和更复杂/嵌套的结构也是可能的。

虽然添加的字段看起来很烦人,但您需要认识到这是唯一通常可靠的方式来传达要使用哪个模型/模式。如果您想要适应您的特定情况而不使用鉴别器,您总是可以在包含(常规)联合的模型上使用pre=True编写自己的验证器,并尝试基于(例如)在传递给该验证器的字典中找到的键来解析该验证器中该字段的数据。但我不建议这样做,因为它会给错误带来很大的空间。歧视工会的出现是有原因的,而这个问题正是这个原因。

最新更新