Pydantic v2.10 is now available! You can install it now from PyPI:
pip install --upgrade pydantic
This release features the work of over 30 contributors! In this post, we'll cover the highlights of the release. You can see the full changelog on GitHub.
For this release, we focused on adding a variety of new features and bug fixes. We'll be pivoting to a focus on performance improvements in the next release, v2.11.
Quick Reference:
- New Features
- Support for partial validation with `experimental_allow_partial
- Support default factories taking validated data as an argument
- Support for
typing.Unpack
with@validate_call
- Support compiled patterns in
protected_namespaces
- Support
defer_build
forTypeAdapter
and Pydanticdataclass
es - Support for
fractions.Fraction
- Incompatibility warnings for mixing v1 and v2 models
- Expose public
sort
method for JSON schema generation - Allow for subclassing of
ValidationError
andPydanticCustomError
- Performance Improvements
- Changes
- Relax
protected_namespace
config default - Deprecation of the
schema_generator
config argument - Use
b64decode
andb64encode
forBase64Bytes
type - Revalidate parametrized generics if instance's origin is subclass of original
- Migrate to subclassing instead of annotated approach for Pydantic URL types
- Tidy up JSON schema generation for
Literals
andEnum
s - Support dates all the way to 1BC
- Relax
New Features
Support for partial validation with experimental_allow_partial
Perhaps the most exciting new feature in v2.10 is support for partial validation. This allows you to validate an incomplete JSON string, or a Python object representing incomplete input data.
Partial validation is particularly helpful when processing the output of an LLM, where the model streams structured responses, and you may wish to begin validating the stream while you're still receiving data (e.g. to show partial data to users).
We've written extensive documentation for this partial feature.
Right now, support for this is only available for TypeAdapter
instances,
but support will likely be added for BaseModel
s and Pydantic dataclass
es soon.
from pydantic import TypeAdapter, BaseModel
from typing import Literal
class UserRecord(BaseModel):
id: int
name: str
role: Literal['admin', 'user']
ta = TypeAdapter(list[UserRecord])
# allow_partial if the input is a python object
d = ta.validate_python(
[
{'id': '1', 'name': 'Alice', 'role': 'user'},
{'id': '1', 'name': 'Ben', 'role': 'user'},
{'id': '1', 'name': 'Char'},
],
experimental_allow_partial=True,
)
print(d)
#> [UserRecord(id=1, name='Alice', role='user'), UserRecord(id=1, name='Ben', role='user')]
# allow_partial if the input is a json string
d = ta.validate_json(
'[{"id":"1","name":"Alice","role":"user"},{"id":"1","name":"Ben","role":"user"},{"id":"1","name":"Char"}]',
experimental_allow_partial=True,
)
print(d)
#> [UserRecord(id=1, name='Alice', role='user'), UserRecord(id=1, name='Ben', role='user')]
We're eager to get your feedback on this experimental feature so that we can finalize the API before migrating to first class support. If you have any questions or feedback, please open a GitHub discussion, or if you encounter any bugs, please open a GitHub issue.
PR reference: #10748
Support default factories taking validated data as an argument
Pydantic now supports default factories that take already validated data as an argument, so you can define dependent defaults.
For example:
from pydantic import BaseModel
class Model(BaseModel):
a: int = 1
b: int = Field(default_factory=lambda data: data['a'] * 2)
model = Model()
assert model.b == 2
Check out our dependent default factories to learn more.
PR reference: #10678
Support for typing.Unpack
with @validate_call
Pydantic supports the use of typing.Unpack
to specify variadic keyword arguments in
@validate_call
decorated functions.
Here's an example:
from typing import Required, TypedDict, Unpack
from pydantic import ValidationError, validate_call, with_config
@with_config({'strict': True})
class TD(TypedDict, total=False):
a: int
b: Required[str]
@validate_call
def foo(**kwargs: Unpack[TD]):
pass
foo(a=1, b='test')
foo(b='test')
try:
foo(a='1')
except ValidationError as e:
print(e)
"""
2 validation errors for foo
a
Input should be a valid integer [type=int_type, input_value='1', input_type=str]
For further information visit https://errors.pydantic.dev/2.10/v/int_type
b
Field required [type=missing, input_value={'a': '1'}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.10/v/missing
"""
foo
also has full type checking support here, so the invalid call to foo(a='1')
will be caught by your type checker as well.
Implementation: #10416
Support compiled patterns in protected_namespaces
We've implemented support for compiled patterns (regexes) in the protected_namespaces
setting.
This allows for more flexibility in defining protected namespaces, compared to the previous prefix-based approach.
This was added in conjunction with Relaxing the protected_namespace
config default.
import re
import warnings
from pydantic import BaseModel, ConfigDict
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter('always') # Catch all warnings
class Model(BaseModel):
safe_field: str
also_protect_field: str
protect_this: str
model_config = ConfigDict(
protected_namespaces=(
'protect_me_',
'also_protect_',
re.compile('^protect_this$'),
)
)
for warning in caught_warnings:
print(f'{warning.message}')
'''
Field "also_protect_field" in Model has conflict with protected namespace "also_protect_".
You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ('protect_me_', re.compile('^protect_this$'))`.
Field "protect_this" in Model has conflict with protected namespace "re.compile('^protect_this$')".
You may be able to resolve this warning by setting `model_config['protected_namespaces'] = ('protect_me_', 'also_protect_')`.
'''
More docs: ConfigDict.protected_namespaces
Implementation details: #10522
Support defer_build
for TypeAdapter
and Pydantic dataclass
es
defer_build
is a
Pydantic ConfigDict
setting that allows you to defer the building
of Pydantic core schemas, validators, and serializers until the first validation, or until manual building is triggered.
This can bring significant performance benefits for application startup time, especially for large applications with many models.
In v2.10, we've introduced support for this setting to Pydantic dataclasses
and TypeAdapter
instances.
Here's how you might defer schema building for a TypeAdapter
instance:
from pydantic import ConfigDict, TypeAdapter
ta = TypeAdapter('MyInt', config=ConfigDict(defer_build=True))
# some time later, the forward reference is defined
MyInt = int
ta.rebuild() # (1)!
assert ta.validate_python(1) == 1
- Manual rebuild triggered with the
rebuild
method, similar to themodel_rebuild
method forBaseModel
subclasses.
For more information, see the additional documentation below.
Support for fractions.Fraction
fractions.Fraction
is now supported as a first class type in Pydantic.
You can validate strings, fraction.Fraction
instances, and float
, int
, and decimal.Decimal
instances.
fractions.Fraction
types are serialized as strings to ensure round-trip safety.
from pydantic import TypeAdapter
from fractions import Fraction
from decimal import Decimal
fraction_adapter = TypeAdapter(Fraction)
assert fraction_adapter.validate_python('3/2') == Fraction(3, 2)
assert fraction_adapter.validate_python(Fraction(3, 2)) == Fraction(3, 2)
assert fraction_adapter.validate_python(Decimal('1.5')) == Fraction(3, 2)
assert fraction_adapter.validate_python(1.5) == Fraction(3, 2)
assert fraction_adapter.dump_python(Fraction(3, 2)) == '3/2'
For implementation details, see PR #10318
Incompatibility warnings for mixing v1 and v2 models
In #10431 and #10432, we've added more helpful warnings for users who mistakenly mix v1 and v2 models. This should make the v1 -> v2 migration process easier.
If you try to use a v1 model with a v2 model, you'll see a warning like this:
from pydantic import BaseModel as BaseModelV2
from pydantic.v1 import BaseModel as BaseModelV1
class V1Model(BaseModelV1):
...
class V2Model(BaseModelV2):
inner: V1Model
"""
UserWarning: Nesting V1 models inside V2 models is not supported. Please upgrade V1Model to V2.
"""
If you try to use a v2 model with a v1 model, you'll see a warning like this:
from pydantic import BaseModel as BaseModelV2
from pydantic.v1 import BaseModel as BaseModelV1
class V2Model(BaseModelV2):
...
class V1Model(BaseModelV1):
inner: V2Model
"""
UserWarning: Mixing V1 and V2 models is not supported. `V2Model` is a V2 model.
"""
Expose public sort
method for JSON schema generation
Pydantic supports customizing JSON schema generation
in a variety of ways, one of which being subclassing of the GenerateJsonSchema
class.
In this release, we've made the sort
method public, allowing you to
sort JSON schema keys in a custom way.
By default, we sort the JSON schema keys, excluding properties
, in order to maintain field order as defined in a model.
If you'd prefer to sort the keys in a different way, you can subclass GenerateJsonSchema
and override the sort
method.
Below, we skip sorting the schema values at all:
import json
from typing import Optional
from pydantic import BaseModel, Field
from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue
class MyGenerateJsonSchema(GenerateJsonSchema):
def sort(
self, value: JsonSchemaValue, parent_key: Optional[str] = None
) -> JsonSchemaValue:
"""No-op, we don't want to sort schema values at all."""
return value
class Bar(BaseModel):
c: str
b: str
a: str = Field(json_schema_extra={'c': 'hi', 'b': 'hello', 'a': 'world'})
json_schema = Bar.model_json_schema(schema_generator=MyGenerateJsonSchema)
print(json.dumps(json_schema, indent=2))
"""
{
"type": "object",
"properties": {
"c": {
"type": "string",
"title": "C"
},
"b": {
"type": "string",
"title": "B"
},
"a": {
"type": "string",
"c": "hi",
"b": "hello",
"a": "world",
"title": "A"
}
},
"required": [
"c",
"b",
"a"
],
"title": "Bar"
}
"""
PR reference: #10595
Allow for subclassing of ValidationError
and PydanticCustomError
You can now subclass ValidationError
and PydanticCustomError
to create custom error classes.
This can be helpful if you want to raise custom exceptions in your custom validators that might have different behavior than the default ValidationError
.
Implementation details: pydantic-core#1413
Performance Improvements
While we didn't emphasize performance improvements in this release as much as we have in the past, we did make some internal changes that should improve performance in some cases. Specifically, we made the following changes:
Changes
Relax protected_namespace
config default
Pydantic's ConfigDict
has a protected_namespaces
setting
that allows you to define a namespace of strings and/or patterns that prevent models from having fields with names that conflict with them.
Before v2.10, Pydantic used ('model_',)
as the default value for the protected_namespaces
config setting to
prevent collisions between model attributes and BaseModel
's own methods. This was changed in v2.10 given feedback that
this restriction was limiting in AI and data science contexts, where it is common to have fields with names like model_id
, model_input
, model_output
, etc.
Now, the default value is ('model_dump', 'model_validate',)
. We believe this is a good balance between preventing collisions with core BaseModel
methods
and allowing for more flexibility in model field naming.
For more details, see these docs. We've also added support for compiled patterns in this setting which enhances the flexibility of this feature.
Reference: PR #10441 and Issue #10315
Deprecation of the schema_generator
config argument
In versions prior to v2.10, we exposed a schema_generator
argument in Pydantic's ConfigDict
.
This argument served as a hook into customizing the GenerateJsonSchema
class. This argument was advertised as experimental + subject to change in minor releases.
As we look to further improve performance, we've decided it's best to once again make the core schema generator logic private so that we have flexibility to make significant changes in the name of performance.
We hope to once again make customization of this (or the resultant) class public, once the API is more stable and core schema building is more performant. If you're running into issues due to the deprecation of this config setting, we'd you to open a GitHub issue with your use case details. Thanks!
Reference: #10303
Use b64decode
and b64encode
for Base64Bytes
and Base64Str
types
In versions of Pydantic prior to v2.10, Base64Bytes
used base64.encodebytes
and base64.decodebytes
functions. According to the base64 documentation,
these methods are considered legacy implementation, and thus, Pydantic v2.10+ now uses the modern
base64.b64encode
and base64.b64decode
functions.
If you'd like to replicate the old behavior, see these docs for instructions.
PR: #10486
Revalidate parametrized generics if instance's origin is subclass of original class
This is more of a bug fix than a change, but it's worth noting here. This should result in more intuitive generic validation behavior than was active in previous versions.
Simply put, when using nested generic models, Pydantic sometimes performs revalidation in an attempt to produce the most intuitive validation result.
Specifically, if you have a field of type GenericModel[SomeType]
and you validate data like GenericModel[SomeCompatibleType]
against this field,
we will inspect the data, recognize that the input data is sort of a "loose" subclass of GenericMode
l, and revalidate the contained SomeCompatibleType
data.
This adds some validation overhead, but makes things more intuitive for cases like that shown below:
from typing import Any, Generic, TypeVar
from pydantic import BaseModel
T = TypeVar('T')
class GenericModel(BaseModel, Generic[T]):
a: T
class Model(BaseModel):
inner: GenericModel[Any]
print(repr(Model.model_validate(Model(inner=GenericModel[int](a=1)))))
#> Model(inner=GenericModel[Any](a=1))
See the "implementation details" section of these new docs for more details.
Implementation details: #10666
Migrate to subclassing instead of annotated approach for Pydantic url types
We've improved the behavior of Pydantic URL types by building them as concrete subclasses of a base URL type
as opposed to using Annotated
with UrlConstraints
to define URL variations.
This is better, as we can now:
- Define appropriate types for URL attributes (like
host
) based on the constraints for a given URL type - Properly initialize URL subclasses with validation against said constraints (can't safely do this with
Annotated
types) - Do proper
isinstance
checks on URL subclasses
For example:
from pydantic import AnyHttpUrl, AnyUrl, TypeAdapter
any_http_url = AnyHttpUrl('https://localhost')
assert isinstance(any_http_url, AnyUrl)
assert isinstance(any_http_url, AnyHttpUrl)
url = TypeAdapter(AnyUrl).validate_python(any_http_url)
assert url is any_http_url
Implementation: #10662
Tidy up JSON schema generation for Literals
and Enum
s
Here's what JSON schema generation for Literals
and Enum
s looked like in v2.10:
import json
from enum import Enum
from typing import Literal
from pydantic import BaseModel
class PrimaryColor(str, Enum):
red = 'red'
yellow = 'yellow'
blue = 'blue'
class Painting(BaseModel):
color: PrimaryColor
medium: Literal['canvas', 'paper']
name: Literal['my_painting']
print(json.dumps(Painting.model_json_schema(), indent=4))
"""
{
"$defs": {
"PrimaryColor": {
"enum": [
"red",
"yellow",
"blue"
],
"title": "PrimaryColor",
"type": "string"
}
},
"properties": {
"color": {
"$ref": "#/$defs/PrimaryColor"
},
"medium": {
"enum": [
"canvas",
"paper"
],
"title": "Medium",
"type": "string"
},
"name": {
"const": "my_painting",
"title": "Name",
"type": "string"
}
},
"required": [
"color",
"medium",
"name"
],
"title": "Painting",
"type": "object"
}
"""
Associated PR: #10692
Support dates all the way to 1BC
Simple as that!
from datetime import datetime
from pydantic import BaseModel
class Model(BaseModel):
dt: datetime
m = Model(dt='1000-01-01T00:00:00+00:00')
print(m.model_dump())
# > {'dt': datetime.datetime(1000, 1, 1, 0, 0, tzinfo=TzInfo(UTC))}
Implementation reference: pydantic/speedate#77
Conclusion
We're excited to share that Pydantic v2.10.0 is here, and it's the most feature-rich version of Pydantic yet. If you have any questions or feedback, please open a GitHub discussion. If you encounter any bugs, please open a GitHub issue.
Thank you to all of our contributors for making this release possible! We would especially like to acknowledge the following individuals for their significant contributions to this release:
Pydantic Logfire
If you're enjoying Pydantic, you might really like Pydantic Logfire, a new observability tool built by the team behind Pydantic. You can now try Logfire for free. We'd love it if you'd join the Pydantic Logfire Slack and let us know what you think!