Pydantic v2.14 was released on October 8th. You can install it now from PyPI:
pip install --upgrade pydantic
This release features the work of 13 external contributors and provides various new features, performance improvements, and bug fixes. Several minor changes (considered non-breaking changes according to our versioning policy) are also included in this release. Make sure to look into them before upgrading.
This release drops support for Python 3.9 and adds support for Python 3.15.
Highlights include:
You can see the full changelog on GitHub.
Quick Reference
New Features
Pydantic 2.14 officially supports Python 3.15, including most of the new features it introduces. These are covered first.
Stabilized MISSING sentinel
The MISSING sentinel, added as an experimental feature in 2.12,
is now stabilized and part of the main Pydantic API.
MISSING is a singleton indicating that a field value was not provided during validation. During serialization, any field with MISSING as a value is excluded from the output.
from pydantic import MISSING, BaseModel
class Configuration(BaseModel):
timeout: int | None | MISSING = MISSING
# configuration defaults, stored somewhere else:
defaults = {'timeout': 200}
conf = Configuration()
# `timeout` is excluded from the serialization output:
conf.model_dump()
#> {}
# The `MISSING` value doesn't appear in the JSON Schema:
Configuration.model_json_schema()['properties']['timeout']
#> {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'title': 'Timeout'}
# `is` can be used to discriminate between the sentinel and other values:
timeout = conf.timeout if conf.timeout is not MISSING else defaults['timeout']
PR reference: #13782.
Support for lazy imports
Lazy imports were introduced by PEP 810 in Python 3.15.
Lazy imports let models in separate modules reference each other without running into cyclic imports:
a.py:
lazy from .b import B
from pydantic import BaseModel
class A(BaseModel):
b: B | None = None
b.py:
lazy from .a import A
from pydantic import BaseModel
class B(BaseModel):
a: A | None = None
See the documentation for more details.
PR reference: #13776.
Support for TypeForm
typing.TypeForm was introduced by PEP 747 in Python 3.15, and is supported by
major static type checkers.
Pydantic now uses TypeForm in a number of places, in particular with TypeAdapter:
from typing import reveal_type
from pydantic import TypeAdapter
ta = TypeAdapter(int | str)
validated = ta.validate_python(1)
reveal_type(validated)
# In <= 2.13: Unknown
# In >= 2.14: int | str
Type checkers now infer the correct type for any type form passed to Pydantic (x | y unions, Annotated[...] forms, etc.), not just classes.
PR reference: #13459.
Support for frozendict
The frozendict type was introduced by PEP 814 in Python 3.15. Pydantic supports it,
and it validates and serializes like a regular dict. See the documentation
for more details.
PR reference: #13634.
__namespace__ argument for create_model()
create_model() now accepts a __namespace__ argument,
to add any attribute to the class namespace of the created model, such as validators, methods or computed fields:
from pydantic import computed_field, create_model
def full_name(self) -> str:
return f'{self.first_name} {self.last_name}'
UserModel = create_model(
'UserModel',
first_name=str,
last_name=str,
__namespace__={'full_name': computed_field(property(full_name))},
)
UserModel(first_name='John', last_name='Doe').model_dump()
#> {'first_name': 'John', 'last_name': 'Doe', 'full_name': 'John Doe'}
The existing __validators__ argument is limited to validators. It is now recommended to use __namespace__ instead, as __validators__ will be deprecated in v3.
PR reference: #13895.
New core schema types
Several supported types now have their own core schema,
meaning they are natively validated/serialized by the pydantic-core Rust component. Previously, Pydantic defined custom
Python validators for them, which were slower and didn't support constraints.
The following types were migrated:
fractions.Fractionin #13339.ZeroDivisionErrors during validation are now properly handled.- Named tuples in #13505. This fixes a number of issues related to validation.
collections.dequein #13757.collections.OrderedDictin #13796.collections.Counterin #13824.
Changes
This release contains some minor changes that may affect existing code. Make sure to go through them before upgrading.
JSON Schema changes
This release introduces a number of JSON Schema changes that may affect your generated model schemas.
Decimal pattern
In 2.12, #11987 added regex patterns in the JSON Schema for Decimal types. This caused a number of issues, as some users define validation contracts based on the generated schema.
For this reason, the pattern is no longer included by default. A
custom GenerateJsonSchema subclass
can be defined to enable the pattern again (see the documentation for more details).
PR reference: #13672.
TypeAdapter config
The configuration of the TypeAdapter is now used when generating the JSON Schema:
from pydantic import ConfigDict, TypeAdapter
ta = TypeAdapter(list[bytes], config=ConfigDict(ser_json_bytes='base64'))
ta.json_schema()
#> {'type': 'array', 'items': {'type': 'string', 'format': 'base64url'}}
PR reference: #13676.
Other JSON Schema changes
These changes are mostly bug fixes that make the JSON Schema more consistent with the validation/serialization behavior:
- Don't apply serialization temporal formats to validation JSON Schemas in #13711.
- Reflect
str_min_lengthandstr_max_lengthconfig in the JSON Schema in #13714. - Use the field name in validation JSON Schemas when
validate_by_aliasisFalsein #13717. - Fix config propagation of stdlib dataclasses and
TypedDicts in JSON Schema in #13891. - Don't apply
ser_json_timedeltato all datetime types in serialization inference in #13892. - Encode JSON Schema defaults with a consistent configuration in #13893.
model_config is no longer mutated
Prior to 2.14, the model_config attribute could
be mutated by Pydantic, e.g. to populate values from deprecated settings. This is no longer the case: model_config will always reflect what was
set by the user (merged with the configuration of parent classes):
from pydantic import BaseModel
class Base(BaseModel):
model_config = {'title': 'MyBase'}
class Model(Base):
model_config = {'populate_by_name': True}
Model.model_config
#> In <= 2.13: {'title': 'MyBase', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}
#> In >= 2.14: {'title': 'MyBase', 'populate_by_name': True}
PR reference: #13825.
Model signature with validate_by_alias=False
When validate_by_alias is set to False,
the generated __signature__ of models and Pydantic dataclasses now uses the field name instead of the alias, matching what validation actually accepts:
import inspect
from pydantic import BaseModel, ConfigDict, Field
class Model(BaseModel):
model_config = ConfigDict(validate_by_alias=False, validate_by_name=True)
my_field: int = Field(alias='myAlias')
inspect.signature(Model)
#> In <= 2.13: (*, myAlias: int) -> None
#> In >= 2.14: (*, my_field: int) -> None
Contributed by @jaideeppyne. PR reference: #13730.
multiple_of constraint
Using a non-positive value for multiple_of now raises an error when the model is defined. Previously, an unhandled runtime exception was raised during validation.
PR reference: #13862.
Numeric constraints in the pipeline API
In the experimental pipeline API, numeric constraints
(gt(), ge(), lt(), le() and multiple_of()) are now always applied natively by pydantic-core on numeric types. Previously, some of them were
applied as Python validators, e.g. when chaining several constraints or when the constraint value didn't match the validated type. As a result,
these constraints are now included in the JSON Schema, and validation errors are the same as for regular fields:
from typing import Annotated
from pydantic import TypeAdapter
from pydantic.experimental.pipeline import validate_as
ta = TypeAdapter(Annotated[int, validate_as(int).ge(1).le(100)])
ta.json_schema()
#> In <= 2.13: {'minimum': 1, 'type': 'integer'}
#> In >= 2.14: {'maximum': 100, 'minimum': 1, 'type': 'integer'}
ta.validate_python(200)
"""
In <= 2.13:
Value error, Expected <= 100 [type=value_error, input_value=200, input_type=int]
In >= 2.14:
Input should be less than or equal to 100 [type=less_than_equal, input_value=200, input_type=int]
"""
PR reference: #13516.
Invalid index_key in wrap serializers
The handler of wrap serializers takes an optional
second index_key argument, which must be an integer or a string. A previous docstring example wrongly passed the info argument instead,
which went unnoticed unless include or exclude was used. Such invalid values now raise an explicit error:
from pydantic import BaseModel, model_serializer
class Model(BaseModel):
a: int
@model_serializer(mode='wrap')
def ser_model(self, handler, info):
return handler(self, info) # should be `handler(self)`
Model(a=1).model_dump()
"""
In <= 2.13:
{'a': 1}
In >= 2.14:
PydanticSerializationError: Error calling function `ser_model`: TypeError: 'index_key' is expected to be an integer or a string, got 'SerializationInfo(...)'
"""
If you copied this pattern, remove the second argument when calling the handler.
PR reference: #13506.
Performance
2.14 includes several optimizations to schema generation. Most of them individually reduce model build time by 5–20% on our benchmarks, which adds up at import time for applications that define many models:
- Optimize type lookup logic in core schema generation in #13573.
- Refactor type references logic in #13643.
- Move schema gathering logic to
pydantic-corein #13725. - Improve performance of
FieldInfoconstruction in #13726. - Improve performance of
GenerateSchema.generate_schema()dispatching in #13614. - Avoid exponential core schema traversal in
gather_schemas_for_cleaning()in #13523. - Introduce micro-optimizations for model class building in #13540.
Ready to try the new features? Upgrade now with pip install --upgrade pydantic or uv add --upgrade pydantic and check out the full release notes. Have questions or feedback? Join the conversation on our community Slack.