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: ...