金字塔式:根据其他字段的值在验证器中将字段设置为无

时间:2019-01-03 13:59:36

标签: python pydantic

我将pydantic BaseModel与这样的验证器一起使用:

from datetime import date
from typing import List, Optional
from pydantic import BaseModel, BaseConfig, validator

class Model(BaseModel):
    class Config(BaseConfig):
        allow_population_by_alias = True
        fields = {
            "some_date": {
                "alias": "some_list"
            }
        }
    some_date: Optional[date]
    some_list: List[date]

    @validator("some_date", pre=True, always=True)
    def validate_date(cls, value):
        if len(value) < 2: # here value is some_list
            return None
        return value[0] # return the first value - let's assume it's a date string

# This reproduces the problem
m = Model(some_list=['2019-01-03'])

我想基于some_date的值来计算some_list的值,如果满足特定条件,则将其设为None

我的JSON从不包含字段some_date,它始终基于some_list填充,因此基于pre=True, always=True。但是,some_date的默认验证器将在我自定义的验证器之后运行 ,如果validate_date返回None,则验证器将失败。

有没有一种方法可以创建仅由另一个人计算并且仍然可以是Optional的字段?

3 个答案:

答案 0 :(得分:3)

如果您希望能够根据另一个字段动态修改字段,则可以使用values参数。它保留了之前的所有字段,并且要小心:顺序很重要。您可以使用validatorroot_validator来做到这一点。

使用validator

>>> from datetime import date
>>> from typing import List, Optional
>>> from pydantic import BaseModel, validator
>>> class Model(BaseModel):
        some_list: List[date]
        some_date: Optional[date]
    
        @validator("some_date", always=True)
        def validate_date(cls, value, values):
            if len(values["some_list"]) < 2:
                return None
            return values["some_list"][0]

>>> Model(some_list=['2019-01-03', '2020-01-03', '2021-01-03'])
Model(some_list=[datetime.date(2019, 1, 3), datetime.date(2020, 1, 3), datetime.date(2021, 1, 3)],
      some_date=datetime.date(2019, 1, 3))

但是正如我所说,如果交换some_listsome_date的顺序,您将拥有KeyError: 'some_list'

使用root_validator

另一种选择是使用root_validator。这些作用于所有字段:

>>> class Model(BaseModel):
        some_list: List[date]
        some_date: Optional[date]
    
        @root_validator
        def validate_date(cls, values):
            if not len(values["some_list"]) < 2:
                values["some_date"] = values["some_list"][0]
            return values

>>> Model(some_list=['2019-01-03', '2020-01-03', '2021-01-03'])
Model(some_list=[datetime.date(2019, 1, 3), datetime.date(2020, 1, 3), datetime.date(2021, 1, 3)],
      some_date=datetime.date(2019, 1, 3))

答案 1 :(得分:1)

我倾向于说这不是您尝试的方式。

您正确地指出,date的默认验证器在数据模型中的自定义validate_date之后被称为 。相关源代码可以在here: pydantic.fields.py中找到(截至2019年1月,git-hash:19320bf)。显然,验证器的级联不支持allow_none模式,有关详细信息,请参见pydantic.Field._apply_validators。更具体地说,永远不会针对None测试特定验证器的输出,而是在Model.validate的更上游进行测试。

从略读文档和pydantic的来源来看,我倾向于说pydantic的验证机制目前在验证功能中对类型转换(list -> datelist -> NoneType)的支持非常有限。如果您的用例有很好的论据,则可能需要请求该功能here

但是,退后一步,使用alias和标志allow_population_by_alias的方法(无论如何,不​​建议这样做,如documentation所述)似乎有点过载。 some_date仅作为some_list[0] if len(some_list) >= 2 else None的快捷方式,但绝不会独立于some_list进行设置。如果确实如此,为什么不选择以下更简单的选择呢?

class Model(BaseModel):
    some_list: List[date] = ...

    @property 
    def some_date(self):
        return None if len(self.some_list) < 2 else self.some_list[0]

答案 2 :(得分:1)

您应该能够根据pydantic docs使用values

您还可以将以下参数的任何子集添加到 签名(名称必须匹配):

values:包含 任何先前验证的字段的名称到值的映射

config: 模型配置

字段:正在验证的字段

** kwargs:如果提供,则将包括未在签名中明确列出的上述参数

@validator()
def set_value_to_zero(cls, v, values):
    # look up other value in values, set v accordingly.