One of the primary ways of defining schema in Pydantic is via models. Models are simply classes which inherit from
pydantic.BaseModel and define fields as annotated attributes.
You can think of models as similar to structs in languages like C, or as the requirements of a single endpoint
in an API.
Models share many similarities with Python's dataclasses, but have been designed with some subtle-yet-important
differences that streamline certain workflows related to validation, serialization, and JSON schema generation.
You can find more discussion of this in the Dataclasses section of the docs.
Untrusted data can be passed to a model and, after parsing and validation, Pydantic guarantees that the fields
of the resultant model instance will conform to the field types defined on the model.
We use the term "validation" to refer to the process of instantiating a model (or other type) that adheres to specified types and
constraints. This task, which Pydantic is well known for, is most widely recognized as "validation" in colloquial terms,
even though in other contexts the term "validation" may be more restrictive.
The potential confusion around the term "validation" arises from the fact that, strictly speaking, Pydantic's
primary focus doesn't align precisely with the dictionary definition of "validation":
noun
the action of checking or proving the validity or accuracy of something.
In Pydantic, the term "validation" refers to the process of instantiating a model (or other type) that adheres to specified
types and constraints. Pydantic guarantees the types and constraints of the output, not the input data.
This distinction becomes apparent when considering that Pydantic's ValidationError is raised
when data cannot be successfully parsed into a model instance.
While this distinction may initially seem subtle, it holds practical significance.
In some cases, "validation" goes beyond just model creation, and can include the copying and coercion of data.
This can involve copying arguments passed to the constructor in order to perform coercion to a new type
without mutating the original input data. For a more in-depth understanding of the implications for your usage,
refer to the Data Conversion and Attribute Copies sections below.
In essence, Pydantic's primary goal is to assure that the resulting structure post-processing (termed "validation")
precisely conforms to the applied type hints. Given the widespread adoption of "validation" as the colloquial term
for this process, we will consistently use it in our documentation.
While the terms "parse" and "validation" were previously used interchangeably, moving forward, we aim to exclusively employ "validate",
with "parse" reserved specifically for discussions related to JSON parsing.
name, which is a string and is not required (it has a default value).
user=User(id='123')
In this example, user is an instance of User.
Initialization of the object will perform all parsing and validation.
If no ValidationError is raised, you know the resulting model instance is valid.
assertuser.id==123assertisinstance(user.id,int)# Note that '123' was coerced to an int and its value is 123
More details on pydantic's coercion logic can be found in Data Conversion.
Fields of a model can be accessed as normal attributes of the user object.
The string '123' has been converted into an int as per the field type.
assertuser.name=='Jane Doe'
name wasn't set when user was initialized, so it has the default value.
assertuser.model_fields_set=={'id'}
The fields which were supplied when user was initialized.
Either .model_dump() or dict(user) will provide a dict of fields, but .model_dump() can take numerous other
arguments. (Note that dict(user) will not recursively convert nested models into dicts, but .model_dump() will.)
user.id=321assertuser.id==321
By default, models are mutable and field values can be changed through attribute assignment.
The model schema can be rebuilt using model_rebuild(). This is useful for building recursive generic models.
frompydanticimportBaseModel,PydanticUserErrorclassFoo(BaseModel):x:'Bar'try:Foo.model_json_schema()exceptPydanticUserErrorase:print(e)""" `Foo` is not fully defined; you should define `Bar`, then call `Foo.model_rebuild()`. For further information visit https://errors.pydantic.dev/2/u/class-not-fully-defined """classBar(BaseModel):passFoo.model_rebuild()print(Foo.model_json_schema())"""{ '$defs': {'Bar': {'properties': {}, 'title': 'Bar', 'type': 'object'}}, 'properties': {'x': {'$ref': '#/$defs/Bar'}}, 'required': ['x'], 'title': 'Foo', 'type': 'object',}"""
Pydantic tries to determine when this is necessary automatically and error if it wasn't done, but you may want to
call model_rebuild() proactively when dealing with recursive models or generics.
In V2, model_rebuild() replaced update_forward_refs() from V1. There are some slight differences with the new behavior.
The biggest change is that when calling model_rebuild() on the outermost model, it builds a core schema used for validation of the
whole model (nested models and all), so all types at all levels need to be ready before model_rebuild() is called.
Pydantic models can also be created from arbitrary class instances by reading the instance attributes corresponding
to the model field names. One common application of this functionality is integration with object-relational mappings
(ORMs).
To do this, set the config attribute model_config['from_attributes'] = True. See
Model Config and ConfigDict for more information.
The example here uses SQLAlchemy, but the same approach should work for any ORM.
fromtypingimportListfromsqlalchemyimportColumn,Integer,Stringfromsqlalchemy.dialects.postgresqlimportARRAYfromsqlalchemy.ormimportdeclarative_basefromtyping_extensionsimportAnnotatedfrompydanticimportBaseModel,ConfigDict,StringConstraintsBase=declarative_base()classCompanyOrm(Base):__tablename__='companies'id=Column(Integer,primary_key=True,nullable=False)public_key=Column(String(20),index=True,nullable=False,unique=True)name=Column(String(63),unique=True)domains=Column(ARRAY(String(255)))classCompanyModel(BaseModel):model_config=ConfigDict(from_attributes=True)id:intpublic_key:Annotated[str,StringConstraints(max_length=20)]name:Annotated[str,StringConstraints(max_length=63)]domains:List[Annotated[str,StringConstraints(max_length=255)]]co_orm=CompanyOrm(id=123,public_key='foobar',name='Testing',domains=['example.com','foobar.com'],)print(co_orm)#> <__main__.CompanyOrm object at 0x0123456789ab>co_model=CompanyModel.model_validate(co_orm)print(co_model)"""id=123 public_key='foobar' name='Testing' domains=['example.com', 'foobar.com']"""
You may want to name a Column after a reserved SQLAlchemy field. In that case, Field aliases will be
convenient:
importtypingimportsqlalchemyassafromsqlalchemy.ormimportdeclarative_basefrompydanticimportBaseModel,ConfigDict,FieldclassMyModel(BaseModel):model_config=ConfigDict(from_attributes=True)metadata:typing.Dict[str,str]=Field(alias='metadata_')Base=declarative_base()classSQLModel(Base):__tablename__='my_table'id=sa.Column('id',sa.Integer,primary_key=True)# 'metadata' is reserved by SQLAlchemy, hence the '_'metadata_=sa.Column('metadata',sa.JSON)sql_model=SQLModel(metadata_={'key':'val'},id=1)pydantic_model=MyModel.model_validate(sql_model)print(pydantic_model.model_dump())#> {'metadata': {'key': 'val'}}print(pydantic_model.model_dump(by_alias=True))#> {'metadata_': {'key': 'val'}}
Note
The example above works because aliases have priority over field names for
field population. Accessing SQLModel's metadata attribute would lead to a ValidationError.
Pydantic will raise ValidationError whenever it finds an error in the data it's validating.
A single exception of type ValidationError will be raised regardless of the number of errors found,
and that ValidationError will contain information about all of the errors and how they happened.
See Error Handling for details on standard and custom errors.
As a demonstration:
fromtypingimportListfrompydanticimportBaseModel,ValidationErrorclassModel(BaseModel):list_of_ints:List[int]a_float:floatdata=dict(list_of_ints=['1',2,'bad'],a_float='not a float',)try:Model(**data)exceptValidationErrorase:print(e)""" 2 validation errors for Model list_of_ints.2 Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='bad', input_type=str] a_float Input should be a valid number, unable to parse string as a number [type=float_parsing, input_value='not a float', input_type=str] """
Pydantic provides three classmethod helper functions on models for parsing data:
model_validate(): this is very similar to the __init__ method of the model, except it takes a dict or an object
rather than keyword arguments. If the object passed cannot be validated, or if it's not a dictionary
or instance of the model in question, a ValidationError will be raised.
model_validate_strings(): this takes a dict (can be nested) with string keys and values and validates the data in json mode so that said strings can be coerced into the correct types.
fromdatetimeimportdatetimefromtypingimportOptionalfrompydanticimportBaseModel,ValidationErrorclassUser(BaseModel):id:intname:str='John Doe'signup_ts:Optional[datetime]=Nonem=User.model_validate({'id':123,'name':'James'})print(m)#> id=123 name='James' signup_ts=Nonetry:User.model_validate(['not','a','dict'])exceptValidationErrorase:print(e)""" 1 validation error for User Input should be a valid dictionary or instance of User [type=model_type, input_value=['not', 'a', 'dict'], input_type=list] """m=User.model_validate_json('{"id": 123, "name": "James"}')print(m)#> id=123 name='James' signup_ts=Nonetry:m=User.model_validate_json('{"id": 123, "name": 123}')exceptValidationErrorase:print(e)""" 1 validation error for User name Input should be a valid string [type=string_type, input_value=123, input_type=int] """try:m=User.model_validate_json('invalid JSON')exceptValidationErrorase:print(e)""" 1 validation error for User Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='invalid JSON', input_type=str] """m=User.model_validate_strings({'id':'123','name':'James'})print(m)#> id=123 name='James' signup_ts=Nonem=User.model_validate_strings({'id':'123','name':'James','signup_ts':'2024-04-01T12:00:00'})print(m)#> id=123 name='James' signup_ts=datetime.datetime(2024, 4, 1, 12, 0)try:m=User.model_validate_strings({'id':'123','name':'James','signup_ts':'2024-04-01'},strict=True)exceptValidationErrorase:print(e)""" 1 validation error for User signup_ts Input should be a valid datetime, invalid datetime separator, expected `T`, `t`, `_` or space [type=datetime_parsing, input_value='2024-04-01', input_type=str] """
If you want to validate serialized data in a format other than JSON, you should load the data into a dict yourself and
then pass it to model_validate.
Note
Depending on the types and model configs involved, model_validate
and model_validate_json may have different validation behavior.
If you have data coming from a non-JSON source, but want the same validation
behavior and errors you'd get from model_validate_json,
our recommendation for now is to use either use model_validate_json(json.dumps(data)), or use model_validate_strings if the data takes the form of a (potentially nested) dict with string keys and values.
Note
Learn more about JSON parsing in the JSON section of the docs.
Note
If you're passing in an instance of a model to model_validate, you will want to consider setting
revalidate_instances
in the model's config. If you don't set this value, then validation will be skipped on model instances. See the below example:
frompydanticimportBaseModelclassModel(BaseModel):a:intm=Model(a=0)# note: the `model_config` setting validate_assignment=True` can prevent this kind of misbehaviorm.a='not an int'# doesn't raise a validation error even though m is invalidm2=Model.model_validate(m)
frompydanticimportBaseModel,ConfigDict,ValidationErrorclassModel(BaseModel):a:intmodel_config=ConfigDict(revalidate_instances='always')m=Model(a=0)# note: the `model_config` setting validate_assignment=True` can prevent this kind of misbehaviorm.a='not an int'try:m2=Model.model_validate(m)exceptValidationErrorase:print(e)""" 1 validation error for Model a Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='not an int', input_type=str] """
Pydantic also provides the model_construct() method, which allows models to be created without validation. This
can be useful in at least a few cases:
when working with complex data that is already known to be valid (for performance reasons)
when one or more of the validator functions are non-idempotent, or
when one or more of the validator functions have side effects that you don't want to be triggered.
Note
In Pydantic V2, the performance gap between BaseModel.__init__ and BaseModel.model_construct has been narrowed
considerably. For simple models, calling BaseModel.__init__ may even be faster. If you are using model_construct()
for performance reasons, you may want to profile your use case before assuming that model_construct() is faster.
Warning
model_construct() does not do any validation, meaning it can create models which are invalid. You should only
ever use the model_construct() method with data which has already been validated, or that you definitely trust.
frompydanticimportBaseModelclassUser(BaseModel):id:intage:intname:str='John Doe'original_user=User(id=123,age=32)user_data=original_user.model_dump()print(user_data)#> {'id': 123, 'age': 32, 'name': 'John Doe'}fields_set=original_user.model_fields_setprint(fields_set)#> {'age', 'id'}# ...# pass user_data and fields_set to RPC or save to the database etc.# ...# you can then create a new instance of User without# re-running validation which would be unnecessary at this point:new_user=User.model_construct(_fields_set=fields_set,**user_data)print(repr(new_user))#> User(id=123, age=32, name='John Doe')print(new_user.model_fields_set)#> {'age', 'id'}# construct can be dangerous, only use it with validated data!:bad_user=User.model_construct(id='dog')print(repr(bad_user))#> User(id='dog', name='John Doe')
The _fields_set keyword argument to model_construct() is optional, but allows you to be more precise about
which fields were originally set and which weren't. If it's omitted model_fields_set will just be the keys
of the data provided.
For example, in the example above, if _fields_set was not provided,
new_user.model_fields_set would be {'id', 'age', 'name'}.
Note that for subclasses of RootModel, the root value can be passed to model_construct()
positionally, instead of using a keyword argument.
Here are some additional notes on the behavior of model_construct():
When we say "no validation is performed" — this includes converting dicts to model instances. So if you have a field
with a Model type, you will need to convert the inner dict to a model yourself before passing it to
model_construct().
In particular, the model_construct() method does not support recursively constructing models from dicts.
If you do not pass keyword arguments for fields with defaults, the default values will still be used.
For models with private attributes, the __pydantic_private__ dict will be initialized the same as it would be when
calling __init__.
When constructing an instance using model_construct(), no __init__ method from the model or any of its parent
classes will be called, even when a custom __init__ method is defined.
On extra behavior with model_construct
For models with model_config['extra'] == 'allow', data not corresponding to fields will be correctly stored in
the __pydantic_extra__ dict and saved to the model's __dict__.
For models with model_config['extra'] == 'ignore', data not corresponding to fields will be ignored - that is,
not stored in __pydantic_extra__ or __dict__ on the instance.
Unlike a call to __init__, a call to model_construct with model_config['extra'] == 'forbid' doesn't raise an
error in the presence of data not corresponding to fields. Rather, said input data is simply ignored.
Pydantic supports the creation of generic models to make it easier to reuse a common model structure.
In order to declare a generic model, you perform the following steps:
Declare one or more typing.TypeVar instances to use to parameterize your model.
Declare a pydantic model that inherits from pydantic.BaseModel and typing.Generic,
where you pass the TypeVar instances as parameters to typing.Generic.
Use the TypeVar instances as annotations where you will want to replace them with other types or
pydantic models.
Here is an example using a generic BaseModel subclass to create an easily-reused HTTP response payload wrapper:
fromtypingimportGeneric,List,Optional,TypeVarfrompydanticimportBaseModel,ValidationErrorDataT=TypeVar('DataT')classDataModel(BaseModel):numbers:List[int]people:List[str]classResponse(BaseModel,Generic[DataT]):data:Optional[DataT]=Noneprint(Response[int](data=1))#> data=1print(Response[str](data='value'))#> data='value'print(Response[str](data='value').model_dump())#> {'data': 'value'}data=DataModel(numbers=[1,2,3],people=[])print(Response[DataModel](data=data).model_dump())#> {'data': {'numbers': [1, 2, 3], 'people': []}}try:Response[int](data='value')exceptValidationErrorase:print(e)""" 1 validation error for Response[int] data Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='value', input_type=str] """
If you set the model_config or make use of @field_validator or other Pydantic decorators in your generic model
definition, they will be applied to parametrized subclasses in the same way as when inheriting from a BaseModel
subclass. Any methods defined on your generic class will also be inherited.
Pydantic's generics also integrate properly with type checkers, so you get all the type checking
you would expect if you were to declare a distinct type for each parametrization.
Note
Internally, Pydantic creates subclasses of BaseModel at runtime when generic models are parametrized.
These classes are cached, so there should be minimal overhead introduced by the use of generics models.
To inherit from a generic model and preserve the fact that it is generic, the subclass must also inherit from
typing.Generic:
fromtypingimportGeneric,TypeVarfrompydanticimportBaseModelTypeX=TypeVar('TypeX')classBaseClass(BaseModel,Generic[TypeX]):X:TypeXclassChildClass(BaseClass[TypeX],Generic[TypeX]):# Inherit from Generic[TypeX]pass# Replace TypeX by intprint(ChildClass[int](X=1))#> X=1
You can also create a generic subclass of a BaseModel that partially or fully replaces the type parameters in the
superclass:
fromtypingimportGeneric,TypeVarfrompydanticimportBaseModelTypeX=TypeVar('TypeX')TypeY=TypeVar('TypeY')TypeZ=TypeVar('TypeZ')classBaseClass(BaseModel,Generic[TypeX,TypeY]):x:TypeXy:TypeYclassChildClass(BaseClass[int,TypeY],Generic[TypeY,TypeZ]):z:TypeZ# Replace TypeY by strprint(ChildClass[str,int](x='1',y='y',z='3'))#> x=1 y='y' z=3
If the name of the concrete subclasses is important, you can also override the default name generation:
When using a parametrized generic model as a type in another model (like product: ResponseModel[Product]),
make sure to parametrize said generic model when you initialize the model instance
(like response = ResponseModel[Product](content=product)). If you don't, a ValidationError will be raised, as
Pydantic doesn't infer the type of the generic model based on the data passed to it.
Using the same TypeVar in nested models allows you to enforce typing relationships at different points in your model:
fromtypingimportGeneric,TypeVarfrompydanticimportBaseModel,ValidationErrorT=TypeVar('T')classInnerT(BaseModel,Generic[T]):inner:TclassOuterT(BaseModel,Generic[T]):outer:Tnested:InnerT[T]nested=InnerT[int](inner=1)print(OuterT[int](outer=1,nested=nested))#> outer=1 nested=InnerT[int](inner=1)try:nested=InnerT[str](inner='a')print(OuterT[int](outer='a',nested=nested))exceptValidationErrorase:print(e)""" 2 validation errors for OuterT[int] outer Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str] nested Input should be a valid dictionary or instance of InnerT[int] [type=model_type, input_value=InnerT[str](inner='a'), input_type=InnerT[str]] """
When using bound type parameters, and when leaving type parameters unspecified, Pydantic treats generic models
similarly to how it treats built-in generic types like List and Dict:
If you don't specify parameters before instantiating the generic model, they are validated as the bound of the TypeVar.
If the TypeVars involved have no bounds, they are treated as Any.
Also, like List and Dict, any parameters specified using a TypeVar can later be substituted with concrete types:
fromtypingimportGeneric,TypeVarfrompydanticimportBaseModel,ValidationErrorAT=TypeVar('AT')BT=TypeVar('BT')classModel(BaseModel,Generic[AT,BT]):a:ATb:BTprint(Model(a='a',b='a'))#> a='a' b='a'IntT=TypeVar('IntT',bound=int)typevar_model=Model[int,IntT]print(typevar_model(a=1,b=1))#> a=1 b=1try:typevar_model(a='a',b='a')exceptValidationErrorasexc:print(exc)""" 2 validation errors for Model[int, TypeVar] a Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str] b Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str] """concrete_model=typevar_model[int]print(concrete_model(a=1,b=1))#> a=1 b=1
Warning
While it may not raise an error, we strongly advise against using parametrized generics in isinstance checks.
For example, you should not do isinstance(my_model, MyGenericModel[int]). However, it is fine to do isinstance(my_model, MyGenericModel). (Note that, for standard generics, it would raise an error to do a subclass check with a parameterized generic.)
If you need to perform isinstance checks against parametrized generics, you can do this by subclassing the parametrized generic class. This looks like class MyIntModel(MyGenericModel[int]): ... and isinstance(my_model, MyIntModel).
If a Pydantic model is used in a TypeVar bound and the generic type is never parametrized then Pydantic will use the bound for validation but treat the value as Any in terms of serialization:
fromtypingimportGeneric,Optional,TypeVarfrompydanticimportBaseModelclassErrorDetails(BaseModel):foo:strErrorDataT=TypeVar('ErrorDataT',bound=ErrorDetails)classError(BaseModel,Generic[ErrorDataT]):message:strdetails:Optional[ErrorDataT]classMyErrorDetails(ErrorDetails):bar:str# serialized as Anyerror=Error(message='We just had an error',details=MyErrorDetails(foo='var',bar='var2'),)asserterror.model_dump()=={'message':'We just had an error','details':{'foo':'var','bar':'var2',},}# serialized using the concrete parametrization# note that `'bar': 'var2'` is missingerror=Error[ErrorDetails](message='We just had an error',details=ErrorDetails(foo='var'),)asserterror.model_dump()=={'message':'We just had an error','details':{'foo':'var',},}
Here's another example of the above behavior, enumerating all permutations regarding bound specification and generic type parametrization:
fromtypingimportGenericfromtyping_extensionsimportTypeVarfrompydanticimportBaseModelTBound=TypeVar('TBound',bound=BaseModel)TNoBound=TypeVar('TNoBound')classIntValue(BaseModel):value:intclassItemBound(BaseModel,Generic[TBound]):item:TBoundclassItemNoBound(BaseModel,Generic[TNoBound]):item:TNoBounditem_bound_inferred=ItemBound(item=IntValue(value=3))item_bound_explicit=ItemBound[IntValue](item=IntValue(value=3))item_no_bound_inferred=ItemNoBound(item=IntValue(value=3))item_no_bound_explicit=ItemNoBound[IntValue](item=IntValue(value=3))# calling `print(x.model_dump())` on any of the above instances results in the following:#> {'item': {'value': 3}}
If you use a default=... (available in Python >= 3.13 or via typing-extensions) or constraints (TypeVar('T', str, int);
note that you rarely want to use this form of a TypeVar) then the default value or constraints will be used for both
validation and serialization if the type variable is not parametrized.
You can override this behavior using pydantic.SerializeAsAny:
fromtypingimportGeneric,Optionalfromtyping_extensionsimportTypeVarfrompydanticimportBaseModel,SerializeAsAnyclassErrorDetails(BaseModel):foo:strErrorDataT=TypeVar('ErrorDataT',default=ErrorDetails)classError(BaseModel,Generic[ErrorDataT]):message:strdetails:Optional[ErrorDataT]classMyErrorDetails(ErrorDetails):bar:str# serialized using the default's serializererror=Error(message='We just had an error',details=MyErrorDetails(foo='var',bar='var2'),)asserterror.model_dump()=={'message':'We just had an error','details':{'foo':'var',},}classSerializeAsAnyError(BaseModel,Generic[ErrorDataT]):message:strdetails:Optional[SerializeAsAny[ErrorDataT]]# serialized as Anyerror=SerializeAsAnyError(message='We just had an error',details=MyErrorDetails(foo='var',bar='baz'),)asserterror.model_dump()=={'message':'We just had an error','details':{'foo':'var','bar':'baz',},}
Note
Note, you may run into a bit of trouble if you don't parametrize a generic when the case of validating against the generic's bound
could cause data loss. See the example below:
There are some occasions where it is desirable to create a model using runtime information to specify the fields.
For this Pydantic provides the create_model function to allow models to be created on the fly:
Here StaticFoobarModel and DynamicFoobarModel are identical.
Fields are defined by one of the following tuple forms:
(<type>, <default value>)
(<type>, Field(...))
typing.Annotated[<type>, Field(...)]
Using a Field(...) call as the second argument in the tuple (the default value)
allows for more advanced field configuration. Thus, the following are analogous:
You can also add validators by passing a dict to the __validators__ argument.
frompydanticimportValidationError,create_model,field_validatordefusername_alphanumeric(cls,v):assertv.isalnum(),'must be alphanumeric'returnvvalidators={'username_validator':field_validator('username')(username_alphanumeric)}UserModel=create_model('UserModel',username=(str,...),__validators__=validators)user=UserModel(username='scolvin')print(user)#> username='scolvin'try:UserModel(username='scolvi%n')exceptValidationErrorase:print(e)""" 1 validation error for UserModel username Assertion failed, must be alphanumeric [type=assertion_error, input_value='scolvi%n', input_type=str] """
Pydantic models can be defined with a "custom root type" by subclassing pydantic.RootModel.
The root type can be any type supported by Pydantic, and is specified by the generic parameter to RootModel.
The root value can be passed to the model __init__ or model_validate
via the first and only argument.
If you want to access items in the root field directly or to iterate over the items, you can implement
custom __iter__ and __getitem__ functions, as shown in the following example.
Models can be configured to be immutable via model_config['frozen'] = True. When this is set, attempting to change the
values of instance attributes will raise errors. See the API reference for more details.
Note
This behavior was achieved in Pydantic V1 via the config setting allow_mutation = False.
This config flag is deprecated in Pydantic V2, and has been replaced with frozen.
Warning
In Python, immutability is not enforced. Developers have the ability to modify objects
that are conventionally considered "immutable" if they choose to do so.
frompydanticimportBaseModel,ConfigDict,ValidationErrorclassFooBarModel(BaseModel):model_config=ConfigDict(frozen=True)a:strb:dictfoobar=FooBarModel(a='hello',b={'apple':'pear'})try:foobar.a='different'exceptValidationErrorase:print(e)""" 1 validation error for FooBarModel a Instance is frozen [type=frozen_instance, input_value='different', input_type=str] """print(foobar.a)#> helloprint(foobar.b)#> {'apple': 'pear'}foobar.b['apple']='grape'print(foobar.b)#> {'apple': 'grape'}
Trying to change a caused an error, and a remains unchanged. However, the dict b is mutable, and the
immutability of foobar doesn't stop b from being changed.
To declare a field as required, you may declare it using an annotation, or an annotation in combination with a Field specification.
You may also use Ellipsis/... to emphasize that a field is required, especially when using the Field constructor.
The Field function is primarily used to configure settings like alias or description for an attribute.
The constructor supports Ellipsis/... as the sole positional argument.
This is used as a way to indicate that said field is mandatory, though it's the type hint that enforces this requirement.
Here a, b and c are all required. However, this use of b: int = ... does not work properly with
mypy, and as of v1.0 should be avoided in most cases.
Note
In Pydantic V1, fields annotated with Optional or Any would be given an implicit default of None even if no
default was explicitly specified. This behavior has changed in Pydantic V2, and there are no longer any type
annotations that will result in a field having an implicit default value.
See the migration guide for more details on changes
to required and nullable fields.
A common source of bugs in python is to use a mutable object as a default value for a function or method argument,
as the same instance ends up being reused in each call.
The dataclasses module actually raises an error in this case, indicating that you should use the default_factory
argument to dataclasses.field.
Pydantic also supports the use of a default_factory for non-hashable default
values, but it is not required. In the event that the default value is not hashable, Pydantic will deepcopy the default
value when creating each instance of the model:
When declaring a field with a default value, you may want it to be dynamic (i.e. different for each model).
To do this, you may want to use a default_factory.
Attributes whose name has a leading underscore are not treated as fields by Pydantic, and are not included in the
model schema. Instead, these are converted into a "private attribute" which is not validated or even set during
calls to __init__, model_validate, etc.
Note
As of Pydantic v2.1.0, you will receive a NameError if trying to use the Field function with a private attribute.
Because private attributes are not treated as fields, the Field() function cannot be applied.
Here is an example of usage:
fromdatetimeimportdatetimefromrandomimportrandintfrompydanticimportBaseModel,PrivateAttrclassTimeAwareModel(BaseModel):_processed_at:datetime=PrivateAttr(default_factory=datetime.now)_secret_value:strdef__init__(self,**data):super().__init__(**data)# this could also be done with default_factoryself._secret_value=randint(1,5)m=TimeAwareModel()print(m._processed_at)#> 2032-01-02 03:04:05.000006print(m._secret_value)#> 3
Private attribute names must start with underscore to prevent conflicts with model fields. However, dunder names
(such as __attr__) are not supported.
An accurate signature is useful for introspection purposes and libraries like FastAPI or hypothesis.
The generated signature will also respect custom __init__ functions:
importinspectfrompydanticimportBaseModelclassMyModel(BaseModel):id:intinfo:str='Foo'def__init__(self,id:int=1,*,bar:str,**data)->None:"""My custom init!"""super().__init__(id=id,bar=bar,**data)print(inspect.signature(MyModel))#> (id: int = 1, *, bar: str, info: str = 'Foo') -> None
To be included in the signature, a field's alias or name must be a valid Python identifier.
Pydantic will prioritize a field's alias over its name when generating the signature, but may use the field name if the
alias is not a valid Python identifier.
If a field's alias and name are both not valid identifiers (which may be possible through exotic use of create_model),
a **data argument will be added. In addition, the **data argument will always be present in the signature if
model_config['extra'] == 'allow'.
Pydantic supports structural pattern matching for models, as introduced by PEP 636 in Python 3.10.
frompydanticimportBaseModelclassPet(BaseModel):name:strspecies:stra=Pet(name='Bones',species='dog')matcha:# match `species` to 'dog', declare and initialize `dog_name`casePet(species='dog',name=dog_name):print(f'{dog_name} is a dog')#> Bones is a dog# default casecase_:print('No dog matched')
Note
A match-case statement may seem as if it creates a new model, but don't be fooled;
it is just syntactic sugar for getting an attribute and either comparing it or declaring and initializing it.
There are some situations where Pydantic does not copy attributes, such as when passing models — we use the
model as is. You can override this behaviour by setting
model_config['revalidate_instances'] = 'always'.
If you want this to raise an error, you can achieve this via model_config:
frompydanticimportBaseModel,ConfigDict,ValidationErrorclassModel(BaseModel):x:intmodel_config=ConfigDict(extra='forbid')try:Model(x=1,y='a')exceptValidationErrorasexc:print(exc)""" 1 validation error for Model y Extra inputs are not permitted [type=extra_forbidden, input_value='a', input_type=str] """
To instead preserve any extra data provided, you can set extra='allow'.
The extra fields will then be stored in BaseModel.__pydantic_extra__:
By default, no validation will be applied to these extra items, but you can set a type for the values by overriding
the type annotation for __pydantic_extra__:
fromtypingimportDictfrompydanticimportBaseModel,ConfigDict,Field,ValidationErrorclassModel(BaseModel):__pydantic_extra__:Dict[str,int]=Field(init=False)x:intmodel_config=ConfigDict(extra='allow')try:Model(x=1,y='a')exceptValidationErrorasexc:print(exc)""" 1 validation error for Model y Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str] """m=Model(x=1,y='2')assertm.x==1assertm.y==2assertm.model_dump()=={'x':1,'y':2}assertm.__pydantic_extra__=={'y':2}
The same configurations apply to TypedDict and dataclass' except the config is controlled by setting the
__pydantic_config__ attribute of the class to a valid ConfigDict.