首页 > 解决方案 > 如何使用 Type 使 pydantic Field 接受子类?

问题描述

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

我猜我只是从一开始就误解了这一点,所以我的问题是:有没有正确的方法来做我想做的事情,而不是在子类上使用 Union,或者其他更好的方法?

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

MRE:

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)

标签: pythonpydantic

解决方案


这比看起来更棘手。

这是一个使用pydantic's验证器的解决方案,但也许有一个更“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)

请注意,自定义验证器必须引发 aValueErrorTypeError(或其子类)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

推荐阅读