Pydantic模型验证器内字段分配的验证

3
我有以下的pydantic模型定义。当我运行p = IntOrStr(value=True)时,我期望会失败,因为True是布尔值,它应该无法分配给__int____str__。请注意保留HTML标签。
class IntOrStr(BaseModel):
    __int: Optional[conint(strict=True, le=100, ge=10)] = None
    __str: Optional[constr(strict=True, max_length=64, min_length=10)] = None
    value: Any

    @validator("value")
    def value_must_be_int_or_str(cls, v):
        try:
            __int = v # no validation. not sure why?
            return v
        except ValidationError as e:
            print(str(e))

        try:
            __str = v # no validation. not sure why?
            return v
        except ValidationError as e:
            print(str(e))

        raise ValueError("error. value must be int or str")

    class Config:
        validate_assignment = True

有人知道为什么 __int = v__str = v 不会触发任何验证吗?
谢谢。
1个回答

5

这里存在相当多的问题。

命名空间

这与Pydantic无关,这只是对Python命名空间工作原理的误解:

在方法的命名空间中,__int__str只是局部变量。你所做的只是创建这些变量并为它们赋值,然后丢弃它们而不做任何处理。

它们与模型的字段/属性完全无关。

如果你想给类属性赋值,你需要执行以下操作:

class Foo:
    x: int = 0
    @classmethod
    def method(cls) -> None:
        cls.x = 42

但在这种情况下,这并不是您想要的,因为...

类与实例

验证器是一个类方法。这可以从第一个参数被命名为cls来看出,即使可以用@validator省略@classmethod修饰符。

因此,您永远无法在验证器中分配任何模型实例的任何字段的值,而不管validate_assignment配置如何。验证器仅处理提供给实例赋值的值。如果没有其他验证器干扰,则返回值可能会最终分配给实例。

如果您希望传递给一个字段的值影响到最终分配给其他字段的内容,则应使用@root_validator

验证器优先级

您需要考虑调用验证器的顺序。该顺序由字段定义的顺序决定。 (请参见文档

根验证器默认在字段验证器之后调用。因此,如果您希望根验证器所做的更改影响字段验证,则需要对其使用pre=True

下划线

Pydantic不会将名称以下划线开头的属性视为字段,这意味着它们不受验证。 如果您需要以下划线开头的字段名称,则必须使用别名

工作示例

所有在一起,您可能需要类似以下内容:

from typing import Any, Optional
from pydantic import BaseModel, Field, ValidationError, conint, constr, root_validator


class IntOrStr(BaseModel):
    a: Optional[
        conint(strict=True, le=100, ge=10)
    ] = Field(default=None, alias="__a")
    b: Optional[
        constr(strict=True, max_length=64, min_length=10)
    ] = Field(default=None, alias="__b")
    value: Any

    @root_validator(pre=True)
    def value_to_a_and_b(cls, values: dict[str, Any]) -> dict[str, Any]:
        value = values.get("value")
        values["__a"] = value
        values["__b"] = value
        return values


if __name__ == "__main__":
    try:
        IntOrStr(value=True)
    except ValidationError as e:
        print(e)

输出结果:

2 validation errors for IntOrStr
__a
  value is not a valid integer (type=type_error.integer)
__b
  str type expected (type=type_error.str)

请注意,在这种设置中,conintconstr 类型的默认字段验证器实际上会检测到错误。
此外,在这个简单的例子中,您不能手动设置 __a__b,因为这些值将始终被根验证器覆盖。但由于我不知道您的实际意图,所以我只是按照这样的方式进行设置,以触发您期望的验证错误。
希望这可以帮到您。

1
感谢您提供详细的答案,我学到了很多。 - Kenny Davidson

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