Pydantic:验证嵌套模型

4

我在 Pydantic 中有一个嵌套模型。外层模型中有一个 root_validator 函数。我原本以为如果调用了外层根验证器,则内部模型将是有效的。但实际上不是这样。在下面的 MWE 中,我给内部模型提供了错误的字段名称,但外部验证器失败了:

from typing import List
from pydantic import BaseModel
from pydantic.class_validators import root_validator

class CarList(BaseModel):
    cars: List[str]
    colors: List[str]

class CarDealership(BaseModel):
    name: str
    cars: CarList

    @root_validator()
    def check_length(cls, v):
        cars_list = v.get("cars")

        if len(cars_list.cars) != len(cars_list.colors):
            raise ValueError("wrong length")
        
        return v


car_deal = {
    "cars": {
        "cars1": ["toyota", "tesla"], 
        "colors": ["white", "red"]
    }
}

CarDealership.parse_obj(car_deal)

The error I get is:

  File "test.py", line 17, in check_length
    if len(cars_list.cars) != len(cars_list.colors):
AttributeError: 'NoneType' object has no attribute 'cars'

我原本期望的是:

pydantic.error_wrappers.ValidationError: 1 validation error for CarList
cars
  field required (type=value_error.missing)

如何确保内部模型首先通过验证?

4个回答

3
问题在于,即使其他验证器失败了,也会调用root_validator。在这种情况下,pydantic的想法是收集所有错误,不在第一个错误上引发错误。
解决方案是在root_validator中设置skip_on_failure=True。结果,只有在其他字段和子模型有效时才会调用root_validator
通过这个更改,您将获得以下错误消息:
ValidationError: 2 validation errors for CarDealership
name
  field required (type=value_error.missing)
cars -> cars
  field required (type=value_error.missing)

如果您将字典更改为以下内容:
car_deal = {
    "name": "Billy",
    "cars": {
        "cars": ["toyota", "tesla", "vw"], 
        "colors": ["white", "red"]
    }
}

root_validator 现在已被调用,我们将收到预期的错误:

ValidationError: 1 validation error for CarDealership
__root__
  wrong length (type=value_error)

0
截至2023年(接近2024年),通过使用Pydantic和Pydantic-Settings的2.x版本(记得安装它),你只需按照以下步骤操作:
from pydantic import BaseModel, root_validator
from pydantic_settings import BaseSettings

class CarList(BaseModel):
    cars: List[str]
    colors: List[str]

class CarDealership(BaseModel):
    name: str
    cars: CarList

    @root_validator
    def check_length(cls, v):
        cars_list = v.get("cars")

        if len(cars_list.cars) != len(cars_list.colors):
            raise ValueError("wrong length")
        
        return v

你最终还可以给它一个默认值!请注意,PeopleInfo(子类)继承自BaseModel,而ClassInfo继承自BaseSettings。
此外,请记住每个字段都必须使用类型注解进行注释。

问题特别涉及到在外部模型上使用root_validator。最好的方式是,你的示例代码实际上与问题中的示例类匹配,这样更清楚地展示你的回答如何处理root_validator - undefined
好的,我已经更新了答案,使其与问题的类别相匹配。 - undefined

-1

更新:对外部类版本进行验证

from typing import List
from pydantic import BaseModel
from pydantic.class_validators import root_validator


class CarList(BaseModel):
    cars: List[str]
    colors: List[str]


class CarDealership(BaseModel):
    name: str
    cars_list: CarList

    @root_validator(pre=True)
    def check_length(cls, v):
        cars_list = v.get("cars_list")

        if not cars_list:
            return v
        cars = cars_list.get("cars")
        colors = cars_list.get("colors")
        if not isinstance(cars, list) or not isinstance(colors, list):
            return v
        if cars is not None and colors is not None and len(cars) != len(colors):
            raise ValueError("wrong length")

        return v

root_validator 默认 pre=False,内部模型已经验证,所以你得到了 v == {}

你可以在 CarList 中使用 check_length,并检查 carscolors 是否存在(它们已经被验证过了,如果失败将为 None)。

from typing import List
from pydantic import BaseModel
from pydantic.class_validators import root_validator


class CarList(BaseModel):
    cars: List[str]
    colors: List[str]

    @root_validator
    def check_length(cls, v):
        cars = v.get("cars")
        colors = v.get("colors")

        if cars is not None and colors is not None and len(cars) != len(colors):
            raise ValueError("wrong length")

        return v


class CarDealership(BaseModel):
    name: str
    car_list: CarList


car_deal_1 = {
    "name": "value",
    "car_list": {
        "cars1": ["toyota", "tesla"],
        "colors": ["white", "red"]
    }
}

CarDealership.parse_obj(car_deal_1)
# pydantic.error_wrappers.ValidationError: 1 validation error for CarDealership
# car_list -> cars
#   field required (type=value_error.missing)

car_deal_2 = {
    "name": "value",
    "car_list": {
        "cars": ["toyota", "tesla"],
        "colors": ["white", "red", "bule"]
    }
}

CarDealership.parse_obj(car_deal_2)
# pydantic.error_wrappers.ValidationError: 1 validation error for CarDealership
# car_list -> __root__
#   wrong length (type=value_error)

1
问题在于我想在外部类上进行验证,因为我想将内部类用于其他不需要此验证的目的。 - Gino

-1

基于 @YeJun 的回答,但假设您对需要使用内部类进行其他用途的回复,您可以创建一个中间类,在保留原始 CarList 类用于其他用途的同时进行验证:

from typing import List
from pydantic import BaseModel
from pydantic.class_validators import root_validator


class CarList(BaseModel):
    cars: List[str]
    colors: List[str]


# this class will have the same fields from CarList, but with validation
class CarListWithValidator(CarList):
    @root_validator
    def check_length(cls, v):
        cars = v.get("cars")
        colors = v.get("colors")

        if cars is not None and colors is not None and len(cars) != len(colors):
            raise ValueError("wrong length")

        return v


class CarDealership(BaseModel):
    name: str
    car_list: CarListWithValidator


car_deal_2 = {
    "name": "value",
    "car_list": {
        "cars": ["toyota", "tesla"],
        "colors": ["white", "red", "bule"]
    }
}

CarDealership.parse_obj(car_deal_2)
# pydantic.error_wrappers.ValidationError: 1 validation error for CarDealership
# car_list -> __root__
#   wrong length (type=value_error)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接