如何使 pydantic Field 使用 Type 接受子类?

Win*_*wer 12 python pydantic

我试图让一个 Pydantic 模型中的一个字段接受我单独定义的一组 BaseModel 派生类或子类中的任何一个。阅读此处的文档,我天真地执行了以下操作,但失败了;然后我意识到我误读了文档,并且在这种情况下“字段可能只接受类(而不是实例)”,而且该示例中的 Foo 和 Bar 并不是从 BaseModel 本身派生的(这很重要吗?) 。

我猜我从一开始就误解了这一点,所以我的问题是:是否有一种正确的方法可以完成我想要做的事情,而无需在子类上使用 Union,或者完全使用其他更好的方法?

额外问题:只能接受类而不接受实例的常见用例是什么?

再次:

from pydantic import BaseModel

from typing import Type, Union

class Foo(BaseModel):
    pass

class Bar(Foo):
    pass

class Baz(Foo):
    pass

class Container(BaseModel):
    some_foo: Type[Foo] # this fails
    # this will run successfully --> some_foo: Union[Bar, Baz]


b = Baz()
c = Container(some_foo = b)
# Traceback (most recent call last):
#   File "mre.py", line 20, in <module>
#     c = Container(some_foo = b)
#   File "pydantic/main.py", line 400, in pydantic.main.BaseModel.__init__
# pydantic.error_wrappers.ValidationError: 1 validation error for Container
# some_foo
#   subclass of Foo expected (type=type_error.subclass; expected_class=Foo)
Run Code Online (Sandbox Code Playgroud)

Pau*_*l P 8

这比看起来更棘手。

pydantic这是一个使用的验证器工作的解决方案,但也许有一种更“pydantic”的方法。

from pydantic import BaseModel, validator
from typing import Any

class Foo(BaseModel):
    pass

class Bar(Foo):
    pass

class Baz(Foo):
    pass

class NotFoo(BaseModel):
    pass

class Container(BaseModel):
    some_foo: Any

    @validator("some_foo")
    def validate_some_foo(cls, val):
        if issubclass(type(val), Foo):
            return val

        raise TypeError("Wrong type for 'some_foo', must be subclass of Foo")

b = Bar()
c = Container(some_foo=b)
# Container(some_foo=Bar())

n = NotFoo()
c = container(some_foo=n)

# Traceback (most recent call last):
#   File "/path/to/file.py", line 64, in <module>
#     c = Container(some_foo=n)
#   File "pydantic/main.py", line 400, in pydantic.main.BaseModel.__init__
# pydantic.error_wrappers.ValidationError: 1 validation error for Container
# some_foo
#   Wrong type for 'some_foo', must be subclass of Foo (type=type_error)
Run Code Online (Sandbox Code Playgroud)

请注意,自定义验证器必须引发ValueErrorTypeError(或其子类)才能pydantic正确重新引发ValudationError.

您可能希望将特定类(而不是该类的实例)作为字段类型的原因之一是当您希望使用该字段稍后使用该字段实例化某些内容时。

这是一个例子:

from pydantic import BaseModel
from typing import Optional, Type

class Foo(BaseModel):
    # x is NOT optional
    x: int

class Bar(Foo):
    y: Optional[str]

class Baz(Foo):
    z: Optional[bool]

class NotFoo(BaseModel):
    # a is NOT optional
    a: str

class ContainerForClass(BaseModel):
    some_foo_class: Type[Foo]

c = ContainerForClass(some_foo_class=Bar)


# At this point you know that you will use this class for something else
# and that x must be always provided and it must be an int:
d = c.some_foo_class(x=5, y="some string")
# Baz(x=5, z=None)


c = ContainerForClass(some_foo_class=Baz)

# Same here with x:
e = c.some_foo_class(x=6, z=True)
# Baz(x=6, z=True)


# Would't work with this:
c = ContainerForClass(some_foo_class=NotFoo)

# Traceback (most recent call last):
#   File "/path/to/file.py", line 98, in <module>
#     c = ContainerForClass(some_foo_class=NotFoo)
#   File "pydantic/main.py", line 400, in pydantic.main.BaseModel.__init__
# pydantic.error_wrappers.ValidationError: 1 validation error for ContainerForClass
# some_foo_class
#   subclass of Foo expected (type=type_error.subclass; expected_class=Foo
Run Code Online (Sandbox Code Playgroud)