|
16 | 16 | IMPORT_READ_ONLY, |
17 | 17 | IMPORT_READ_ONLY_BACKPORT, |
18 | 18 | IMPORT_TYPED_DICT, |
| 19 | + IMPORT_TYPED_DICT_BACKPORT, |
19 | 20 | ) |
20 | 21 | from datamodel_code_generator.types import NOT_REQUIRED_PREFIX, READ_ONLY_PREFIX |
21 | 22 |
|
@@ -88,6 +89,68 @@ def __init__( # noqa: PLR0913 |
88 | 89 | keyword_only=keyword_only, |
89 | 90 | treat_dot_as_module=treat_dot_as_module, |
90 | 91 | ) |
| 92 | + self._setup_closed_extra_items() |
| 93 | + |
| 94 | + def _setup_closed_extra_items(self) -> None: |
| 95 | + """Set up closed and extra_items kwargs based on additionalProperties. |
| 96 | +
|
| 97 | + For PEP 728 TypedDict support: |
| 98 | + - additionalProperties: false -> closed=True |
| 99 | + - additionalProperties: { type: X } -> extra_items=X |
| 100 | +
|
| 101 | + Note: closed=True is not applied to TypedDicts used as base classes, |
| 102 | + as PEP 728 doesn't allow child TypedDicts to add new fields when |
| 103 | + parent has closed=True. |
| 104 | + """ |
| 105 | + additional_props = self.extra_template_data.get("additionalProperties") |
| 106 | + additional_props_type = self.extra_template_data.get("additionalPropertiesType") |
| 107 | + is_base_class = self.extra_template_data.get("is_base_class", False) |
| 108 | + |
| 109 | + typed_dict_kwargs: dict[str, str] = {} |
| 110 | + |
| 111 | + if additional_props is False and not is_base_class: |
| 112 | + typed_dict_kwargs["closed"] = "True" |
| 113 | + elif additional_props_type and not is_base_class: |
| 114 | + typed_dict_kwargs["extra_items"] = additional_props_type |
| 115 | + |
| 116 | + if typed_dict_kwargs: |
| 117 | + self.extra_template_data["typed_dict_kwargs"] = typed_dict_kwargs |
| 118 | + kwargs_str = ", ".join(f"{k}={v}" for k, v in typed_dict_kwargs.items()) |
| 119 | + self.extra_template_data["typed_dict_kwargs_suffix"] = f", {kwargs_str}" |
| 120 | + |
| 121 | + @property |
| 122 | + def _has_typed_dict_kwargs(self) -> bool: |
| 123 | + """Check if this TypedDict has closed or extra_items kwargs.""" |
| 124 | + return bool(self.extra_template_data.get("typed_dict_kwargs")) |
| 125 | + |
| 126 | + @property |
| 127 | + def _use_typeddict_backport(self) -> bool: |
| 128 | + """Check if this TypedDict needs typing_extensions.TypedDict for closed/extra_items.""" |
| 129 | + return bool(self.extra_template_data.get("use_typeddict_backport")) |
| 130 | + |
| 131 | + @property |
| 132 | + def base_class(self) -> str: |
| 133 | + """Get base class string with kwargs if needed. |
| 134 | +
|
| 135 | + For PEP 728 support, includes closed=True or extra_items=X in the base class. |
| 136 | + """ |
| 137 | + base = super().base_class |
| 138 | + typed_dict_kwargs = self.extra_template_data.get("typed_dict_kwargs") |
| 139 | + if typed_dict_kwargs: |
| 140 | + kwargs_str = ", ".join(f"{k}={v}" for k, v in typed_dict_kwargs.items()) |
| 141 | + return f"{base}, {kwargs_str}" |
| 142 | + return base |
| 143 | + |
| 144 | + @property |
| 145 | + def imports(self) -> tuple[Import, ...]: |
| 146 | + """Get imports, using backport TypedDict when closed/extra_items are used on Python < 3.13.""" |
| 147 | + base_imports = list(super().imports) |
| 148 | + |
| 149 | + if self._use_typeddict_backport and self._has_typed_dict_kwargs: |
| 150 | + base_imports = [i for i in base_imports if i != IMPORT_TYPED_DICT] |
| 151 | + base_imports.append(IMPORT_TYPED_DICT_BACKPORT) |
| 152 | + |
| 153 | + return tuple(base_imports) |
91 | 154 |
|
92 | 155 | @property |
93 | 156 | def is_functional_syntax(self) -> bool: |
|
0 commit comments