Skip to content

Commit e34ecfa

Browse files
committed
Harden RootModel config sync
1 parent 96e9535 commit e34ecfa

2 files changed

Lines changed: 45 additions & 10 deletions

File tree

src/datamodel_code_generator/model/pydantic_v2/root_model.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@
2424
_SEQUENCE_BASE_CLASS_TEMPLATE_DATA_KEY = "sequence_base_class"
2525
_SEQUENCE_ITEM_TYPE_TEMPLATE_DATA_KEY = "sequence_item_type"
2626
_SEQUENCE_SLICE_TYPE_TEMPLATE_DATA_KEY = "sequence_slice_type"
27+
_ROOT_MODEL_CONFIG_KEYS: frozenset[str] = frozenset({"regex_engine", "frozen"})
28+
29+
30+
def _root_model_config_items(config: Any) -> list[tuple[str, Any]]:
31+
return [
32+
(field_name, value)
33+
for field_name, value in _config_dict_items(config)
34+
if field_name in _ROOT_MODEL_CONFIG_KEYS and value is not None
35+
]
2736

2837

2938
class RootModel(BaseModel):
@@ -56,28 +65,25 @@ def __init__(
5665

5766
@staticmethod
5867
def _has_meaningful_config(config: Any) -> bool:
68+
has_config = False
5969
match config:
6070
case None:
61-
return False
62-
case dict():
63-
return config.get("regex_engine") is not None or config.get("frozen") is not None
71+
pass
6472
case _:
65-
return getattr(config, "regex_engine", None) is not None or getattr(config, "frozen", None) is not None
73+
has_config = bool(_root_model_config_items(config))
74+
return has_config
6675

6776
def _sync_config_items(self) -> None:
68-
if _CONFIG_ITEMS_TEMPLATE_DATA_KEY in self.extra_template_data:
69-
return
7077
config = self.extra_template_data.get("config")
71-
if not self._has_meaningful_config(config):
72-
self.extra_template_data.pop("config", None)
73-
return
74-
if config_items := _config_dict_items(config):
78+
if config_items := _root_model_config_items(config):
79+
self.extra_template_data["config"] = dict(config_items)
7580
self.extra_template_data[_CONFIG_ITEMS_TEMPLATE_DATA_KEY] = config_items
7681
if IMPORT_CONFIG_DICT not in self._additional_imports:
7782
self._additional_imports.append(IMPORT_CONFIG_DICT)
7883
self.clear_imports_cache()
7984
return
8085
self.extra_template_data.pop("config", None)
86+
self.extra_template_data.pop(_CONFIG_ITEMS_TEMPLATE_DATA_KEY, None)
8187
self._additional_imports = [imp for imp in self._additional_imports if imp != IMPORT_CONFIG_DICT]
8288
self.clear_imports_cache()
8389

tests/model/pydantic_v2/test_root_model.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,32 @@ def test_root_model_renders_late_config_dict() -> None:
314314
assert "model_config = ConfigDict(" in rendered
315315
assert 'regex_engine="python-re",' in rendered
316316
assert _CONFIG_ITEMS_TEMPLATE_DATA_KEY in root_model.extra_template_data
317+
318+
319+
def test_root_model_rebuilds_stale_config_items() -> None:
320+
"""RootModel rebuilds stale config_items before rendering ConfigDict."""
321+
root_model = RootModel(
322+
fields=[
323+
DataModelFieldBase(
324+
name="a",
325+
data_type=DataType(type="str"),
326+
required=True,
327+
)
328+
],
329+
reference=Reference(name="TestRootModel", path="test_root_model"),
330+
extra_template_data=defaultdict(
331+
dict,
332+
{
333+
"test_root_model": {
334+
"config": ConfigDict(regex_engine='"python-re"'),
335+
_CONFIG_ITEMS_TEMPLATE_DATA_KEY: object(),
336+
}
337+
},
338+
),
339+
)
340+
341+
rendered = root_model.render()
342+
343+
assert "model_config = ConfigDict(" in rendered
344+
assert 'regex_engine="python-re",' in rendered
345+
assert root_model.extra_template_data[_CONFIG_ITEMS_TEMPLATE_DATA_KEY] == [("regex_engine", '"python-re"')]

0 commit comments

Comments
 (0)