Reputation: 2061
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?
Upvotes: 45
Views: 71650
Reputation: 3897
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
.
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)
Upvotes: 67
Reputation: 749
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.
Upvotes: 20
Reputation: 360
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})
Upvotes: 0