简体   繁体   中英

Pydantic validations for extra fields that not defined in schema

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

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 wanna 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 achive either one of that?

You can use theextra 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", eg:

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)

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})

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM