diff --git a/bump-pydantic-core-2.35.1.patch b/bump-pydantic-core-2.35.1.patch deleted file mode 100644 index 18f11dd..0000000 --- a/bump-pydantic-core-2.35.1.patch +++ /dev/null @@ -1,424 +0,0 @@ -From 4494c31a4834bdc2301cfa3d94f4bbc62c2774dc Mon Sep 17 00:00:00 2001 -From: Viicos <65306057+Viicos@users.noreply.github.com> -Date: Wed, 11 Jun 2025 14:52:26 +0200 -Subject: [PATCH] Bump `pydantic-core` to v2.35.1 - -Make use of `ensure_ascii` option -Update typechecking tests -Remove core schema validation hook ---- - docs/api/standard_library_types.md | 2 +- - docs/why.md | 2 +- - pydantic/_internal/_core_utils.py | 8 - - pydantic/_internal/_generate_schema.py | 5 +- - pydantic/functional_serializers.py | 4 +- - pydantic/functional_validators.py | 10 +- - pydantic/main.py | 4 + - pydantic/type_adapter.py | 3 + - pydantic/version.py | 2 +- - pyproject.toml | 4 +- - tests/typechecking/decorators.py | 79 +++++++-- - 12 files changed, 203 insertions(+), 140 deletions(-) - -Index: pydantic-2.11.7/docs/api/standard_library_types.md -=================================================================== ---- pydantic-2.11.7.orig/docs/api/standard_library_types.md -+++ pydantic-2.11.7/docs/api/standard_library_types.md -@@ -81,7 +81,7 @@ event = Event(dt='2032-04-23T10:20:30.40 - - print(event.model_dump()) - """ --{'dt': datetime.datetime(2032, 4, 23, 10, 20, 30, 400000, tzinfo=TzInfo(+02:30))} -+{'dt': datetime.datetime(2032, 4, 23, 10, 20, 30, 400000, tzinfo=TzInfo(9000))} - """ - ``` - -Index: pydantic-2.11.7/docs/why.md -=================================================================== ---- pydantic-2.11.7.orig/docs/why.md -+++ pydantic-2.11.7/docs/why.md -@@ -363,7 +363,7 @@ Functional validators and serializers, a - - - print(Meeting(when='2020-01-01T12:00+01:00')) -- #> when=datetime.datetime(2020, 1, 1, 12, 0, tzinfo=TzInfo(+01:00)) -+ #> when=datetime.datetime(2020, 1, 1, 12, 0, tzinfo=TzInfo(3600)) - print(Meeting(when='now')) - #> when=datetime.datetime(2032, 1, 2, 3, 4, 5, 6) - print(Meeting(when='2020-01-01T12:00')) -Index: pydantic-2.11.7/pydantic/_internal/_core_utils.py -=================================================================== ---- pydantic-2.11.7.orig/pydantic/_internal/_core_utils.py -+++ pydantic-2.11.7/pydantic/_internal/_core_utils.py -@@ -1,12 +1,10 @@ - from __future__ import annotations - - import inspect --import os - from collections.abc import Mapping, Sequence - from typing import TYPE_CHECKING, Any, Union - - from pydantic_core import CoreSchema, core_schema --from pydantic_core import validate_core_schema as _validate_core_schema - from typing_extensions import TypeGuard, get_args, get_origin - from typing_inspection import typing_objects - -@@ -109,12 +107,6 @@ def get_ref(s: core_schema.CoreSchema) - - return s.get('ref', None) - - --def validate_core_schema(schema: CoreSchema) -> CoreSchema: -- if os.getenv('PYDANTIC_VALIDATE_CORE_SCHEMAS'): -- return _validate_core_schema(schema) -- return schema -- -- - def _clean_schema_for_pretty_print(obj: Any, strip_metadata: bool = True) -> Any: # pragma: no cover - """A utility function to remove irrelevant information from a core schema.""" - if isinstance(obj, Mapping): -Index: pydantic-2.11.7/pydantic/_internal/_generate_schema.py -=================================================================== ---- pydantic-2.11.7.orig/pydantic/_internal/_generate_schema.py -+++ pydantic-2.11.7/pydantic/_internal/_generate_schema.py -@@ -70,7 +70,6 @@ from ._core_utils import ( - get_ref, - get_type_ref, - is_list_like_schema_with_items_schema, -- validate_core_schema, - ) - from ._decorators import ( - Decorator, -@@ -666,9 +665,7 @@ class GenerateSchema: - return schema - - def clean_schema(self, schema: CoreSchema) -> CoreSchema: -- schema = self.defs.finalize_schema(schema) -- schema = validate_core_schema(schema) -- return schema -+ return self.defs.finalize_schema(schema) - - def _add_js_function(self, metadata_schema: CoreSchema, js_function: Callable[..., Any]) -> None: - metadata = metadata_schema.get('metadata', {}) -Index: pydantic-2.11.7/pydantic/functional_serializers.py -=================================================================== ---- pydantic-2.11.7.orig/pydantic/functional_serializers.py -+++ pydantic-2.11.7/pydantic/functional_serializers.py -@@ -300,7 +300,7 @@ def field_serializer( - if TYPE_CHECKING: - # The first argument in the following callables represent the `self` type: - -- ModelPlainSerializerWithInfo: TypeAlias = Callable[[Any, SerializationInfo], Any] -+ ModelPlainSerializerWithInfo: TypeAlias = Callable[[Any, SerializationInfo[Any]], Any] - """A model serializer method with the `info` argument, in `plain` mode.""" - - ModelPlainSerializerWithoutInfo: TypeAlias = Callable[[Any], Any] -@@ -309,7 +309,7 @@ if TYPE_CHECKING: - ModelPlainSerializer: TypeAlias = 'ModelPlainSerializerWithInfo | ModelPlainSerializerWithoutInfo' - """A model serializer method in `plain` mode.""" - -- ModelWrapSerializerWithInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler, SerializationInfo], Any] -+ ModelWrapSerializerWithInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler, SerializationInfo[Any]], Any] - """A model serializer method with the `info` argument, in `wrap` mode.""" - - ModelWrapSerializerWithoutInfo: TypeAlias = Callable[[Any, SerializerFunctionWrapHandler], Any] -Index: pydantic-2.11.7/pydantic/functional_validators.py -=================================================================== ---- pydantic-2.11.7.orig/pydantic/functional_validators.py -+++ pydantic-2.11.7/pydantic/functional_validators.py -@@ -332,7 +332,7 @@ if TYPE_CHECKING: - def __call__(self, cls: Any, value: Any, /) -> Any: ... - - class _V2ValidatorClsMethod(Protocol): -- def __call__(self, cls: Any, value: Any, info: _core_schema.ValidationInfo, /) -> Any: ... -+ def __call__(self, cls: Any, value: Any, info: core_schema.ValidationInfo[Any], /) -> Any: ... - - class _OnlyValueWrapValidatorClsMethod(Protocol): - def __call__(self, cls: Any, value: Any, handler: _core_schema.ValidatorFunctionWrapHandler, /) -> Any: ... -@@ -343,7 +343,7 @@ if TYPE_CHECKING: - cls: Any, - value: Any, - handler: _core_schema.ValidatorFunctionWrapHandler, -- info: _core_schema.ValidationInfo, -+ info: core_schema.ValidationInfo[Any], - /, - ) -> Any: ... - -@@ -559,7 +559,7 @@ class ModelWrapValidator(Protocol[_Model - # thus validators _must_ handle all cases - value: Any, - handler: ModelWrapValidatorHandler[_ModelType], -- info: _core_schema.ValidationInfo, -+ info: core_schema.ValidationInfo[Any], - /, - ) -> _ModelType: ... - -@@ -604,7 +604,7 @@ class FreeModelBeforeValidator(Protocol) - # or anything else that gets passed to validate_python - # thus validators _must_ handle all cases - value: Any, -- info: _core_schema.ValidationInfo, -+ info: core_schema.ValidationInfo[Any], - /, - ) -> Any: ... - -@@ -619,7 +619,7 @@ class ModelBeforeValidator(Protocol): - # or anything else that gets passed to validate_python - # thus validators _must_ handle all cases - value: Any, -- info: _core_schema.ValidationInfo, -+ info: core_schema.ValidationInfo[Any], - /, - ) -> Any: ... - -@@ -629,7 +629,7 @@ ModelAfterValidatorWithoutInfo = Callabl - have info argument. - """ - --ModelAfterValidator = Callable[[_ModelType, _core_schema.ValidationInfo], _ModelType] -+ModelAfterValidator = Callable[[_ModelType, core_schema.ValidationInfo[Any]], _ModelType] - """A `@model_validator` decorated function signature. This is used when `mode='after'`.""" - - _AnyModelWrapValidator = Union[ModelWrapValidator[_ModelType], ModelWrapValidatorWithoutInfo[_ModelType]] -Index: pydantic-2.11.7/pydantic/main.py -=================================================================== ---- pydantic-2.11.7.orig/pydantic/main.py -+++ pydantic-2.11.7/pydantic/main.py -@@ -480,6 +480,7 @@ class BaseModel(metaclass=_model_constru - self, - *, - indent: int | None = None, -+ ensure_ascii: bool = False, - include: IncEx | None = None, - exclude: IncEx | None = None, - context: Any | None = None, -@@ -499,6 +500,8 @@ class BaseModel(metaclass=_model_constru - - Args: - indent: Indentation to use in the JSON output. If None is passed, the output will be compact. -+ ensure_ascii: If `True`, the output is guaranteed to have all incoming non-ASCII characters escaped. -+ If `False` (the default), these characters will be output as-is. - include: Field(s) to include in the JSON output. - exclude: Field(s) to exclude from the JSON output. - context: Additional context to pass to the serializer. -@@ -519,6 +522,7 @@ class BaseModel(metaclass=_model_constru - return self.__pydantic_serializer__.to_json( - self, - indent=indent, -+ ensure_ascii=ensure_ascii, - include=include, - exclude=exclude, - context=context, -Index: pydantic-2.11.7/pydantic/type_adapter.py -=================================================================== ---- pydantic-2.11.7.orig/pydantic/type_adapter.py -+++ pydantic-2.11.7/pydantic/type_adapter.py -@@ -591,6 +591,7 @@ class TypeAdapter(Generic[T]): - /, - *, - indent: int | None = None, -+ ensure_ascii: bool = False, - include: IncEx | None = None, - exclude: IncEx | None = None, - by_alias: bool | None = None, -@@ -611,6 +612,8 @@ class TypeAdapter(Generic[T]): - Args: - instance: The instance to be serialized. - indent: Number of spaces for JSON indentation. -+ ensure_ascii: If `True`, the output is guaranteed to have all incoming non-ASCII characters escaped. -+ If `False` (the default), these characters will be output as-is. - include: Fields to include. - exclude: Fields to exclude. - by_alias: Whether to use alias names for field names. -Index: pydantic-2.11.7/pydantic/version.py -=================================================================== ---- pydantic-2.11.7.orig/pydantic/version.py -+++ pydantic-2.11.7/pydantic/version.py -@@ -66,7 +66,7 @@ def version_info() -> str: - def check_pydantic_core_version() -> bool: - """Check that the installed `pydantic-core` dependency is compatible.""" - # Keep this in sync with the version constraint in the `pyproject.toml` dependencies: -- return __pydantic_core_version__ == '2.33.2' -+ return __pydantic_core_version__ == '2.35.1' - - - def parse_mypy_version(version: str) -> tuple[int, int, int]: -Index: pydantic-2.11.7/pyproject.toml -=================================================================== ---- pydantic-2.11.7.orig/pyproject.toml -+++ pydantic-2.11.7/pyproject.toml -@@ -43,10 +43,10 @@ classifiers = [ - ] - requires-python = '>=3.9' - dependencies = [ -- 'typing-extensions>=4.12.2', -+ 'typing-extensions>=4.13.0', - 'annotated-types>=0.6.0', - # Keep this in sync with the version in the `check_pydantic_core_version()` function: -- 'pydantic-core==2.33.2', -+ 'pydantic-core==2.35.1', - 'typing-inspection>=0.4.0', - ] - dynamic = ['version', 'readme'] -Index: pydantic-2.11.7/tests/typechecking/decorators.py -=================================================================== ---- pydantic-2.11.7.orig/tests/typechecking/decorators.py -+++ pydantic-2.11.7/tests/typechecking/decorators.py -@@ -31,13 +31,25 @@ class BeforeModelValidator(BaseModel): - """TODO This shouldn't be valid. At runtime, `self` is the actual value and `value` is the `ValidationInfo` instance.""" - - @model_validator(mode='before') -- def valid_method_info(self, value: Any, info: ValidationInfo) -> Any: ... -+ def valid_method_info_default(self, value: Any, info: ValidationInfo) -> Any: ... -+ -+ @model_validator(mode='before') -+ def valid_method_info(self, value: Any, info: ValidationInfo[int]) -> Any: -+ assert_type(info.context, int) - - @model_validator(mode='before') - @classmethod - def valid_classmethod(cls, value: Any) -> Any: ... - - @model_validator(mode='before') -+ @classmethod -+ def valid_classmethod_info_default(cls, value: Any, info: ValidationInfo) -> Any: ... -+ -+ @model_validator(mode='before') -+ @classmethod -+ def valid_classmethod_info(cls, value: Any, info: ValidationInfo[int]) -> Any: ... -+ -+ @model_validator(mode='before') - @staticmethod - def valid_staticmethod(value: Any) -> Any: ... - -@@ -91,7 +103,10 @@ class AfterModelValidator(BaseModel): - def valid_method_no_info(self) -> Self: ... - - @model_validator(mode='after') -- def valid_method_info(self, info: ValidationInfo) -> Self: ... -+ def valid_method_info_default(self, info: ValidationInfo) -> Self: ... -+ -+ @model_validator(mode='after') -+ def valid_method_info(self, info: ValidationInfo[int]) -> Self: ... - - - class BeforeFieldValidator(BaseModel): -@@ -114,7 +129,11 @@ class BeforeFieldValidator(BaseModel): - - @field_validator('foo', mode='before', json_schema_input_type=int) # `json_schema_input_type` allowed here. - @classmethod -- def valid_with_info(cls, value: Any, info: ValidationInfo) -> Any: ... -+ def valid_with_info_default(cls, value: Any, info: ValidationInfo) -> Any: ... -+ -+ @field_validator('foo', mode='before', json_schema_input_type=int) # `json_schema_input_type` allowed here. -+ @classmethod -+ def valid_with_info(cls, value: Any, info: ValidationInfo[int]) -> Any: ... - - - class AfterFieldValidator(BaseModel): -@@ -122,6 +141,14 @@ class AfterFieldValidator(BaseModel): - @classmethod - def valid_classmethod(cls, value: Any) -> Any: ... - -+ @field_validator('foo', mode='after') -+ @classmethod -+ def valid_classmethod_info_default(cls, value: Any, info: ValidationInfo) -> Any: ... -+ -+ @field_validator('foo', mode='after') -+ @classmethod -+ def valid_classmethod_info(cls, value: Any, info: ValidationInfo[int]) -> Any: ... -+ - @field_validator('foo', mode='after', json_schema_input_type=int) # type: ignore[call-overload] # pyright: ignore[reportCallIssue, reportArgumentType] - @classmethod - def invalid_input_type_not_allowed(cls, value: Any) -> Any: ... -@@ -148,7 +175,13 @@ class WrapFieldValidator(BaseModel): - - @field_validator('foo', mode='wrap', json_schema_input_type=int) # `json_schema_input_type` allowed here. - @classmethod -- def valid_with_info(cls, value: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo) -> Any: ... -+ def valid_with_info_default( -+ cls, value: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo -+ ) -> Any: ... -+ -+ @field_validator('foo', mode='wrap', json_schema_input_type=int) # `json_schema_input_type` allowed here. -+ @classmethod -+ def valid_with_info(cls, value: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo[int]) -> Any: ... - - - class PlainModelSerializer(BaseModel): -@@ -162,7 +195,10 @@ class PlainModelSerializer(BaseModel): - def valid_plain_serializer_2(self) -> Any: ... - - @model_serializer(mode='plain') -- def valid_plain_serializer_info(self, info: SerializationInfo) -> Any: ... -+ def valid_plain_serializer_info_default(self, info: SerializationInfo) -> Any: ... -+ -+ @model_serializer(mode='plain') -+ def valid_plain_serializer_info(self, info: SerializationInfo[int]) -> Any: ... - - - class WrapModelSerializer(BaseModel): -@@ -175,7 +211,12 @@ class WrapModelSerializer(BaseModel): - return value - - @model_serializer(mode='wrap') -- def valid_info(self, handler: SerializerFunctionWrapHandler, info: SerializationInfo) -> Any: -+ def valid_info_default(self, handler: SerializerFunctionWrapHandler, info: SerializationInfo) -> Any: -+ value = handler(self) -+ return value -+ -+ @model_serializer(mode='wrap') -+ def valid_info(self, handler: SerializerFunctionWrapHandler, info: SerializationInfo[int]) -> Any: - value = handler(self) - return value - -@@ -205,7 +246,10 @@ class PlainFieldSerializer(BaseModel): - """ - - @field_serializer('a', mode='plain') -- def valid_method_info(self, value: Any, info: FieldSerializationInfo) -> Any: ... -+ def valid_method_info_default(self, value: Any, info: FieldSerializationInfo) -> Any: ... -+ -+ @field_serializer('a', mode='plain') -+ def valid_method_info(self, value: Any, info: FieldSerializationInfo[int]) -> Any: ... - - @field_serializer('a', mode='plain') - @staticmethod -@@ -213,7 +257,11 @@ class PlainFieldSerializer(BaseModel): - - @field_serializer('a', mode='plain') - @staticmethod -- def valid_staticmethod_info(value: Any, info: FieldSerializationInfo) -> Any: ... -+ def valid_staticmethod_info_default(value: Any, info: FieldSerializationInfo) -> Any: ... -+ -+ @field_serializer('a', mode='plain') -+ @staticmethod -+ def valid_staticmethod_info(value: Any, info: FieldSerializationInfo[int]) -> Any: ... - - @field_serializer('a', mode='plain') - @classmethod -@@ -221,7 +269,11 @@ class PlainFieldSerializer(BaseModel): - - @field_serializer('a', mode='plain') - @classmethod -- def valid_classmethod_info(cls, value: Any, info: FieldSerializationInfo) -> Any: ... -+ def valid_classmethod_info_default(cls, value: Any, info: FieldSerializationInfo) -> Any: ... -+ -+ @field_serializer('a', mode='plain') -+ @classmethod -+ def valid_classmethod_info(cls, value: Any, info: FieldSerializationInfo[int]) -> Any: ... - - partial_ = field_serializer('a', mode='plain')(partial(lambda v, x: v, x=1)) - -@@ -250,4 +302,11 @@ class WrapFieldSerializer(BaseModel): - def valid_no_info(self, value: Any, handler: SerializerFunctionWrapHandler) -> Any: ... - - @field_serializer('a', mode='wrap') -- def valid_info(self, value: Any, handler: SerializerFunctionWrapHandler, info: FieldSerializationInfo) -> Any: ... -+ def valid_info_default( -+ self, value: Any, handler: SerializerFunctionWrapHandler, info: FieldSerializationInfo -+ ) -> Any: ... -+ -+ @field_serializer('a', mode='wrap') -+ def valid_info( -+ self, value: Any, handler: SerializerFunctionWrapHandler, info: FieldSerializationInfo[int] -+ ) -> Any: ... diff --git a/field-name-validator-core-schemas.patch b/field-name-validator-core-schemas.patch deleted file mode 100644 index 760e5b4..0000000 --- a/field-name-validator-core-schemas.patch +++ /dev/null @@ -1,233 +0,0 @@ -From cd0d37c4c18f24b5624ae86cfe5288cd82edf2c1 Mon Sep 17 00:00:00 2001 -From: Douwe Maan -Date: Wed, 16 Apr 2025 18:01:58 +0000 -Subject: [PATCH 1/4] Stop using deprecated field_name argument on validation - function schemas - ---- - docs/concepts/types.md | 2 +- - pydantic/_internal/_generate_schema.py | 45 ++++++++++---------------- - pydantic/functional_validators.py | 5 +-- - tests/test_validators.py | 2 +- - 4 files changed, 20 insertions(+), 34 deletions(-) - -Index: pydantic-2.11.7/docs/concepts/types.md -=================================================================== ---- pydantic-2.11.7.orig/docs/concepts/types.md -+++ pydantic-2.11.7/docs/concepts/types.md -@@ -979,7 +979,7 @@ class CustomType: - cls, source_type: Any, handler: GetCoreSchemaHandler - ) -> core_schema.CoreSchema: - return core_schema.with_info_after_validator_function( -- cls.validate, handler(int), field_name=handler.field_name -+ cls.validate, handler(int) - ) - - -Index: pydantic-2.11.7/pydantic/_internal/_generate_schema.py -=================================================================== ---- pydantic-2.11.7.orig/pydantic/_internal/_generate_schema.py -+++ pydantic-2.11.7/pydantic/_internal/_generate_schema.py -@@ -222,7 +222,6 @@ def filter_field_decorator_info_by_field - def apply_each_item_validators( - schema: core_schema.CoreSchema, - each_item_validators: list[Decorator[ValidatorDecoratorInfo]], -- field_name: str | None, - ) -> core_schema.CoreSchema: - # This V1 compatibility shim should eventually be removed - -@@ -234,21 +233,20 @@ def apply_each_item_validators( - # note that this won't work for any Annotated types that get wrapped by a function validator - # but that's okay because that didn't exist in V1 - if schema['type'] == 'nullable': -- schema['schema'] = apply_each_item_validators(schema['schema'], each_item_validators, field_name) -+ schema['schema'] = apply_each_item_validators(schema['schema'], each_item_validators) - return schema - elif schema['type'] == 'tuple': - if (variadic_item_index := schema.get('variadic_item_index')) is not None: - schema['items_schema'][variadic_item_index] = apply_validators( - schema['items_schema'][variadic_item_index], - each_item_validators, -- field_name, - ) - elif is_list_like_schema_with_items_schema(schema): - inner_schema = schema.get('items_schema', core_schema.any_schema()) -- schema['items_schema'] = apply_validators(inner_schema, each_item_validators, field_name) -+ schema['items_schema'] = apply_validators(inner_schema, each_item_validators) - elif schema['type'] == 'dict': - inner_schema = schema.get('values_schema', core_schema.any_schema()) -- schema['values_schema'] = apply_validators(inner_schema, each_item_validators, field_name) -+ schema['values_schema'] = apply_validators(inner_schema, each_item_validators) - else: - raise TypeError( - f'`@validator(..., each_item=True)` cannot be applied to fields with a schema of {schema["type"]}' -@@ -840,7 +838,7 @@ class GenerateSchema: - extras_keys_schema=extras_keys_schema, - model_name=cls.__name__, - ) -- inner_schema = apply_validators(fields_schema, decorators.root_validators.values(), None) -+ inner_schema = apply_validators(fields_schema, decorators.root_validators.values()) - inner_schema = apply_model_validators(inner_schema, model_validators, 'inner') - - model_schema = core_schema.model_schema( -@@ -1380,9 +1378,9 @@ class GenerateSchema: - field_info.validate_default = True - each_item_validators = [v for v in this_field_validators if v.info.each_item is True] - this_field_validators = [v for v in this_field_validators if v not in each_item_validators] -- schema = apply_each_item_validators(schema, each_item_validators, name) -+ schema = apply_each_item_validators(schema, each_item_validators) - -- schema = apply_validators(schema, this_field_validators, name) -+ schema = apply_validators(schema, this_field_validators) - - # the default validator needs to go outside of any other validators - # so that it is the topmost validator for the field validator -@@ -1972,7 +1970,7 @@ class GenerateSchema: - collect_init_only=has_post_init, - ) - -- inner_schema = apply_validators(args_schema, decorators.root_validators.values(), None) -+ inner_schema = apply_validators(args_schema, decorators.root_validators.values()) - - model_validators = decorators.model_validators.values() - inner_schema = apply_model_validators(inner_schema, model_validators, 'inner') -@@ -2484,24 +2482,16 @@ class GenerateSchema: - - _VALIDATOR_F_MATCH: Mapping[ - tuple[FieldValidatorModes, Literal['no-info', 'with-info']], -- Callable[[Callable[..., Any], core_schema.CoreSchema, str | None], core_schema.CoreSchema], -+ Callable[[Callable[..., Any], core_schema.CoreSchema], core_schema.CoreSchema], - ] = { -- ('before', 'no-info'): lambda f, schema, _: core_schema.no_info_before_validator_function(f, schema), -- ('after', 'no-info'): lambda f, schema, _: core_schema.no_info_after_validator_function(f, schema), -- ('plain', 'no-info'): lambda f, _1, _2: core_schema.no_info_plain_validator_function(f), -- ('wrap', 'no-info'): lambda f, schema, _: core_schema.no_info_wrap_validator_function(f, schema), -- ('before', 'with-info'): lambda f, schema, field_name: core_schema.with_info_before_validator_function( -- f, schema, field_name=field_name -- ), -- ('after', 'with-info'): lambda f, schema, field_name: core_schema.with_info_after_validator_function( -- f, schema, field_name=field_name -- ), -- ('plain', 'with-info'): lambda f, _, field_name: core_schema.with_info_plain_validator_function( -- f, field_name=field_name -- ), -- ('wrap', 'with-info'): lambda f, schema, field_name: core_schema.with_info_wrap_validator_function( -- f, schema, field_name=field_name -- ), -+ ('before', 'no-info'): lambda f, schema: core_schema.no_info_before_validator_function(f, schema), -+ ('after', 'no-info'): lambda f, schema: core_schema.no_info_after_validator_function(f, schema), -+ ('plain', 'no-info'): lambda f, _: core_schema.no_info_plain_validator_function(f), -+ ('wrap', 'no-info'): lambda f, schema: core_schema.no_info_wrap_validator_function(f, schema), -+ ('before', 'with-info'): lambda f, schema: core_schema.with_info_before_validator_function(f, schema), -+ ('after', 'with-info'): lambda f, schema: core_schema.with_info_after_validator_function(f, schema), -+ ('plain', 'with-info'): lambda f, _: core_schema.with_info_plain_validator_function(f), -+ ('wrap', 'with-info'): lambda f, schema: core_schema.with_info_wrap_validator_function(f, schema), - } - - -@@ -2512,7 +2502,6 @@ def apply_validators( - validators: Iterable[Decorator[RootValidatorDecoratorInfo]] - | Iterable[Decorator[ValidatorDecoratorInfo]] - | Iterable[Decorator[FieldValidatorDecoratorInfo]], -- field_name: str | None, - ) -> core_schema.CoreSchema: - """Apply validators to a schema. - -@@ -2528,7 +2517,7 @@ def apply_validators( - info_arg = inspect_validator(validator.func, validator.info.mode) - val_type = 'with-info' if info_arg else 'no-info' - -- schema = _VALIDATOR_F_MATCH[(validator.info.mode, val_type)](validator.func, schema, field_name) -+ schema = _VALIDATOR_F_MATCH[(validator.info.mode, val_type)](validator.func, schema) - return schema - - -Index: pydantic-2.11.7/pydantic/functional_validators.py -=================================================================== ---- pydantic-2.11.7.orig/pydantic/functional_validators.py -+++ pydantic-2.11.7/pydantic/functional_validators.py -@@ -75,7 +75,7 @@ class AfterValidator: - info_arg = _inspect_validator(self.func, 'after') - if info_arg: - func = cast(core_schema.WithInfoValidatorFunction, self.func) -- return core_schema.with_info_after_validator_function(func, schema=schema, field_name=handler.field_name) -+ return core_schema.with_info_after_validator_function(func, schema=schema) - else: - func = cast(core_schema.NoInfoValidatorFunction, self.func) - return core_schema.no_info_after_validator_function(func, schema=schema) -@@ -136,7 +136,6 @@ class BeforeValidator: - return core_schema.with_info_before_validator_function( - func, - schema=schema, -- field_name=handler.field_name, - json_schema_input_schema=input_schema, - ) - else: -@@ -230,7 +229,6 @@ class PlainValidator: - func = cast(core_schema.WithInfoValidatorFunction, self.func) - return core_schema.with_info_plain_validator_function( - func, -- field_name=handler.field_name, - serialization=serialization, # pyright: ignore[reportArgumentType] - json_schema_input_schema=input_schema, - ) -@@ -307,7 +305,6 @@ class WrapValidator: - return core_schema.with_info_wrap_validator_function( - func, - schema=schema, -- field_name=handler.field_name, - json_schema_input_schema=input_schema, - ) - else: -Index: pydantic-2.11.7/tests/test_validators.py -=================================================================== ---- pydantic-2.11.7.orig/tests/test_validators.py -+++ pydantic-2.11.7/tests/test_validators.py -@@ -21,7 +21,7 @@ from unittest.mock import MagicMock - import pytest - from dirty_equals import HasRepr, IsInstance - from pydantic_core import core_schema --from typing_extensions import TypedDict -+from typing_extensions import TypeAliasType, TypedDict - - from pydantic import ( - BaseModel, -@@ -2684,7 +2684,7 @@ def foobar_validate(value: Any, info: co - class Foobar: - @classmethod - def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: -- return core_schema.with_info_plain_validator_function(foobar_validate, field_name=handler.field_name) -+ return core_schema.with_info_plain_validator_function(foobar_validate) - - - def test_custom_type_field_name_model(): -@@ -2779,6 +2779,29 @@ def test_plain_validator_field_name(): - assert m.foobar == {'value': '1', 'field_name': 'foobar', 'data': {'x': 123}} - - -+def test_validator_field_name_with_reused_type_alias(): -+ calls = [] -+ -+ def validate_my_field(value: str, info: ValidationInfo): -+ calls.append((info.field_name, value)) -+ return value -+ -+ MyField = TypeAliasType('MyField', Annotated[str, AfterValidator(validate_my_field)]) -+ -+ class MyModel(BaseModel): -+ field1: MyField -+ field2: MyField -+ -+ MyModel.model_validate( -+ { -+ 'field1': 'value1', -+ 'field2': 'value2', -+ } -+ ) -+ -+ assert calls == [('field1', 'value1'), ('field2', 'value2')] -+ -+ - def validate_wrap(value: Any, handler: core_schema.ValidatorFunctionWrapHandler, info: core_schema.ValidationInfo): - data = info.data - if isinstance(data, dict): diff --git a/py314.patch b/py314.patch deleted file mode 100644 index 7512d03..0000000 --- a/py314.patch +++ /dev/null @@ -1,566 +0,0 @@ -From 9452e13c571db7d31051768c3b4d47a6e2ceea7d Mon Sep 17 00:00:00 2001 -From: Victorien <65306057+Viicos@users.noreply.github.com> -Date: Thu, 10 Jul 2025 11:31:03 +0200 -Subject: [PATCH] Add initial support for Python 3.14 (#11991) - -Adds basic support for Python 3.14. Deferred annotations work for simple cases, but will need to be improved in the future. ---- - .github/workflows/ci.yml | 11 +- - .github/workflows/integration.yml | 4 +- - docs/migration.md | 4 +- - pydantic/_internal/_config.py | 10 +- - pydantic/_internal/_fields.py | 7 +- - pydantic/_internal/_generics.py | 7 +- - pydantic/_internal/_model_construction.py | 24 +- - pydantic/_internal/_typing_extra.py | 36 +- - pydantic/dataclasses.py | 13 +- - pyproject.toml | 3 + - tests/test_dataclasses.py | 11 +- - tests/test_deferred_annotations.py | 81 ++++ - tests/test_forward_ref.py | 15 - - tests/test_model_signature.py | 2 +- - tests/test_pickle.py | 12 +- - tests/test_v1.py | 2 + - uv.lock | 512 ++++++++++++---------- - 17 files changed, 469 insertions(+), 285 deletions(-) - create mode 100644 tests/test_deferred_annotations.py - - -Index: pydantic-2.11.9/docs/migration.md -=================================================================== ---- pydantic-2.11.9.orig/docs/migration.md -+++ pydantic-2.11.9/docs/migration.md -@@ -188,7 +188,7 @@ to help ease migration, but calling them - If you'd still like to use said arguments, you can use [this workaround](https://github.com/pydantic/pydantic/issues/8825#issuecomment-1946206415). - * JSON serialization of non-string key values is generally done with `str(key)`, leading to some changes in behavior such as the following: - --```python -+```python {test="skip"} - from typing import Optional - - from pydantic import BaseModel as V2BaseModel -@@ -218,7 +218,7 @@ print(v2_model.model_dump_json()) - * `model_dump_json()` results are compacted in order to save space, and don't always exactly match that of `json.dumps()` output. - That being said, you can easily modify the separators used in `json.dumps()` results in order to align the two outputs: - --```python -+```python {test="skip"} - import json - - from pydantic import BaseModel as V2BaseModel -Index: pydantic-2.11.9/pydantic/_internal/_config.py -=================================================================== ---- pydantic-2.11.9.orig/pydantic/_internal/_config.py -+++ pydantic-2.11.9/pydantic/_internal/_config.py -@@ -98,7 +98,13 @@ class ConfigWrapper: - self.config_dict = cast(ConfigDict, config) - - @classmethod -- def for_model(cls, bases: tuple[type[Any], ...], namespace: dict[str, Any], kwargs: dict[str, Any]) -> Self: -+ def for_model( -+ cls, -+ bases: tuple[type[Any], ...], -+ namespace: dict[str, Any], -+ raw_annotations: dict[str, Any], -+ kwargs: dict[str, Any], -+ ) -> Self: - """Build a new `ConfigWrapper` instance for a `BaseModel`. - - The config wrapper built based on (in descending order of priority): -@@ -109,6 +115,7 @@ class ConfigWrapper: - Args: - bases: A tuple of base classes. - namespace: The namespace of the class being created. -+ raw_annotations: The (non-evaluated) annotations of the model. - kwargs: The kwargs passed to the class being created. - - Returns: -@@ -123,7 +130,6 @@ class ConfigWrapper: - config_class_from_namespace = namespace.get('Config') - config_dict_from_namespace = namespace.get('model_config') - -- raw_annotations = namespace.get('__annotations__', {}) - if raw_annotations.get('model_config') and config_dict_from_namespace is None: - raise PydanticUserError( - '`model_config` cannot be used as a model field name. Use `model_config` for model configuration.', -Index: pydantic-2.11.9/pydantic/_internal/_fields.py -=================================================================== ---- pydantic-2.11.9.orig/pydantic/_internal/_fields.py -+++ pydantic-2.11.9/pydantic/_internal/_fields.py -@@ -119,7 +119,8 @@ def collect_model_fields( # noqa: C901 - - # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older - # annotations is only used for finding fields in parent classes -- annotations = cls.__dict__.get('__annotations__', {}) -+ annotations = _typing_extra.safe_get_annotations(cls) -+ - fields: dict[str, FieldInfo] = {} - - class_vars: set[str] = set() -@@ -375,7 +376,9 @@ def collect_dataclass_fields( - - with ns_resolver.push(base): - for ann_name, dataclass_field in dataclass_fields.items(): -- if ann_name not in base.__dict__.get('__annotations__', {}): -+ base_anns = _typing_extra.safe_get_annotations(base) -+ -+ if ann_name not in base_anns: - # `__dataclass_fields__`contains every field, even the ones from base classes. - # Only collect the ones defined on `base`. - continue -Index: pydantic-2.11.9/pydantic/_internal/_generics.py -=================================================================== ---- pydantic-2.11.9.orig/pydantic/_internal/_generics.py -+++ pydantic-2.11.9/pydantic/_internal/_generics.py -@@ -1,5 +1,6 @@ - from __future__ import annotations - -+import operator - import sys - import types - import typing -@@ -7,6 +8,7 @@ from collections import ChainMap - from collections.abc import Iterator, Mapping - from contextlib import contextmanager - from contextvars import ContextVar -+from functools import reduce - from itertools import zip_longest - from types import prepare_class - from typing import TYPE_CHECKING, Annotated, Any, TypeVar -@@ -21,9 +23,6 @@ from ._core_utils import get_type_ref - from ._forward_ref import PydanticRecursiveRef - from ._utils import all_identical, is_model_class - --if sys.version_info >= (3, 10): -- from typing import _UnionGenericAlias # type: ignore[attr-defined] -- - if TYPE_CHECKING: - from ..main import BaseModel - -@@ -311,7 +310,7 @@ def replace_types(type_: Any, type_map: - # PEP-604 syntax (Ex.: list | str) is represented with a types.UnionType object that does not have __getitem__. - # We also cannot use isinstance() since we have to compare types. - if sys.version_info >= (3, 10) and origin_type is types.UnionType: -- return _UnionGenericAlias(origin_type, resolved_type_args) -+ return reduce(operator.or_, resolved_type_args) - # NotRequired[T] and Required[T] don't support tuple type resolved_type_args, hence the condition below - return origin_type[resolved_type_args[0] if len(resolved_type_args) == 1 else resolved_type_args] - -Index: pydantic-2.11.9/pydantic/_internal/_model_construction.py -=================================================================== ---- pydantic-2.11.9.orig/pydantic/_internal/_model_construction.py -+++ pydantic-2.11.9/pydantic/_internal/_model_construction.py -@@ -105,12 +105,29 @@ class ModelMetaclass(ABCMeta): - # that `BaseModel` itself won't have any bases, but any subclass of it will, to determine whether the `__new__` - # call we're in the middle of is for the `BaseModel` class. - if bases: -+ raw_annotations: dict[str, Any] -+ if sys.version_info >= (3, 14): -+ if ( -+ '__annotations__' in namespace -+ ): # `from __future__ import annotations` was used in the model's module -+ raw_annotations = namespace['__annotations__'] -+ else: -+ # See https://docs.python.org/3.14/library/annotationlib.html#using-annotations-in-a-metaclass: -+ from annotationlib import Format, call_annotate_function, get_annotate_from_class_namespace -+ -+ if annotate := get_annotate_from_class_namespace(namespace): -+ raw_annotations = call_annotate_function(annotate, format=Format.FORWARDREF) -+ else: -+ raw_annotations = {} -+ else: -+ raw_annotations = namespace.get('__annotations__', {}) -+ - base_field_names, class_vars, base_private_attributes = mcs._collect_bases_data(bases) - -- config_wrapper = ConfigWrapper.for_model(bases, namespace, kwargs) -+ config_wrapper = ConfigWrapper.for_model(bases, namespace, raw_annotations, kwargs) - namespace['model_config'] = config_wrapper.config_dict - private_attributes = inspect_namespace( -- namespace, config_wrapper.ignored_types, class_vars, base_field_names -+ namespace, raw_annotations, config_wrapper.ignored_types, class_vars, base_field_names - ) - if private_attributes or base_private_attributes: - original_model_post_init = get_model_post_init(namespace, bases) -@@ -365,6 +382,7 @@ def get_model_post_init(namespace: dict[ - - def inspect_namespace( # noqa C901 - namespace: dict[str, Any], -+ raw_annotations: dict[str, Any], - ignored_types: tuple[type[Any], ...], - base_class_vars: set[str], - base_class_fields: set[str], -@@ -375,6 +393,7 @@ def inspect_namespace( # noqa C901 - - Args: - namespace: The attribute dictionary of the class to be created. -+ raw_annotations: The (non-evaluated) annotations of the model. - ignored_types: A tuple of ignore types. - base_class_vars: A set of base class class variables. - base_class_fields: A set of base class fields. -@@ -396,7 +415,6 @@ def inspect_namespace( # noqa C901 - all_ignored_types = ignored_types + default_ignored_types() - - private_attributes: dict[str, ModelPrivateAttr] = {} -- raw_annotations = namespace.get('__annotations__', {}) - - if '__root__' in raw_annotations or '__root__' in namespace: - raise TypeError("To define root models, use `pydantic.RootModel` rather than a field called '__root__'") -Index: pydantic-2.11.9/pydantic/_internal/_typing_extra.py -=================================================================== ---- pydantic-2.11.9.orig/pydantic/_internal/_typing_extra.py -+++ pydantic-2.11.9/pydantic/_internal/_typing_extra.py -@@ -26,6 +26,9 @@ else: - from types import EllipsisType as EllipsisType - from types import NoneType as NoneType - -+if sys.version_info >= (3, 14): -+ import annotationlib -+ - if TYPE_CHECKING: - from pydantic import BaseModel - -@@ -289,6 +292,19 @@ def _type_convert(arg: Any) -> Any: - return arg - - -+def safe_get_annotations(cls: type[Any]) -> dict[str, Any]: -+ """Get the annotations for the provided class, accounting for potential deferred forward references. -+ -+ Starting with Python 3.14, accessing the `__annotations__` attribute might raise a `NameError` if -+ a referenced symbol isn't defined yet. In this case, we return the annotation in the *forward ref* -+ format. -+ """ -+ if sys.version_info >= (3, 14): -+ return annotationlib.get_annotations(cls, format=annotationlib.Format.FORWARDREF) -+ else: -+ return cls.__dict__.get('__annotations__', {}) -+ -+ - def get_model_type_hints( - obj: type[BaseModel], - *, -@@ -309,9 +325,14 @@ def get_model_type_hints( - ns_resolver = ns_resolver or NsResolver() - - for base in reversed(obj.__mro__): -- ann: dict[str, Any] | None = base.__dict__.get('__annotations__') -- if not ann or isinstance(ann, types.GetSetDescriptorType): -+ # For Python 3.14, we could also use `Format.VALUE` and pass the globals/locals -+ # from the ns_resolver, but we want to be able to know which specific field failed -+ # to evaluate: -+ ann = safe_get_annotations(base) -+ -+ if not ann: - continue -+ - with ns_resolver.push(base): - globalns, localns = ns_resolver.types_namespace - for name, value in ann.items(): -@@ -341,13 +362,18 @@ def get_cls_type_hints( - obj: The class to inspect. - ns_resolver: A namespace resolver instance to use. Defaults to an empty instance. - """ -- hints: dict[str, Any] | dict[str, tuple[Any, bool]] = {} -+ hints: dict[str, Any] = {} - ns_resolver = ns_resolver or NsResolver() - - for base in reversed(obj.__mro__): -- ann: dict[str, Any] | None = base.__dict__.get('__annotations__') -- if not ann or isinstance(ann, types.GetSetDescriptorType): -+ # For Python 3.14, we could also use `Format.VALUE` and pass the globals/locals -+ # from the ns_resolver, but we want to be able to know which specific field failed -+ # to evaluate: -+ ann = safe_get_annotations(base) -+ -+ if not ann: - continue -+ - with ns_resolver.push(base): - globalns, localns = ns_resolver.types_namespace - for name, value in ann.items(): -Index: pydantic-2.11.9/pydantic/dataclasses.py -=================================================================== ---- pydantic-2.11.9.orig/pydantic/dataclasses.py -+++ pydantic-2.11.9/pydantic/dataclasses.py -@@ -157,7 +157,12 @@ def dataclass( - `x: int = dataclasses.field(default=pydantic.Field(..., kw_only=True), kw_only=True)` - """ - for annotation_cls in cls.__mro__: -- annotations: dict[str, Any] = getattr(annotation_cls, '__annotations__', {}) -+ if sys.version_info >= (3, 14): -+ from annotationlib import Format, get_annotations -+ -+ annotations = get_annotations(annotation_cls, format=Format.FORWARDREF) -+ else: -+ annotations: dict[str, Any] = getattr(annotation_cls, '__annotations__', {}) - for field_name in annotations: - field_value = getattr(cls, field_name, None) - # Process only if this is an instance of `FieldInfo`. -@@ -176,9 +181,9 @@ def dataclass( - field_args['repr'] = field_value.repr - - setattr(cls, field_name, dataclasses.field(**field_args)) -- # In Python 3.9, when subclassing, information is pulled from cls.__dict__['__annotations__'] -- # for annotations, so we must make sure it's initialized before we add to it. -- if cls.__dict__.get('__annotations__') is None: -+ if sys.version_info < (3, 10) and cls.__dict__.get('__annotations__') is None: -+ # In Python 3.9, when a class doesn't have any annotations, accessing `__annotations__` -+ # raises an `AttributeError`. - cls.__annotations__ = {} - cls.__annotations__[field_name] = annotations[field_name] - -Index: pydantic-2.11.9/pyproject.toml -=================================================================== ---- pydantic-2.11.9.orig/pyproject.toml -+++ pydantic-2.11.9/pyproject.toml -@@ -32,6 +32,7 @@ classifiers = [ - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python :: 3.13', -+ 'Programming Language :: Python :: 3.14', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'License :: OSI Approved :: MIT License', -@@ -220,6 +221,8 @@ pydocstyle = { convention = 'google' } - 'docs/*' = ['D'] - 'pydantic/__init__.py' = ['F405', 'F403', 'D'] - 'tests/test_forward_ref.py' = ['F821'] -+# We can't configure a specific Python version per file (this one only supports 3.14+): -+'tests/test_deferred_annotations.py' = ['F821', 'F841'] - 'tests/*' = ['D', 'B', 'C4'] - 'pydantic/deprecated/*' = ['D', 'PYI'] - 'pydantic/color.py' = ['PYI'] -Index: pydantic-2.11.9/tests/test_dataclasses.py -=================================================================== ---- pydantic-2.11.9.orig/tests/test_dataclasses.py -+++ pydantic-2.11.9/tests/test_dataclasses.py -@@ -30,6 +30,7 @@ from pydantic import ( - BaseModel, - BeforeValidator, - ConfigDict, -+ Field, - PydanticDeprecatedSince20, - PydanticUndefinedAnnotation, - PydanticUserError, -@@ -45,7 +46,6 @@ from pydantic import ( - ) - from pydantic._internal._mock_val_ser import MockValSer - from pydantic.dataclasses import is_pydantic_dataclass, rebuild_dataclass --from pydantic.fields import Field, FieldInfo - from pydantic.json_schema import model_json_schema - - -@@ -2072,15 +2072,14 @@ def test_inheritance_replace(decorator1: - def test_dataclasses_inheritance_default_value_is_not_deleted( - decorator1: Callable[[Any], Any], default: Literal[1] - ) -> None: -- if decorator1 is dataclasses.dataclass and isinstance(default, FieldInfo): -- pytest.skip(reason="stdlib dataclasses don't support Pydantic fields") -- - @decorator1 - class Parent: - a: int = default - -- assert Parent.a == 1 -- assert Parent().a == 1 -+ # stdlib dataclasses don't support Pydantic's `Field()`: -+ if decorator1 is pydantic.dataclasses.dataclass: -+ assert Parent.a == 1 -+ assert Parent().a == 1 - - @pydantic.dataclasses.dataclass - class Child(Parent): -Index: pydantic-2.11.9/tests/test_deferred_annotations.py -=================================================================== ---- /dev/null -+++ pydantic-2.11.9/tests/test_deferred_annotations.py -@@ -0,0 +1,81 @@ -+"""Tests related to deferred evaluation of annotations introduced in Python 3.14 by PEP 649 and 749.""" -+ -+import sys -+from dataclasses import field -+from typing import Annotated -+ -+import pytest -+from annotated_types import MaxLen -+ -+from pydantic import BaseModel, Field, ValidationError -+from pydantic.dataclasses import dataclass -+ -+pytestmark = pytest.mark.skipif( -+ sys.version_info < (3, 14), reason='Requires deferred evaluation of annotations introduced in Python 3.14' -+) -+ -+ -+def test_deferred_annotations_model() -> None: -+ class Model(BaseModel): -+ a: Int -+ b: Str = 'a' -+ -+ Int = int -+ Str = str -+ -+ inst = Model(a='1', b=b'test') -+ assert inst.a == 1 -+ assert inst.b == 'test' -+ -+ -+@pytest.mark.xfail( -+ reason=( -+ 'When rebuilding model fields, we individually re-evaluate all fields (using `_eval_type()`) ' -+ "and as such we don't benefit from PEP 649's capabilities." -+ ), -+) -+def test_deferred_annotations_nested_model() -> None: -+ def outer(): -+ def inner(): -+ class Model(BaseModel): -+ ann: Annotated[List[Dict[str, str]], MaxLen(1)] -+ -+ Dict = dict -+ -+ return Model -+ -+ List = list -+ -+ Model = inner() -+ -+ return Model -+ -+ Model = outer() -+ -+ with pytest.raises(ValidationError) as exc_info: -+ Model(ann=[{'a': 'b'}, {'c': 'd'}]) -+ -+ assert exc_info.value.errors()[0]['type'] == 'too_long' -+ -+ -+def test_deferred_annotations_pydantic_dataclass() -> None: -+ @dataclass -+ class A: -+ a: Int = field(default=1) -+ -+ Int = int -+ -+ assert A(a='1').a == 1 -+ -+ -+@pytest.mark.xfail( -+ reason="To support Pydantic's `Field()` function in dataclasses, we directly write to `__annotations__`" -+) -+def test_deferred_annotations_pydantic_dataclass_pydantic_field() -> None: -+ @dataclass -+ class A: -+ a: Int = Field(default=1) -+ -+ Int = int -+ -+ assert A(a='1').a == 1 -Index: pydantic-2.11.9/tests/test_forward_ref.py -=================================================================== ---- pydantic-2.11.9.orig/tests/test_forward_ref.py -+++ pydantic-2.11.9/tests/test_forward_ref.py -@@ -74,21 +74,6 @@ def test_forward_ref_auto_update_no_mode - assert f.model_dump() == {'a': {'b': {'a': {'b': {'a': None}}}}} - - --def test_forward_ref_one_of_fields_not_defined(create_module): -- @create_module -- def module(): -- from pydantic import BaseModel -- -- class Foo(BaseModel): -- foo: 'Foo' -- bar: 'Bar' -- -- assert {k: repr(v) for k, v in module.Foo.model_fields.items()} == { -- 'foo': 'FieldInfo(annotation=Foo, required=True)', -- 'bar': "FieldInfo(annotation=ForwardRef('Bar'), required=True)", -- } -- -- - def test_basic_forward_ref(create_module): - @create_module - def module(): -Index: pydantic-2.11.9/tests/test_model_signature.py -=================================================================== ---- pydantic-2.11.9.orig/tests/test_model_signature.py -+++ pydantic-2.11.9/tests/test_model_signature.py -@@ -184,7 +184,7 @@ def test_annotated_field(): - assert typing_objects.is_annotated(get_origin(sig.parameters['foo'].annotation)) - - --@pytest.mark.skipif(sys.version_info < (3, 10), reason='repr different on older versions') -+@pytest.mark.skipif(sys.version_info < (3, 10), sys.version_info >= (3, 14), reason='repr different on older versions') - def test_annotated_optional_field(): - from annotated_types import Gt - -Index: pydantic-2.11.9/tests/test_pickle.py -=================================================================== ---- pydantic-2.11.9.orig/tests/test_pickle.py -+++ pydantic-2.11.9/tests/test_pickle.py -@@ -1,6 +1,7 @@ - import dataclasses - import gc - import pickle -+import sys - from typing import Optional - - import pytest -@@ -17,6 +18,11 @@ except ImportError: - - pytestmark = pytest.mark.skipif(cloudpickle is None, reason='cloudpickle is not installed') - -+cloudpickle_xfail = pytest.mark.xfail( -+ condition=sys.version_info >= (3, 14), -+ reason='Cloudpickle issue: https://github.com/cloudpipe/cloudpickle/issues/572', -+) -+ - - class IntWrapper: - def __init__(self, v: int): -@@ -88,7 +94,7 @@ def model_factory() -> type: - (ImportableModel, False), - (ImportableModel, True), - # Locally-defined model can only be pickled with cloudpickle. -- (model_factory(), True), -+ pytest.param(model_factory(), True, marks=cloudpickle_xfail), - ], - ) - def test_pickle_model(model_type: type, use_cloudpickle: bool): -@@ -133,7 +139,7 @@ def nested_model_factory() -> type: - (ImportableNestedModel, False), - (ImportableNestedModel, True), - # Locally-defined model can only be pickled with cloudpickle. -- (nested_model_factory(), True), -+ pytest.param(nested_model_factory(), True, marks=cloudpickle_xfail), - ], - ) - def test_pickle_nested_model(model_type: type, use_cloudpickle: bool): -@@ -264,7 +270,7 @@ def nested_dataclass_model_factory() -> - (ImportableNestedDataclassModel, False), - (ImportableNestedDataclassModel, True), - # Locally-defined model can only be pickled with cloudpickle. -- (nested_dataclass_model_factory(), True), -+ pytest.param(nested_dataclass_model_factory(), True, marks=cloudpickle_xfail), - ], - ) - def test_pickle_dataclass_nested_in_model(model_type: type, use_cloudpickle: bool): -Index: pydantic-2.11.9/tests/test_v1.py -=================================================================== ---- pydantic-2.11.9.orig/tests/test_v1.py -+++ pydantic-2.11.9/tests/test_v1.py -@@ -1,3 +1,4 @@ -+import sys - import warnings - - import pytest -@@ -14,6 +15,7 @@ def test_version(): - assert V1_VERSION != VERSION - - -+@pytest.mark.skipif(sys.version_info >= (3, 14), reason='Python 3.14+ not supported') - @pytest.mark.thread_unsafe(reason='Mutates the value') - def test_root_validator(): - class Model(V1BaseModel): diff --git a/pydantic-2.11.9.tar.gz b/pydantic-2.11.9.tar.gz deleted file mode 100644 index 0c315eb..0000000 --- a/pydantic-2.11.9.tar.gz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e14caadd65cf15f778c90cd9f4878942b44aeaeaf3a6b595fbc88b0555df0dc1 -size 3090332 diff --git a/pydantic-2.12.5.tar.gz b/pydantic-2.12.5.tar.gz new file mode 100644 index 0000000..08bd452 --- /dev/null +++ b/pydantic-2.12.5.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c79f06e47b8a49593a02ad0b3a0102089c4d22c55666183614dbbad33c12ae73 +size 1976436 diff --git a/python-pydantic.changes b/python-pydantic.changes index 5af0f21..ffce658 100644 --- a/python-pydantic.changes +++ b/python-pydantic.changes @@ -1,3 +1,12 @@ +------------------------------------------------------------------- +Wed Dec 3 20:31:21 UTC 2025 - Guang Yee + +- Update to 2.12.5 + * Fix pickle error when using model_construct() on a model with MISSING as a default value in #12522. + * Several updates to the documentation +- Remove patches bump-pydantic-core-2.35.1.patch, field-name-validator-core-schemas.patch, + py314.patch, and support-pydantic-core-2.39.0.patch as they've merged upstream. + ------------------------------------------------------------------- Mon Sep 29 12:28:15 UTC 2025 - Markéta Machová diff --git a/python-pydantic.spec b/python-pydantic.spec index 4bb4d06..b5adff3 100644 --- a/python-pydantic.spec +++ b/python-pydantic.spec @@ -27,25 +27,17 @@ %endif %{?sle15_python_module_pythons} Name: python-pydantic%{psuffix} -Version: 2.11.9 +Version: 2.12.5 Release: 0 Summary: Data validation and settings management using python type hinting License: MIT URL: https://github.com/pydantic/pydantic Source: https://github.com/pydantic/pydantic/archive/v%{version}.tar.gz#/pydantic-%{version}.tar.gz -# PATCH-FIX-UPSTREAM bump-pydantic-core-2.35.1.patch gh#pydantic/pydantic#11963 -Patch0: bump-pydantic-core-2.35.1.patch -# PATCH-FIX-UPSTREAM field-name-validator-core-schemas.patch gh#pydantic/pydantic#11761 -Patch1: field-name-validator-core-schemas.patch -# PATCH-FIX-UPSTREAM Based on gh#pydantic/pydantic#11883 -Patch2: support-pydantic-core-2.39.0.patch -# PATCH-FIX-UPSTREAM https://github.com/pydantic/pydantic/pull/11991 Add initial support for Python 3.14 -Patch3: py314.patch BuildRequires: %{python_module hatch-fancy-pypi-readme} BuildRequires: %{python_module hatchling} BuildRequires: %{python_module packaging} BuildRequires: %{python_module pip} -BuildRequires: %{python_module pydantic-core = 2.39.0} +BuildRequires: %{python_module pydantic-core = 2.41.5} BuildRequires: %{python_module wheel} BuildRequires: fdupes BuildRequires: python-rpm-macros @@ -65,9 +57,9 @@ BuildRequires: %{python_module python-dotenv >= 0.10.4} BuildRequires: %{python_module rich} BuildRequires: %{python_module typing-inspection} %endif -Requires: python-annotated-types >= 0.4.0 -Requires: python-pydantic-core = 2.39.0 -Requires: python-typing-extensions >= 4.12.2 +Requires: python-annotated-types >= 0.6.0 +Requires: python-pydantic-core = 2.41.5 +Requires: python-typing-extensions >= 4.14.1 Requires: python-typing-inspection BuildArch: noarch %python_subpackages @@ -78,6 +70,10 @@ Data validation and settings management using Python type hinting. %prep %autosetup -p1 -n pydantic-%{version} +# FIXME: make it compatible with the older version of setuptools. +# make sure to remove this hack once we have a newer version of setuptools. +sed -i '/.*Programming Language :: Python :: 3\.14.*/d' pyproject.toml + %build %pyproject_wheel diff --git a/support-pydantic-core-2.39.0.patch b/support-pydantic-core-2.39.0.patch deleted file mode 100644 index c2e2d12..0000000 --- a/support-pydantic-core-2.39.0.patch +++ /dev/null @@ -1,293 +0,0 @@ -From d6c65493a8436b22733d0f04d0bb3df1bc952ac9 Mon Sep 17 00:00:00 2001 -From: Viicos <65306057+Viicos@users.noreply.github.com> -Date: Fri, 16 May 2025 15:46:24 +0200 -Subject: [PATCH 1/8] Add `UNSET` sentinel - ---- - pydantic/_internal/_generate_schema.py | 3 + - pydantic/fields.py | 4 +- - pydantic/json_schema.py | 7 +- - pyproject.toml | 2 +- - 5 files changed, 15 insertions(+), 122 deletions(-) - -Index: pydantic-2.11.7/pydantic/_internal/_generate_schema.py -=================================================================== ---- pydantic-2.11.7.orig/pydantic/_internal/_generate_schema.py -+++ pydantic-2.11.7/pydantic/_internal/_generate_schema.py -@@ -42,6 +42,7 @@ from zoneinfo import ZoneInfo - - import typing_extensions - from pydantic_core import ( -+ MISSING, - CoreSchema, - MultiHostUrl, - PydanticCustomError, -@@ -1050,6 +1051,8 @@ class GenerateSchema: - return core_schema.multi_host_url_schema() - elif obj is None or obj is _typing_extra.NoneType: - return core_schema.none_schema() -+ if obj is MISSING: -+ return core_schema.missing_sentinel_schema() - elif obj in IP_TYPES: - return self._ip_schema(obj) - elif obj in TUPLE_TYPES: -Index: pydantic-2.11.7/pydantic/fields.py -=================================================================== ---- pydantic-2.11.7.orig/pydantic/fields.py -+++ pydantic-2.11.7/pydantic/fields.py -@@ -15,7 +15,7 @@ from warnings import warn - - import annotated_types - import typing_extensions --from pydantic_core import PydanticUndefined -+from pydantic_core import MISSING, PydanticUndefined - from typing_extensions import Self, TypeAlias, Unpack, deprecated - from typing_inspection import typing_objects - from typing_inspection.introspection import UNKNOWN, AnnotationSource, ForbiddenQualifier, Qualifier, inspect_annotation -@@ -392,7 +392,7 @@ class FieldInfo(_repr.Representation): - Returns: - A field object with the passed values. - """ -- if annotation is default: -+ if annotation is not MISSING and annotation is default: - raise PydanticUserError( - 'Error when building FieldInfo from annotated attribute. ' - "Make sure you don't have any field name clashing with a type annotation.", -Index: pydantic-2.11.7/pydantic/json_schema.py -=================================================================== ---- pydantic-2.11.7.orig/pydantic/json_schema.py -+++ pydantic-2.11.7/pydantic/json_schema.py -@@ -36,7 +36,7 @@ from typing import ( - ) - - import pydantic_core --from pydantic_core import CoreSchema, PydanticOmit, core_schema, to_jsonable_python -+from pydantic_core import MISSING, CoreSchema, PydanticOmit, core_schema, to_jsonable_python - from pydantic_core.core_schema import ComputedField - from typing_extensions import TypeAlias, assert_never, deprecated, final - from typing_inspection.introspection import get_literal_values -@@ -805,6 +805,17 @@ class GenerateJsonSchema: - result['type'] = 'null' - return result - -+ def missing_sentinel_schema(self, schema: core_schema.MissingSentinelSchema) -> JsonSchemaValue: -+ """Generates a JSON schema that matches the `MISSING` sentinel value. -+ -+ Args: -+ schema: The core schema. -+ -+ Returns: -+ The generated JSON schema. -+ """ -+ raise PydanticOmit -+ - def enum_schema(self, schema: core_schema.EnumSchema) -> JsonSchemaValue: - """Generates a JSON schema that matches an Enum value. - -@@ -1109,7 +1120,7 @@ class GenerateJsonSchema: - json_schema = self.generate_inner(schema['schema']) - - default = self.get_default_value(schema) -- if default is NoDefault: -+ if default is NoDefault or default is MISSING: - return json_schema - - # we reflect the application of custom plain, no-info serializers to defaults for -Index: pydantic-2.11.7/pydantic/version.py -=================================================================== ---- pydantic-2.11.7.orig/pydantic/version.py -+++ pydantic-2.11.7/pydantic/version.py -@@ -66,7 +66,7 @@ def version_info() -> str: - def check_pydantic_core_version() -> bool: - """Check that the installed `pydantic-core` dependency is compatible.""" - # Keep this in sync with the version constraint in the `pyproject.toml` dependencies: -- return __pydantic_core_version__ == '2.35.1' -+ return __pydantic_core_version__ == '2.39.0' - - - def parse_mypy_version(version: str) -> tuple[int, int, int]: -Index: pydantic-2.11.7/docs/concepts/experimental.md -=================================================================== ---- pydantic-2.11.7.orig/docs/concepts/experimental.md -+++ pydantic-2.11.7/docs/concepts/experimental.md -@@ -502,3 +502,49 @@ args, kwargs = val.validate_json('{"args - print(args, kwargs) - #> ('arg1',) {'extra': 1} - ``` -+ -+## `MISSING` sentinel -+ -+The `MISSING` sentinel is a singleton indicating a field value was not provided during validation. -+ -+This singleton can be used as a default value, as an alternative to `None` when it has an explicit -+meaning. During serialization, any field with `MISSING` as a value is excluded from the output. -+ -+```python -+from typing import Union -+ -+from pydantic import BaseModel -+from pydantic.experimental.missing_sentinel import MISSING -+ -+ -+class Configuration(BaseModel): -+ timeout: Union[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 discrimate between the sentinel and other values: -+timeout = conf.timeout if conf.timeout is not MISSING else defaults['timeout'] -+``` -+ -+This feature is marked as experimental because it relies on the draft [PEP 661](https://peps.python.org/pep-0661/), introducing sentinels in the standard library. -+ -+As such, the following limitations currently apply: -+ -+* Static type checking of sentinels is only supported with Pyright -+ [1.1.402](https://github.com/microsoft/pyright/releases/tag/1.1.402) -+ or greater, and the `enableExperimentalFeatures` type evaluation setting -+ should be enabled. -+* Pickling of models containing `MISSING` as a value is not supported. -Index: pydantic-2.11.7/docs/errors/validation_errors.md -=================================================================== ---- pydantic-2.11.7.orig/docs/errors/validation_errors.md -+++ pydantic-2.11.7/docs/errors/validation_errors.md -@@ -1384,6 +1384,27 @@ except ValidationError as exc: - #> 'missing_positional_only_argument' - ``` - -+## `missing_sentinel_error` -+ -+This error is raised when the experimental `MISSING` sentinel is the only value allowed, and wasn't -+provided during validation: -+ -+```python -+from pydantic import BaseModel, ValidationError -+from pydantic.experimental.missing_sentinel import MISSING -+ -+ -+class Model(BaseModel): -+ f: MISSING -+ -+ -+try: -+ Model(f=1) -+except ValidationError as exc: -+ print(repr(exc.errors()[0]['type'])) -+ #> 'missing_sentinel_error' -+``` -+ - ## `model_attributes_type` - - This error is raised when the input value is not a valid dictionary, model instance, or instance that fields can be extracted from: -Index: pydantic-2.11.7/pydantic/experimental/missing_sentinel.py -=================================================================== ---- /dev/null -+++ pydantic-2.11.7/pydantic/experimental/missing_sentinel.py -@@ -0,0 +1,5 @@ -+"""Experimental module exposing a function a `MISSING` sentinel.""" -+ -+from pydantic_core import MISSING -+ -+__all__ = ('MISSING',) -Index: pydantic-2.11.7/pyproject.toml -=================================================================== ---- pydantic-2.11.7.orig/pyproject.toml -+++ pydantic-2.11.7/pyproject.toml -@@ -46,7 +46,7 @@ dependencies = [ - 'typing-extensions>=4.13.0', - 'annotated-types>=0.6.0', - # Keep this in sync with the version in the `check_pydantic_core_version()` function: -- 'pydantic-core==2.35.1', -+ 'pydantic-core==2.39.0', - 'typing-inspection>=0.4.0', - ] - dynamic = ['version', 'readme'] -Index: pydantic-2.11.7/tests/test_missing_sentinel.py -=================================================================== ---- /dev/null -+++ pydantic-2.11.7/tests/test_missing_sentinel.py -@@ -0,0 +1,71 @@ -+import pickle -+from typing import Union -+ -+import pytest -+from pydantic_core import MISSING, PydanticSerializationUnexpectedValue -+ -+from pydantic import BaseModel, TypeAdapter, ValidationError -+ -+ -+def test_missing_sentinel_model() -> None: -+ class Model(BaseModel): -+ f: Union[int, MISSING] = MISSING -+ g: MISSING = MISSING -+ -+ m1 = Model() -+ -+ assert m1.model_dump() == {} -+ assert m1.model_dump_json() == '{}' -+ -+ m2 = Model.model_validate({'f': MISSING, 'g': MISSING}) -+ -+ assert m2.f is MISSING -+ assert m2.g is MISSING -+ -+ m3 = Model(f=1) -+ -+ assert m3.model_dump() == {'f': 1} -+ assert m3.model_dump_json() == '{"f":1}' -+ -+ -+def test_missing_sentinel_type_adapter() -> None: -+ """Note that this usage isn't explicitly supported (and useless in practice).""" -+ -+ # TODO Remove annotation with PEP 747: -+ ta: TypeAdapter[object] = TypeAdapter(MISSING) -+ -+ assert ta.validate_python(MISSING) is MISSING -+ -+ with pytest.raises(ValidationError) as exc_info: -+ ta.validate_python(1) -+ -+ assert exc_info.value.errors()[0]['type'] == 'missing_sentinel_error' -+ -+ assert ta.dump_python(MISSING) is MISSING -+ -+ with pytest.raises(PydanticSerializationUnexpectedValue): -+ ta.dump_python(1) -+ -+ -+# Defined in module to be picklable: -+class ModelPickle(BaseModel): -+ f: Union[int, MISSING] = MISSING -+ -+ -+@pytest.mark.xfail(reason="PEP 661 sentinels aren't picklable yet in the experimental typing-extensions implementation") -+def test_missing_sentinel_pickle() -> None: -+ m = ModelPickle() -+ m_reconstructed = pickle.loads(pickle.dumps(m)) -+ -+ assert m_reconstructed.f is MISSING -+ -+ -+def test_missing_sentinel_json_schema() -> None: -+ class Model(BaseModel): -+ f: Union[int, MISSING] = MISSING -+ g: MISSING = MISSING -+ h: MISSING -+ -+ assert Model.model_json_schema()['properties'] == { -+ 'f': {'title': 'F', 'type': 'integer'}, -+ }