Skip to content

Commit e223a88

Browse files
authored
fix: quote forward references in recursive RootModel generic parameters (#2720)
1 parent 3aeab90 commit e223a88

3 files changed

Lines changed: 68 additions & 2 deletions

File tree

src/datamodel_code_generator/parser/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1892,11 +1892,11 @@ def __is_new_required_field(self, field: DataModelFieldBase, inherited: set[str]
18921892

18931893
@classmethod
18941894
def __update_type_aliases(cls, models: list[DataModel]) -> None:
1895-
"""Update type aliases to properly handle forward references per PEP 484."""
1895+
"""Update type aliases and RootModels to properly handle forward references per PEP 484."""
18961896
model_index: dict[str, int] = {m.class_name: i for i, m in enumerate(models)}
18971897

18981898
for i, model in enumerate(models):
1899-
if not isinstance(model, TypeAliasBase):
1899+
if not isinstance(model, (TypeAliasBase, pydantic_model_v2.RootModel)):
19001900
continue
19011901
if isinstance(model, TypeStatement):
19021902
continue
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# generated by datamodel-codegen:
2+
# filename: type_alias_recursive.yaml
3+
# timestamp: 2019-07-26T00:00:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from typing import Union
8+
9+
from pydantic import BaseModel, RootModel
10+
11+
12+
class File(BaseModel):
13+
path: str
14+
15+
16+
class Folder(BaseModel):
17+
address: str | None = None
18+
files: list[File]
19+
subfolders: list[Folder] | None = None
20+
21+
22+
class ElementaryType(RootModel[bool | str | int | float | None]):
23+
root: bool | str | int | float | None = None
24+
25+
26+
class JsonType(
27+
RootModel[Union[ElementaryType, list["JsonType"], dict[str, "JsonType"]]]
28+
):
29+
root: Union[ElementaryType, list["JsonType"], dict[str, "JsonType"]]
30+
31+
32+
class Space(BaseModel):
33+
label: str | None = None
34+
data: JsonType | None = None
35+
dual: DualSpace | None = None
36+
37+
38+
class DualSpace(BaseModel):
39+
label: str | None = None
40+
data: JsonType | None = None
41+
predual: Space | None = None
42+
43+
44+
Folder.model_rebuild()
45+
JsonType.model_rebuild()
46+
Space.model_rebuild()

tests/main/openapi/test_main_openapi.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3468,6 +3468,26 @@ def test_main_openapi_type_alias_recursive(output_file: Path) -> None:
34683468
)
34693469

34703470

3471+
def test_main_openapi_type_alias_recursive_pydantic_v2(output_file: Path) -> None:
3472+
"""Test recursive RootModel with forward references in Pydantic v2.
3473+
3474+
Without --use-type-alias, recursive schemas generate RootModel classes.
3475+
Forward references in the generic parameter must be quoted to avoid
3476+
NameError at class definition time.
3477+
"""
3478+
run_main_and_assert(
3479+
input_path=OPEN_API_DATA_PATH / "type_alias_recursive.yaml",
3480+
output_path=output_file,
3481+
input_file_type="openapi",
3482+
assert_func=assert_file_content,
3483+
expected_file="type_alias_recursive_pydantic_v2.py",
3484+
extra_args=[
3485+
"--output-model-type",
3486+
"pydantic_v2.BaseModel",
3487+
],
3488+
)
3489+
3490+
34713491
def test_main_openapi_type_alias_cross_module_collision_a(output_file: Path) -> None:
34723492
"""Test TypeAlias generation for module A in cross-module collision scenario."""
34733493
run_main_and_assert(

0 commit comments

Comments
 (0)