Pydantic:根据其他字段的值在验证器中设置字段 None

Posted

技术标签:

【中文标题】Pydantic:根据其他字段的值在验证器中设置字段 None【英文标题】:Pydantic: Make field None in validator based on other field's value 【发布时间】:2019-05-30 03:08:16 【问题描述】:

我正在使用 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_list 的值计算some_date 的值,如果满足某个条件,则使其成为None

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

有没有办法创建这样一个只能由另一个人计算并且仍然可以是Optional的字段?

【问题讨论】:

@normanius 我的错,忘记了 allow...fields 在内部 Config 类中 顺便说一句,我正在尝试使用 python 3.6.1 和 pydantic 0.16.1 较新版本的 pydantic(从 0.20 开始)可以更好地处理您的用例。 【参考方案1】:

更新:正如其他人指出的那样,现在可以使用较新的版本 (>=0.20) 来完成。见this answer。 (旁注:即使是 OP 的代码现在也可以工作,但不使用别名会更好。)


从略读文档和 pydantic 的源代码来看,我倾向于说 pydantic 的验证机制目前对验证函数中的类型转换(list -&gt; datelist -&gt; NoneType)的支持非常有限。

但是,退后一步,您使用alias 和标志allow_population_by_alias 的方法似乎有点过载。 some_date 只需要作为some_list[0] if len(some_list) &gt;= 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】:

如果您希望能够根据另一个字段动态修改一个字段,您可以使用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))

【讨论】:

这应该是公认的答案,恕我直言:“order [声明属性] matters” - 遇到了这个鬼鬼祟祟的KeyError 【参考方案3】:

你应该可以根据pydantic docs使用values

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

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

配置: 模型配置

field:正在验证的字段

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

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

【讨论】:

以上是关于Pydantic:根据其他字段的值在验证器中设置字段 None的主要内容,如果未能解决你的问题,请参考以下文章

jQuery 用于根据下拉列表的更改值在 asp 中继器中设置背景颜色

pydantic学习与使用-7.字段顺序field-ordering

如何在 pydantic 中设置最大字符串字段长度约束?

根据数据表值在gridview中设置复选框

如何使用列表中的值作为 pydantic 验证器?

pydantic学习与使用-12.使用 Field 定制字段