Skip to content

Commit bf40653

Browse files
committed
Remove legacy annotation reassignment
1 parent 7d9c4ee commit bf40653

4 files changed

Lines changed: 54 additions & 8 deletions

File tree

src/datamodel_code_generator/model/pydantic_v2/base_model.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,13 @@ def validate_min_max_items(cls, values: Any) -> dict[str, Any]: # noqa: N805
105105
_LEGACY_PYDANTIC_EXTRA_TEMPLATE_PATTERN = re.compile(
106106
r"{%-?\s*(?:if|elif)\s+(?:not\s+)?field\.use_pydantic_extra_annotation_assignment\b"
107107
)
108+
_LEGACY_PYDANTIC_EXTRA_POST_CLASS_PATTERN = re.compile(
109+
r"(?m)^(?P<class_name>[^.\s]+)[ \t]*\.[ \t]*__annotations__[ \t]*"
110+
r"\[[ \t]*(?P<quote>['\"])__pydantic_extra__(?P=quote)[ \t]*\][ \t]*=[^\r\n]*\r?\n"
111+
r"(?:[ \t]*\r?\n)*"
112+
r"(?P=class_name)[ \t]*\.[ \t]*model_rebuild[ \t]*"
113+
r"\([ \t]*force[ \t]*=[ \t]*True[ \t]*\)[ \t]*(?:#[^\r\n]*)?(?:\r?\n)?"
114+
)
108115
_ALIAS_GENERATOR_IMPORTS: dict[str, Import] = {
109116
AliasGenerator.ToCamel.value: IMPORT_ALIAS_GENERATOR_TO_CAMEL,
110117
AliasGenerator.ToPascal.value: IMPORT_ALIAS_GENERATOR_TO_PASCAL,
@@ -119,6 +126,22 @@ def _uses_legacy_pydantic_extra_template(template_file_path: str) -> bool:
119126
return bool(_LEGACY_PYDANTIC_EXTRA_TEMPLATE_PATTERN.search(template_source))
120127

121128

129+
def _strip_legacy_pydantic_extra_post_class_assignment(rendered: str, class_name: str) -> str:
130+
"""Remove the unsupported post-class typed-extra assignment for the rendered model."""
131+
if (
132+
assignment := next(
133+
(
134+
match
135+
for match in _LEGACY_PYDANTIC_EXTRA_POST_CLASS_PATTERN.finditer(rendered)
136+
if match["class_name"] == class_name
137+
),
138+
None,
139+
)
140+
) is None:
141+
return rendered
142+
return f"{rendered[: assignment.start()]}{rendered[assignment.end() :]}"
143+
144+
122145
def _alias_generator_name(value: Any) -> str | None:
123146
generator_name: str | None = None
124147
match value:
@@ -555,6 +578,9 @@ def _render(self, *args: Any, **kwargs: Any) -> str:
555578
"}",
556579
*(kwargs.get("class_body_lines") or ()),
557580
]
581+
return _strip_legacy_pydantic_extra_post_class_assignment(
582+
super()._render(*args, **kwargs), kwargs["class_name"]
583+
)
558584
return super()._render(*args, **kwargs)
559585

560586
@classmethod

tests/data/expected/main/jsonschema/additional_properties_schema_with_properties_legacy_custom_template.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,3 @@ class KnownAndExtra(BaseModel):
1717
'__pydantic_extra__': Dict[str, int],
1818
}
1919
name: str
20-
21-
22-
KnownAndExtra.__annotations__['__pydantic_extra__'] = Dict[str, int]
23-
KnownAndExtra.model_rebuild(force=True)

tests/data/expected/main/jsonschema/additional_properties_self_ref_legacy_custom_template.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,4 @@ class Node(BaseModel):
1919
name: str
2020

2121

22-
Node.__annotations__['__pydantic_extra__'] = Dict[str, "Node"]
23-
Node.model_rebuild(force=True)
24-
25-
2622
Node.model_rebuild()

tests/model/test_base.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141
from datamodel_code_generator.model.pydantic_base import DataModelField as PydanticBaseDataModelField
4242
from datamodel_code_generator.model.pydantic_v2 import BaseModel
4343
from datamodel_code_generator.model.pydantic_v2 import DataModelField as PydanticV2DataModelField
44+
from datamodel_code_generator.model.pydantic_v2.base_model import (
45+
_strip_legacy_pydantic_extra_post_class_assignment,
46+
)
4447
from datamodel_code_generator.model.pydantic_v2.imports import IMPORT_FIELD, IMPORT_MISSING
4548
from datamodel_code_generator.reference import Reference
4649
from datamodel_code_generator.types import ANY, NONE, DataType, Types
@@ -261,9 +264,34 @@ def test_pydantic_v2_legacy_extra_template_supports_relative_custom_path() -> No
261264
rendered = model.render()
262265

263266
assert "'__pydantic_extra__': Dict[str, str]," in rendered
267+
assert "Model.__annotations__['__pydantic_extra__']" not in rendered
268+
assert "Model.model_rebuild(force=True)" not in rendered
264269
assert "locals()" not in rendered
265270

266271

272+
def test_strip_legacy_pydantic_extra_post_class_assignment_is_model_scoped() -> None:
273+
"""Strip only the target model's old assignment while preserving rebuilds and helpers."""
274+
rendered = (
275+
"Helper.__annotations__['__pydantic_extra__'] = Dict[str, int]\n"
276+
"Helper.model_rebuild(force=True)\n"
277+
'Model . __annotations__ [ "__pydantic_extra__" ] = Dict[str, int]\r\n'
278+
"\r\n"
279+
"Model . model_rebuild ( force = True ) # legacy\r\n"
280+
"Model.model_rebuild()\n"
281+
)
282+
283+
assert _strip_legacy_pydantic_extra_post_class_assignment(rendered, "Missing") == rendered
284+
assert _strip_legacy_pydantic_extra_post_class_assignment(rendered, "Model") == (
285+
"Helper.__annotations__['__pydantic_extra__'] = Dict[str, int]\n"
286+
"Helper.model_rebuild(force=True)\n"
287+
"Model.model_rebuild()\n"
288+
)
289+
assert not _strip_legacy_pydantic_extra_post_class_assignment(
290+
"℘Model.__annotations__['__pydantic_extra__'] = Dict[str, int]\n℘Model.model_rebuild(force=True)\n",
291+
"℘Model",
292+
)
293+
294+
267295
def test_pydantic_v2_missing_sentinel_default_keeps_explicit_default() -> None:
268296
"""Test explicit defaults are not replaced by the MISSING sentinel."""
269297
field = PydanticV2DataModelField(

0 commit comments

Comments
 (0)