The long awaited Pydantic v2.12 is here! You can install it now from PyPI:
pip install --upgrade pydantic
This release features the work of over 20 external contributors and provides useful new features, along with initial Python 3.14 support. 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.
Highlights include:
- Support for Python 3.14
- A new
MISSING
sentinel - PEP 728 support – TypedDict with Typed Extra Items
- Preserve empty URL paths
You can see the full changelog on GitHub.
Quick Reference:
New Features
Support for Python 3.14
Python 3.14 ships with new type annotations semantics, introduced by PEP 649
and PEP 749. Type annotations are now lazily evaluated, dropping the need
to use string annotations (or
enabling this behavior per module using from __future__ import annotations
).
Here is an example demonstrating the new behavior:
class Model(BaseModel):
f: ForwardType # No quotes required.
type ForwardType = int
m = Model(f=1)
PR references:
MISSING
sentinel
A highly requested Pydantic feature is having the ability to differentiate between a default value and value provided during
the model creation, especially if None
has a specific meaning in the context it is used. Until now, Pydantic provided the
model_fields_set
property, but
its usage was proven difficult.
Pydantic 2.12 introduces an experimental MISSING
sentinel:
a singleton indicating 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 BaseModel
from pydantic.experimental.missing_sentinel import MISSING
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'}}
This feature is marked as experimental because it relies on the draft PEP 661, introducing sentinels in the standard library.
See the documentation for more details.
PR reference: #11883.
PEP 728 support – TypedDict with Typed Extra Items
Back in August, PEP 728 was accepted as part of the upcoming Python 3.15 release.
This PEP adds two class parameters, closed
and extra_items
to type the extra items on a TypedDict,
addressing the need to define closed TypedDict types or to type a subset of keys that might appear in a dict
while permitting additional items of a specified type.
from typing_extensions import TypedDict
from pydantic import TypeAdapter
class TD(TypedDict, extra_items=int):
a: str
ta = TypeAdapter(TD)
print(ta.validate_python({'a': 'test', 'extra': 1}))
#> {'a': 'test', 'extra': 1}
Such behavior was partially achievable using the extra
configuration value. It is now recommended to use PEP 728 capabilities.
Note that PEP 728 is currently only implemented in typing_extensions. Make sure to
import TypedDict
from there if you want to make use of this feature.
The additionalProperties
JSON Schema keyword
will be populated depending on the closed
/extra_items
specification (False
if closed, True
if extra_items
is Any
or object
,
matching the schema of the extra_items
type otherwise).
PR reference: #12179.
Preserve empty URL paths
The URL types had a behavior change in V2, where a trailing slash would be added if the path was empty. In many cases, this is unwanted.
Pydantic 2.12 adds a new url_preserve_empty_path
configuration value to opt-out of this behavior:
from pydantic import AnyUrl, BaseModel, ConfigDict
class Model(BaseModel):
u: AnyUrl
model_config = ConfigDict(url_preserve_empty_path=True)
print(Model(u='https://example.com').u)
#> Before: 'https://example/com/'
#> After: 'https://example/com'
This is also configurable per-field using the UrlConstraints
metadata class:
from typing import Annotated
from pydantic import TypeAdapter, UrlConstraints
ta = TypeAdapter(Annotated[AnyUrl, UrlConstraints(preserve_empty_path=True)])
print(ta.validate_python('https://example.com'))
#> https://example.com
This configuration value may default to True
in V3.
PR references:
Control validation behavior of timestamps
Pydantic uses to guess if a timestamp was provided in seconds or milliseconds for temporal types (such as datetime
or date
). The new val_temporal_unit
configuration value can now be used to force validation as seconds, milliseconds, or by inferring as before.
from datetime import datetime
from pydantic import BaseModel, ConfigDict
class Model(BaseModel):
d: datetime
model_config = ConfigDict(val_temporal_unit='milliseconds')
print(Model(d=datetime(1970, 4, 11, 19, 13).timestamp() * 1000))
#> d=datetime.datetime(1970, 4, 11, 18, 13)), would be somewhere around year 2245 in 'infer' mode.
A new ser_json_temporal
configuration
value is also introduced, generalizing the existing ser_json_timedelta
one.
Contributed by @ollz272. PR references:
exclude_if
field option
A new exclude_if
option was added, which can be used at the field level.
from pydantic import BaseModel, Field
class Transaction(BaseModel):
id: int
value: int = Field(ge=0, exclude_if=lambda v: v == 0)
print(Transaction(id=1, value=0).model_dump())
#> {'id': 1}
Contributed by @andresliszt. PR references:
ensure_ascii
JSON serialization option
A new ensure_ascii
option was added to the JSON serialization methods
(such as model_dump_json()
), to ensure non-ASCII characters will be Unicode-encoded. For backwards compatibility, this option defaults to False
.
from pydantic import TypeAdapter
ta = TypeAdapter(str)
ta.dump_json('🔥', ensure_ascii=True)
#> b'"\\ud83d\\udd25"'
PR reference: pydantic-core#1689.
extra
configuration per validation
It is now possible to control the extra
behavior per validation call:
from pydantic import BaseModel
class Model(BaseModel):
x: int
model_config = ConfigDict(extra='allow')
# Validates fine:
m = Model(x=1, y='a')
# Raises a validation error:
m = Model.model_validate({'x': 1, 'y': 'a'}, extra='forbid')
Contributed by @anvilpete. PR references:
Changes
This release contains some minor changes that may affect existing code. Make sure to go over through them before upgrading.
Error when using incompatible pydantic-core
versions
Pydantic is meant to work with one and only one pydantic-core
version. However, many users reported errors caused by an incompatible
pydantic-core
version being used. Starting in 2.12, Pydantic raises an explicit error at startup if this is the case. Note that most
dependency bots (such as GitHub's Dependabot) do not understand the pydantic-core
exact constraint,
which might be the source of such issues.
PR reference: #12196.
Remove warning for experimental features
Pydantic has a set of experimental features, exposed under the pydantic.experimental
module. Until now, a PydanticExperimentalWarning
was emitted whenever the module was imported. We decided to remove this warning, as the module
name already conveys that it is experimental. As such, it is no longer required to filter the warning
on import.
PR reference: #12265.
Field changes
Small changes and bug fixes affect Pydantic fields and the Field()
function.
-
Warning emitted when field-specific metadata is used in invalid contexts:
Using field-specific metadata (e.g.
alias
orexclude
) in invalid contexts will now raise a warning. Previously, it was silently ignored and was a common source of confusion. In particular, these two examples don't behave as expected:from typing import Annotated, Optional from pydantic import BaseModel, Field # ❌ `alias` can't be used on type aliases: type AliasedInt = Annotated[int, Field(alias='b')] class Model(BaseModel): a: AliasedInt # ❌ Instead use: Annotated[Optional[int], Field(exclude=True)] c: Optional[Annotated[int, Field(exclude=True)]]
(for type aliases, see more details in the documentation).
-
Refactor of the
FieldInfo
class:The
FieldInfo
class (created by theField()
function and used to store information about each field) underwent a complete refactor to fix many related bugs. While this shouldn't affect anything in theory, there is always a small change that it can break some edge cases. Do not hesitate to report any issues that may be occur from this refactor.As a result, the undocumented
FieldInfo.merge_field_infos()
is deprecated. If you made use of this function previously, please open an issue to discuss potential alternatives. -
Dataclasses fields inconsistencies:
Two small inconsistencies were found when mixing dataclasses and the Pydantic [
Field()
] function. These two bugs were fixed in 2.12, but may result in a behavior change in your code. The two issues can be found in issue #12045.
PR references:
JSON Schema changes
While not breaking, some JSON Schema changes in this release might break your tests if you make assertions on the generated JSON Schemas for your data. Here are the potential changes thay may affect you:
- Add regex patterns to JSON schema for
Decimal
type (contributed by @Dima-Bulavenko in #11987). - Respect custom title in functions JSON Schema (in #11892).
- When manually creating TypedDict schemas, the
extra_behavior
key is now used to populate theadditionalProperties
keyword.
After model validators
Model after validators are documented as being instance methods. However, an invalid signature used to accidentally be accepted, but could lead to undefined behavior. The following signature now raises an error:
class Model(BaseModel):
@model_validator(mode='after')
# This used to be converted into a classmethod, resulting
# in this inconsistent signature still accepted:
def validator(cls, model, info): ...
PR reference: #11957.
Mypy version support
Until now, Pydantic supported the mypy versions released less than 6 months ago to work with the mypy plugin. This incurred extra maintenance cost on our side, and as such we now only explicitly support the latest mypy released version.
PR reference: #11832.
Disable virtual subclassing capabilities on Pydantic models
The CPython implementation has a long-standing performance issue when defining a large
number of subclasses of an abstract base class (to be able to define abstract Pydantic models, the BaseModel
class uses
abc.ABCMeta
as a metaclass and as such is affected).
To work around this issue, Pydantic implemented partial optimization in isinstance()
/issubclass()
model checks. However, this ended up
breaking virtual subclasses (see the register()
method). The partial
optimization still resulted in performance and memory issues, and was not working correctly with some logic from the
unittest.mock
module.
As such, we restored the isinstance()
/issubclass()
behavior to how it is normally implemented, and using the
register()
method now raises a warning. Doing
so also improves validation performance slightly.
PR reference: #11669.