Pydantic 验证模式中未定义的额外字段

发布于 2025-01-20 19:08:40 字数 1037 浏览 2 评论 0 原文

我正在使用 pydantic 进行架构验证,当任何未定义的额外字段添加到架构中时,我想抛出错误。

from typing import Literal, Union

from pydantic import BaseModel, Field, ValidationError


class Cat(BaseModel):
    pet_type: Literal['cat']
    meows: int


class Dog(BaseModel):
    pet_type: Literal['dog']
    barks: float


class Lizard(BaseModel):
    pet_type: Literal['reptile', 'lizard']
    scales: bool


class Model(BaseModel):
    pet: Union[Cat, Dog, Lizard] = Field(..., discriminator='pet_type')
    n: int


print(Model(pet={'pet_type': 'dog', 'barks': 3.14, 'eats': 'biscuit'}, n=1))
""" try:
    Model(pet={'pet_type': 'dog'}, n=1)
except ValidationError as e:
    print(e) """

在上面的代码中,我添加了未定义的 eats 字段。应用 pydantic 验证并删除我定义的额外值作为响应。我想抛出一个错误,指出 eats is not allowed for Dog 或类似的内容。有什么办法可以实现这一点吗?

我们是否有机会直接提供输入而不是 pet 对象?
print(Model({'pet_type': 'dog', 'barks': 3.14, 'eats': 'biscuit', n=1})).我尝试不使用鉴别器,但缺少与pet_type相关的特定验证。有人可以指导我如何实现其中之一吗?

I am using pydantic for schema validations and I would like to throw an error when any extra field that isn't defined is added to a schema.

from typing import Literal, Union

from pydantic import BaseModel, Field, ValidationError


class Cat(BaseModel):
    pet_type: Literal['cat']
    meows: int


class Dog(BaseModel):
    pet_type: Literal['dog']
    barks: float


class Lizard(BaseModel):
    pet_type: Literal['reptile', 'lizard']
    scales: bool


class Model(BaseModel):
    pet: Union[Cat, Dog, Lizard] = Field(..., discriminator='pet_type')
    n: int


print(Model(pet={'pet_type': 'dog', 'barks': 3.14, 'eats': 'biscuit'}, n=1))
""" try:
    Model(pet={'pet_type': 'dog'}, n=1)
except ValidationError as e:
    print(e) """

In the above code, I have added the eats field which is not defined. The pydantic validations are applied and the extra values that I defined are removed in response. I want to throw an error saying eats is not allowed for Dog or something like that. Is there any way to achieve that?

And is there any chance that we can provide the input directly instead of the pet object?
print(Model({'pet_type': 'dog', 'barks': 3.14, 'eats': 'biscuit', n=1})). I tried without descriminator but those specific validations are missing related to pet_type. Can someone guide me how to achieve either one of that?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

南汐寒笙箫 2025-01-27 19:08:40

Pydantic v2

您可以使用额外 字段rel="noreferrer">model_config 类属性用于在模型初始化期间禁止额外属性(默认情况下,额外属性将被忽略 )。

例如:

from pydantic import BaseModel, ConfigDict

class Pet(BaseModel):
    model_config = ConfigDict(extra="forbid")

    name: str

data = {
    "name": "some name",
    "some_extra_field": "some value",
}

my_pet = Pet.model_validate(data)   # <- effectively the same as Pet(**pet_data)

将引发 ValidationError

ValidationError: 1 validation error for Pet
some_extra_field
  Extra inputs are not permitted [type=extra_forbidden, input_value='some value', input_type=str]
    For further information visit https://errors.pydantic.dev/2.7/v/extra_forbidden

当模型“嵌套”时也有效,例如:

class PetModel(BaseModel):
    my_pet: Pet
    n: int

pet_data = {
    "my_pet": {"name": "Some Name", "invalid_field": "some value"},
    "n": 5,
}

pet_model = PetModel.model_validate(pet_data)
# Effectively the same as
# pet_model = PetModel(my_pet={"name": "Some Name", "invalid_field": "some value"}, n=5)

将引发:

ValidationError: 1 validation error for PetModel
my_pet.invalid_field
  Extra inputs are not permitted [type=extra_forbidden, input_value='some value', input_type=str]
    For further information visit https://errors.pydantic.dev/2.7/v/extra_forbidden

NB: 如您所见,extra 的类型为 ExtraValues现在,并且它的值将由 ConfigDict 进行验证。这意味着不可能意外地为 extra 提供不受支持的值(例如输入错误),即 ConfigDict(extra="fordib") 之类的内容将失败并显示 < a href="https://docs.pydantic.dev/latest/api/pydantic_core/#pydantic_core.SchemaError" rel="noreferrer">SchemaError

Pydantic v1

您可以使用 extra 字段Config 类在模型初始化期间禁止额外的属性(默认情况下,额外的属性将被忽略)。

例如:

from pydantic import BaseModel, Extra

class Pet(BaseModel):
    name: str

    class Config:
        extra = Extra.forbid

data = {
    "name": "some name",
    "some_extra_field": "some value",
}

my_pet = Pet.parse_obj(data)   # <- effectively the same as Pet(**pet_data)

将引发 VaidationError

ValidationError: 1 validation error for Pet
some_extra_field
  extra fields not permitted (type=value_error.extra)

当模型“嵌套”时也有效,例如:

class PetModel(BaseModel):
    my_pet: Pet
    n: int

pet_data = {
    "my_pet": {"name": "Some Name", "invalid_field": "some value"},
    "n": 5,
}

pet_model = PetModel.parse_obj(pet_data)
# Effectively the same as
# pet_model = PetModel(my_pet={"name": "Some Name", "invalid_field": "some value"}, n=5)

将引发:

ValidationError: 1 validation error for PetModel
my_pet -> invalid_field
  extra fields not permitted (type=value_error.extra)

Pydantic v2

You can use the extra field in the model_config class attribute to forbid extra attributes during model initialisation (by default, additional attributes will be ignored).

For example:

from pydantic import BaseModel, ConfigDict

class Pet(BaseModel):
    model_config = ConfigDict(extra="forbid")

    name: str

data = {
    "name": "some name",
    "some_extra_field": "some value",
}

my_pet = Pet.model_validate(data)   # <- effectively the same as Pet(**pet_data)

will raise a ValidationError:

ValidationError: 1 validation error for Pet
some_extra_field
  Extra inputs are not permitted [type=extra_forbidden, input_value='some value', input_type=str]
    For further information visit https://errors.pydantic.dev/2.7/v/extra_forbidden

Works as well when the model is "nested", e.g.:

class PetModel(BaseModel):
    my_pet: Pet
    n: int

pet_data = {
    "my_pet": {"name": "Some Name", "invalid_field": "some value"},
    "n": 5,
}

pet_model = PetModel.model_validate(pet_data)
# Effectively the same as
# pet_model = PetModel(my_pet={"name": "Some Name", "invalid_field": "some value"}, n=5)

will raise:

ValidationError: 1 validation error for PetModel
my_pet.invalid_field
  Extra inputs are not permitted [type=extra_forbidden, input_value='some value', input_type=str]
    For further information visit https://errors.pydantic.dev/2.7/v/extra_forbidden

NB: As you can see, extra has the type ExtraValues now, and its value will get validated by ConfigDict. This means it's not possible to accidentally provide an unsupported value for extra (e.g. having a typo), i.e. something like ConfigDict(extra="fordib") will fail with a SchemaError.

Pydantic v1

You can use the extra field in the Config class to forbid extra attributes during model initialisation (by default, additional attributes will be ignored).

For example:

from pydantic import BaseModel, Extra

class Pet(BaseModel):
    name: str

    class Config:
        extra = Extra.forbid

data = {
    "name": "some name",
    "some_extra_field": "some value",
}

my_pet = Pet.parse_obj(data)   # <- effectively the same as Pet(**pet_data)

will raise a VaidationError:

ValidationError: 1 validation error for Pet
some_extra_field
  extra fields not permitted (type=value_error.extra)

Works as well when the model is "nested", e.g.:

class PetModel(BaseModel):
    my_pet: Pet
    n: int

pet_data = {
    "my_pet": {"name": "Some Name", "invalid_field": "some value"},
    "n": 5,
}

pet_model = PetModel.parse_obj(pet_data)
# Effectively the same as
# pet_model = PetModel(my_pet={"name": "Some Name", "invalid_field": "some value"}, n=5)

will raise:

ValidationError: 1 validation error for PetModel
my_pet -> invalid_field
  extra fields not permitted (type=value_error.extra)
国粹 2025-01-27 19:08:40

首选解决方案是使用 ConfigDict(参考文档):

from pydantic import BaseModel, ConfigDict

class Pet(BaseModel):
    model_config = ConfigDict(extra='forbid')

    name: str

Paul P 的回答 仍然有效(目前),但 Config 类已经在 pydantic v2.0 中已弃用。
另一个已弃用的解决方案是 pydantic.Extra.forbid。

上述方法的优点之一是可以进行类型检查。

The preferred solution is to use a ConfigDict (ref. the documentation):

from pydantic import BaseModel, ConfigDict

class Pet(BaseModel):
    model_config = ConfigDict(extra='forbid')

    name: str

Paul P's answer still works (for now), but the Config class has been deprecated in pydantic v2.0.
Another deprecated solution is pydantic.Extra.forbid.

One advantage of the method above is that it can be type checked.

贩梦商人 2025-01-27 19:08:40

Pydantic是为了通过模式来验证您的输入。在您的情况下,您要删除其验证功能之一。

我认为您应该创建一个从 basemodel 继承的新类

class ModifiedBaseModel(BaseModel):
    def __init__(__pydantic_self__, **data: Any) -> None:
        registered, not_registered = __pydantic_self__.filter_data(data)
        super().__init__(**registered)
        for k, v in not_registered.items():
            __pydantic_self__.__dict__[k] = v
    
    @classmethod
    def filter_data(cls, data):
        registered_attr = {}
        not_registered_attr = {}
        annots = cls.__annotations__
        for k, v in data.items():
            if k in annots:
                registered_attr[k] = v
            else:
                not_registered_attr[k] = v
        return registered_attr, not_registered_attr

,然后创建您的验证类

class Cat(ModifiedBaseModel):
    pet_type: Literal['cat']
    meows: int

,现在可以创建一个新的 cat ,而不必担心未定义的属性。像这个

my_cat = Cat(pet_type='cat', meows=3, name='blacky', age=3)

第二个问题一样,要直接从 dict 进行输入,您可以使用双星号 **

Dog(**my_dog_data_in_dict)

Dog(**{'pet_type': 'dog', 'barks': 3.14, 'eats': 'biscuit', n=1})

Pydantic is made to validate your input with the schema. In your case, you want to remove one of its validation feature.

I think you should create a new class that inherit from BaseModel

class ModifiedBaseModel(BaseModel):
    def __init__(__pydantic_self__, **data: Any) -> None:
        registered, not_registered = __pydantic_self__.filter_data(data)
        super().__init__(**registered)
        for k, v in not_registered.items():
            __pydantic_self__.__dict__[k] = v
    
    @classmethod
    def filter_data(cls, data):
        registered_attr = {}
        not_registered_attr = {}
        annots = cls.__annotations__
        for k, v in data.items():
            if k in annots:
                registered_attr[k] = v
            else:
                not_registered_attr[k] = v
        return registered_attr, not_registered_attr

then create your validation classes

class Cat(ModifiedBaseModel):
    pet_type: Literal['cat']
    meows: int

now you can create a new Cat without worries about undefined attribute. Like this

my_cat = Cat(pet_type='cat', meows=3, name='blacky', age=3)

2nd question, to put the input directly from dict you can use double asterisk **

Dog(**my_dog_data_in_dict)

or

Dog(**{'pet_type': 'dog', 'barks': 3.14, 'eats': 'biscuit', n=1})
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文