8000 Do not duplicate metadata on model rebuild (#11902) · pydantic/pydantic@1b63218 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1b63218

Browse files
authored
Do not duplicate metadata on model rebuild (#11902)
1 parent 5aefad8 commit 1b63218

File tree

3 files changed

+28
-5
lines changed

3 files changed

+28
-5
lines changed

pydantic/_internal/_fields.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def collect_model_fields( # noqa: C901
231231

232232
# The `from_annotated_attribute()` call below mutates the assigned `Field()`, so make a copy:
233233
original_assignment = (
234-
copy(assigned_value) if not evaluated and isinstance(assigned_value, FieldInfo_) else assigned_value
234+
assigned_value._copy() if not evaluated and isinstance(assigned_value, FieldInfo_) else assigned_value
235235
)
236236

237237
field_info = FieldInfo_.from_annotated_attribute(ann_type, assigned_value, _source=AnnotationSource.CLASS)

pydantic/fields.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import annotated_types
1717
import typing_extensions
1818
from pydantic_core import PydanticUndefined
19-
from typing_extensions import TypeAlias, Unpack, deprecated
19+
from typing_extensions import Self, TypeAlias, Unpack, deprecated
2020
from typing_inspection import typing_objects
2121
from typing_inspection.introspection import UNKNOWN, AnnotationSource, ForbiddenQualifier, Qualifier, inspect_annotation
2222

@@ -349,7 +349,7 @@ class MyModel(pydantic.BaseModel):
349349
field_info_annotations = [a for a in metadata if isinstance(a, FieldInfo)]
350350
field_info = FieldInfo.merge_field_infos(*field_info_annotations, annotation=type_expr)
351351

352-
new_field_info = copy(field_info)
352+
new_field_info = field_info._copy()
353353
new_field_info.annotation = type_expr
354354
new_field_info.frozen = final or field_info.frozen
355355
field_metadata: list[Any] = []
@@ -478,7 +478,7 @@ def merge_field_infos(*field_infos: FieldInfo, **overrides: Any) -> FieldInfo:
478478
"""
479479
if len(field_infos) == 1:
480480
# No merging necessary, but we still need to make a copy and apply the overrides
481-
field_info = copy(field_infos[0])
481+
field_info = field_infos[0]._copy()
482482
field_info._attributes_set.update(overrides)
483483

484484
default_override = overrides.pop('default', PydanticUndefined)
@@ -588,6 +588,15 @@ def _collect_metadata(kwargs: dict[str, Any]) -> list[Any]:
588588
metadata.append(_fields.pydantic_general_metadata(**general_metadata))
589589
return metadata
590590

591+
def _copy(self) -> Self:
592+
copied = copy(self)
593+
for attr_name in ('metadata', '_attributes_set', '_qualifiers'):
594+
# Apply "deep-copy" behavior on collections attributes:
595+
value = getattr(copied, attr_name).copy()
596+
setattr(copied, attr_name, value)
597+
598+
return copied
599+
591600
@property
592601
def deprecation_message(self) -> str | None:
593602
"""The deprecation message to be emitted, or `None` if not set."""

tests/test_fields.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from typing import Union
1+
from typing import Annotated, Union
22

33
import pytest
4+
from annotated_types import Gt
45

56
import pydantic.dataclasses
67
from pydantic import BaseModel, ConfigDict, Field, PydanticUserError, RootModel, ValidationError, computed_field, fields
@@ -188,3 +189,16 @@ class Model(BaseModel):
188189
Model.model_rebuild()
189190

190191
assert Model.model_fields['f'].description == 'test doc'
192+
193+
194+
def test_no_duplicate_metadata_with_assignment_and_rebuild() -> None:
195+
"""https://github.com/pydantic/pydantic/issues/11870"""
196+
197+
class Model(BaseModel):
198+
f: Annotated['Int', Gt(1)] = Field()
199+
200+
Int = int
201+
202+
Model.model_rebuild()
203+
204+
assert len(Model.model_fields['f'].metadata) == 1

0 commit comments

Comments
 (0)
0