[英]Pydantic: Make field None in validator based on other field's value
我将 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
的值,如果满足某个条件,则将其some_list
None
。
我的 JSON 从不包含字段some_date
,它始终基于some_list
填充,因此pre=True, always=True
。 但是, some_date
的默认验证器将在我的自定义验证器之后运行,如果validate_date
返回None
,它将失败。
有没有办法创建这样一个仅由另一个计算并且仍然可以是Optional
的字段?
如果您希望能够根据另一个字段动态修改字段,则可以使用values
参数。 它包含所有先前的字段,并且小心:顺序很重要。 您可以使用validator
或root_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_list
和some_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))
更新:正如其他人指出的那样,现在可以使用较新的版本(> = 0.20)来完成。 看到这个答案。 (旁注:即使是 OP 的代码现在也可以工作,但不使用别名会更好。)
从略读文档和 pydantic 的来源来看,我倾向于说 pydantic 的验证机制目前对验证函数中的类型转换(
list -> date
,
list -> NoneType
)的支持非常有限。
但是,退后一步,您使用alias
和标志allow_population_by_alias
的方法似乎有点过载。 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]
您应该能够根据pydantic docs使用values
您还可以将以下参数的任何子集添加到签名中(名称必须匹配):
values:包含任何先前验证字段的名称到值映射的字典
配置:模型配置
field:正在验证的字段
**kwargs:如果提供,这将包括上述未在签名中明确列出的参数
@validator()
def set_value_to_zero(cls, v, values):
# look up other value in values, set v accordingly.
覆盖__init__
怎么样?
from datetime import date
from typing import List, Optional
from pydantic import BaseModel
class Model(BaseModel):
some_date: Optional[date]
some_list: List[date]
def __init__(self, *args, **kwargs):
# Modify the arguments
if len(kwargs['some_list']) < 2:
kwargs['some_date'] = None
else:
kwargs['some_date'] = kwargs['some_list'][0]
# Call parent's __init__
super().__init__(**kwargs)
Model(some_list=['2019-01-03', '2022-01-01'])
# Output: Model(some_date=datetime.date(2019, 1, 3), some_list=[datetime.date(2019, 1, 3), datetime.date(2022, 1, 1)])
请注意,如果您在创建后修改实例,则不会执行此验证。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.