Pydantic v2.13 was released on April 13th. You can install it now from PyPI:
pip install --upgrade pydantic
v2.13 also comes with new docs! A unified home for Pydantic Validation, Pydantic AI and Logfire.
This release features the work of 21 external contributors and provides various new features 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 contains the updated pydantic.v1 namespace, matching version 1.10.26 which includes support for Python 3.14.
Highlights include:
- New polymorphic serialization behavior
exclude_iffor computed fields- Validated data for default factories of private attributes
You can see the full changelog on GitHub.
Quick Reference:
New Features
Polymorphic serialization
In v2.12, the behavior of serialize_as_any was unified with the SerializeAsAny annotation.
This introduced unfortunate breakage for users relying on serialize_as_any to serialize model subclasses.
In this release, we introduced a new polymorphic serialization option, that applies specifically to model subclasses:
from pydantic import BaseModel
class User(BaseModel):
name: str
class UserLogin(User):
password: str
class OuterModel(BaseModel):
user: User
outer_model = OuterModel(
user=UserLogin(name='pydantic', password='password'),
)
print(outer_model.model_dump())
#> {'user': {'name': 'pydantic'}}
print(outer_model.model_dump(polymorphic_serialization=True))
#> {'user': {'name': 'pydantic', 'password': 'password'}}
In some way, polymorphic serialization can be seen as a subset of serialize as any, that only applies to Pydantic models and dataclasses (serialize as any applies to all kind of fields).
PR reference: #12518.
exclude_if support for computed fields
In the previous v2.12 version, a new exclude_if option was added for regular fields. This option is now available for computed fields:
from pydantic import BaseModel, computed_field
class Model(BaseModel):
cash_payments: int
card_payments: int
@computed_field(exclude_if=lambda v: v == 0)
def total_payments(self) -> int:
return self.cash_payments + self.card_payments
m = Model(cash_payments=0, card_payments=0)
m.model_dump()
#> {'cash_payments': 0, 'card_payments': 0}
Contributed by @andresliszt. PR reference: #12748.
Validated data for default factories of private attributes
In v2.10, default factories of regular fields gained the ability to take the validated data as an optional argument. This ability was extended to private attributes in this release:
from pydantic import BaseModel, PrivateAttr
class Model(BaseModel):
foo: int
_priv1: int = PrivateAttr(default=2)
_priv2: int = PrivateAttr(default_factory=lambda data: data['foo'] + data['_priv1'])
m = Model(foo=1)
m._priv2
#> 3
Contributed by @andresliszt. PR reference: #11685.
ascii_only string validation flag
A new ascii_only flag was added to the StringConstraints metadata:
from typing import Annotated
from pydantic import StringConstraints, TypeAdapter
ta = TypeAdapter(Annotated[str, StringConstraints(ascii_only=True)])
ta.validate_python('é')
"""
pydantic_core._pydantic_core.ValidationError: 1 validation error for constrained-str
String should contain only ASCII characters [type=string_not_ascii, input_value='é', input_type=str]
For further information visit https://errors.pydantic.dev/2.13/v/string_not_ascii
"""
Contributed by @ai-man-codes. PR reference: #12907.
Changes
This release contains some minor changes that may affect existing code. Make sure to go through them before upgrading.
Serialization of unions
Some fixes and optimizations were applied to the serialization of unions (including discriminated unions). This should result in more correct behavior and better performance, but could introduce unexpected regressions.
Tracking extra fields in model_fields_set
While extra fields set during validation were already tracked in
model_fields_set, this was not the case
if set after model instantiation (unlike regular fields):
from pydantic import BaseModel
class Model(BaseModel, extra='allow'):
a: int
b: int = 1
m = Model(a=1, extra_data='test')
m.model_fields_set
#> {'a', 'extra_data'}
m.b = 2
m.model_fields_set
#> {'a', 'b', 'extra_data'}
m.other_extra = 'test'
m.model_fields_set
#> In <= 2.12: {'a', 'b', 'extra_data'}
#> In >= 2.13: {'a', 'b', 'extra_data', 'other_extra'}
PR reference: #12817.
Shallow copies of root models
The behavior of shallow model_copy() on RootModel
has been changed to make a shallow copy of the root type.
The previous behavior was to only make a shallow copy of the root model instance, which wasn't really the behavior users would expect:
from pydantic import RootModel
class MyList(RootModel[list[int]]):
root: list[int]
m = MyList([1, 2])
copied = m.model_copy(deep=False) # shallow copy (the default)
copied.root is m.root
#> In <= 2.12: True
#> In >= 2.13: False
Contributed by @YassinNouh21. PR reference: #12679.
### Validation of @field_serializer field names
Up until now, the field names arguments to the @field_serializer decorator
were not validated, unlike the @field_validator.
If you don't provide any field name to the decorator, or arguments that are not strings, a PydanticUserError will now be raised.
Validation of complex and Decimal values
Prior to v2.13, complex Python values were validated using the following rules:
complexinstances are validated as-is.- Strings are validated using the
complex()constructor. - Numbers (integers and floats) are used as the real part.
Pydantic now defers validation to the complex() constructor, meaning it will
support the __complex__(), __float__()
or __index__() methods on the input being validated.
Similarly, Decimal values can now be validated as a three-tuple:
from decimal import Decimal
from pydantic import TypeAdapter
ta = TypeAdapter(Decimal)
ta.validate_python((0, (1, 4, 1, 4), -3))
#> Decimal('1.414')
ta.validate_json("[0, [1, 4, 1, 4], -3]")
#> Decimal('1.414')
Contributed by @tanmaymunjal. PR references: #12498, #12500.
NamedTuple subclasses
When using a subclass of an existing NamedTuple class, Pydantic was treating all annotations of this subclass as fields. In v2.13, Pydantic aligns with the Python behavior (which doesn't support fields
defined on subclasses), by checking the _fields attribute:
from typing import NamedTuple
from pydantic import TypeAdapter
class Foo(NamedTuple):
test: str
test2: str
class Bar(Foo):
test3: str
ta = TypeAdapter(Bar)
# Would work on <= 2.12, fails in >= 2.13:
ta.validate_python({'test': 'a', 'test2': 'b', 'test3': 'c'})
Defining such subclasses isn't a supported Python pattern for now, see python/typing#427.
PR reference: #12951.
Ready to try the new features? Upgrade now with pip install --upgrade pydantic or uv add --upgrade pydantic and check out the https😕/github.com/pydantic/pydantic/releases/tag/v2.13.0. Have questions or feedback? Join the conversation on our community Slack.