diff --git a/CITATION.cff b/CITATION.cff index 05ab123e174..bd3b574cd5b 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -44,5 +44,5 @@ keywords: - hints - typing license: MIT -version: v2.11.0 -date-released: 2025-03-27 +version: v2.11.1 +date-released: 2025-03-28 diff --git a/HISTORY.md b/HISTORY.md index 640fa8dd405..02cd05498a2 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,13 @@ +## v2.11.1 (2025-03-28) + +[GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.1) + +### What's Changed + +#### Fixes + +* Do not override `'definitions-ref'` schemas containing serialization schemas or metadata by @Viicos in [#11644](https://github.com/pydantic/pydantic/pull/11644) + ## v2.11.0 (2025-03-27) [GitHub release](https://github.com/pydantic/pydantic/releases/tag/v2.11.0) @@ -7,7 +17,7 @@ Pydantic v2.11 is a version strongly focused on build time performance of Pydantic models (and core schema generation in general). See the [blog post](https://pydantic.dev/articles/pydantic-v2-11-release) for more details. -#### Pacaking +#### Packaging * Bump `pydantic-core` to v2.33.0 by @Viicos in [#11631](https://github.com/pydantic/pydantic/pull/11631) @@ -2030,7 +2040,7 @@ See [this post](https://docs.pydantic.dev/blog/pydantic-v2-alpha/) for more deta ## v1.10.1 (2022-08-31) -* Add `__hash__` method to `pydancic.color.Color` class, [#4454](https://github.com/pydantic/pydantic/pull/4454) by @czaki +* Add `__hash__` method to `pydantic.color.Color` class, [#4454](https://github.com/pydantic/pydantic/pull/4454) by @czaki ## v1.10.0 (2022-08-30) diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py index 42aefcba478..c7efbf5b209 100644 --- a/pydantic/_internal/_generate_schema.py +++ b/pydantic/_internal/_generate_schema.py @@ -2813,7 +2813,7 @@ def _resolve_definition(self, ref: str, definitions: dict[str, CoreSchema]) -> C # Some `'definition-ref'` schemas might act as "intermediate" references (e.g. when using # a PEP 695 type alias (which is referenceable) that references another PEP 695 type alias): visited: set[str] = set() - while definition['type'] == 'definition-ref': + while definition['type'] == 'definition-ref' and _inlining_behavior(definition) == 'inline': schema_ref = definition['schema_ref'] if schema_ref in visited: raise PydanticUserError( diff --git a/pydantic/_internal/_generics.py b/pydantic/_internal/_generics.py index fe2980d25b7..80136766612 100644 --- a/pydantic/_internal/_generics.py +++ b/pydantic/_internal/_generics.py @@ -356,7 +356,7 @@ class Model[T, U, V = int](BaseModel): ... map_generic_model_arguments(Model, (str,)) #> TypeError: Too few arguments for ; actual 1, expected at least 2 - map_generic_model_argumenst(Model, (str, bytes, int, complex)) + map_generic_model_arguments(Model, (str, bytes, int, complex)) #> TypeError: Too many arguments for ; actual 4, expected 3 ``` diff --git a/pydantic/dataclasses.py b/pydantic/dataclasses.py index 5111bc3e894..f1ac051dab2 100644 --- a/pydantic/dataclasses.py +++ b/pydantic/dataclasses.py @@ -330,7 +330,7 @@ def rebuild_dataclass( for attr in ('__pydantic_core_schema__', '__pydantic_validator__', '__pydantic_serializer__'): if attr in cls.__dict__: # Deleting the validator/serializer is necessary as otherwise they can get reused in - # pycantic-core. Same applies for the core schema that can be reused in schema generation. + # pydantic-core. Same applies for the core schema that can be reused in schema generation. delattr(cls, attr) cls.__pydantic_complete__ = False diff --git a/pydantic/version.py b/pydantic/version.py index 99e874d45cc..1c15d010a49 100644 --- a/pydantic/version.py +++ b/pydantic/version.py @@ -6,7 +6,7 @@ __all__ = 'VERSION', 'version_info' -VERSION = '2.11.0' +VERSION = '2.11.1' """The version of Pydantic.""" diff --git a/tests/test_forward_ref.py b/tests/test_forward_ref.py index 2e91eab0d3c..e400c486f9c 100644 --- a/tests/test_forward_ref.py +++ b/tests/test_forward_ref.py @@ -1361,7 +1361,7 @@ class Model[T](BaseModel): @pytest.mark.skipif(sys.version_info < (3, 12), reason='Test related to PEP 695 syntax.') -def test_pep695_generics_syntax_arbitry_class(create_module) -> None: +def test_pep695_generics_syntax_arbitrary_class(create_module) -> None: mod_1 = create_module( """ from typing import TypedDict diff --git a/tests/test_type_alias_type.py b/tests/test_type_alias_type.py index 15f727b415e..dac87904ec7 100644 --- a/tests/test_type_alias_type.py +++ b/tests/test_type_alias_type.py @@ -7,7 +7,7 @@ from annotated_types import MaxLen from typing_extensions import TypeAliasType -from pydantic import BaseModel, PydanticUserError, TypeAdapter, ValidationError +from pydantic import BaseModel, PlainSerializer, PydanticUserError, TypeAdapter, ValidationError, WithJsonSchema T = TypeVar('T') @@ -430,3 +430,47 @@ class MyModel(BaseModel): assert exc_info.value.code == 'circular-reference-schema' assert exc_info.value.message.startswith('tests.test_type_alias_type.C') + + +def test_type_alias_type_with_serialization() -> None: + """Regression test for https://github.com/pydantic/pydantic/issues/11642. + + The issue lied in the definition resolving logic, which wasn't taking + possible metadata or serialization attached to a `'definition-ref'` schema. + + In this example, the core schema for `B` — before schema cleaning — will look like: + + ```python + { + 'type': 'definition-ref', + 'schema_ref': '__main__.A', + 'serialization': {...}, + 'ref': '__main__.B', + } + ``` + + and the "main"/"top-level" core schema is a `'definition-ref'` schema pointing to + this `__main__.B` core schema. + + In schema cleaning, when resolving this top-level core schema, we are recursively + unpack `'definition-ref'` schemas (and will end up with the actual schema for `__main__.A`), + without taking into account the fact that the schema of `B` had a `serialization` key! + """ + + A = TypeAliasType('A', int) + B = TypeAliasType('B', Annotated[A, PlainSerializer(lambda v: 3)]) + + ta = TypeAdapter(B) + + assert ta.dump_python(1) == 3 + + +@pytest.mark.skip_json_schema_validation(reason='Extra info added.') +def test_type_alias_type_with_metadata() -> None: + """Same as `test_type_alias_type_with_serialization()` but with JSON Metadata.""" + + A = TypeAliasType('A', int) + B = TypeAliasType('B', Annotated[A, WithJsonSchema({'type': 'int', 'extra': 1})]) + + ta = TypeAdapter(B) + assert ta.json_schema() == {'type': 'int', 'extra': 1}