Skip to content

Add TypedDict closed and extra_items support (PEP 728)#2922

Merged
koxudaxi merged 3 commits intomainfrom
feature/typeddict-closed-extra-items
Jan 5, 2026
Merged

Add TypedDict closed and extra_items support (PEP 728)#2922
koxudaxi merged 3 commits intomainfrom
feature/typeddict-closed-extra-items

Conversation

@koxudaxi
Copy link
Copy Markdown
Owner

@koxudaxi koxudaxi commented Jan 5, 2026

Fixes: #2921

Summary by CodeRabbit

  • New Features

    • Support for generating closed TypedDicts (PEP 728) and automatic backporting via typing_extensions for older Python versions.
  • Improvements

    • Improved handling of JSON Schema additionalProperties with precise type hints and better inheritance/base-class resolution for generated TypedDicts.
  • Tests

    • Added tests covering closed TypedDicts, backport behavior, and extra-properties (extra_items) generation.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 5, 2026

Warning

Rate limit exceeded

@koxudaxi has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 19 minutes and 5 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 3c39200 and a479686.

⛔ Files ignored due to path filters (3)
  • src/datamodel_code_generator/model/template/TypedDictFunction.jinja2 is excluded by none and included by none
  • tests/data/jsonschema/typed_dict_closed.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/typed_dict_extra_items.json is excluded by !tests/data/**/*.json and included by none
📒 Files selected for processing (12)
  • src/datamodel_code_generator/_types/generate_config_dict.py
  • src/datamodel_code_generator/_types/parse_config_dict.py
  • src/datamodel_code_generator/_types/parser_config_dicts.py
  • src/datamodel_code_generator/format.py
  • src/datamodel_code_generator/input_model.py
  • src/datamodel_code_generator/model/typed_dict.py
  • src/datamodel_code_generator/parser/jsonschema.py
  • tests/data/expected/main/input_model/config_class.py
  • tests/data/expected/main/jsonschema/typed_dict_closed.py
  • tests/data/expected/main/jsonschema/typed_dict_extra_items.py
  • tests/main/jsonschema/test_main_jsonschema.py
  • tests/model/test_typed_dict.py
📝 Walkthrough

Walkthrough

This PR implements PEP 728 TypedDict support and backport handling: TypedDict declarations are made closed/extra_items-aware, PythonVersion gains a feature flag, JSON Schema parsing propagates typed-dict kwargs and base-class markers, and model rendering includes conditional typing_extensions backport usage.

Changes

Cohort / File(s) Summary
Config TypedDict definitions
src/datamodel_code_generator/_types/generate_config_dict.py, src/datamodel_code_generator/_types/parse_config_dict.py, src/datamodel_code_generator/_types/parser_config_dicts.py
Import TypedDict from typing_extensions; several TypedDicts now declared with closed=True; ParseConfigDict and OpenAPIParserConfigDict gain additional NotRequired[...] fields.
Python version feature flag
src/datamodel_code_generator/format.py
Added has_typed_dict_closed property to PythonVersion to detect PEP 728 support (Python 3.13+).
TypedDict model & imports/backport
src/datamodel_code_generator/model/typed_dict.py, src/datamodel_code_generator/model/imports.py (symbol added)
New backport-aware logic: _setup_closed_extra_items(), _has_typed_dict_kwargs, _use_typeddict_backport, adjusted base_class and imports, and new IMPORT_TYPED_DICT_BACKPORT symbol.
JSON Schema parsing & inheritance flags
src/datamodel_code_generator/parser/jsonschema.py, src/datamodel_code_generator/input_model.py
Propagate additionalProperties as typed_dict_kwargs (closed/extra_items) and additionalPropertiesType; set use_typeddict_backport when target lacks native support; mark and prefer x-is-base-class during schema transforms/merging.
Tests / expected outputs
tests/data/expected/.../config_class.py, tests/data/expected/.../typed_dict_closed*.py, tests/data/expected/.../typed_dict_extra_items.py, tests/main/jsonschema/test_main_jsonschema.py, tests/model/test_typed_dict.py
Updated expected outputs to PEP 728 syntax (closed/extra_items) and added tests exercising closed TypedDict, backport behavior, extra_items handling, and related model/import behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Schema as JSON Schema
    participant Parser as Parser.jsonschema
    participant PV as PythonVersion
    participant TD as TypedDict Model
    participant Gen as Code Generator

    Schema->>Parser: parse schema (includes additionalProperties / $ref / x-is-base-class)
    Parser->>Parser: set_schema_extensions / set_additional_properties
    Parser->>PV: query has_typed_dict_closed
    alt additionalProperties == false
        Parser->>TD: set typed_dict_kwargs {closed=True}
    else additionalProperties is schema
        Parser->>TD: derive additionalPropertiesType
        Parser->>TD: set typed_dict_kwargs {extra_items=Type}
    end
    alt PV lacks native support
        Parser->>TD: set use_typeddict_backport=True
    end

    Note over TD,Gen: Rendering phase
    TD->>TD: _has_typed_dict_kwargs?
    alt backport required
        TD->>Gen: include IMPORT_TYPED_DICT_BACKPORT
        Gen->>Gen: emit class X(TypedDict, closed=True / extra_items=...)
    else native support
        TD->>Gen: include TypedDict import
        Gen->>Gen: emit class X(TypedDict, closed=True / extra_items=...)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

breaking-change-analyzed, breaking-change

Suggested reviewers

  • ilovelinux

Poem

🐰 I nibble schemas, tidy and neat,
Closed doors and extras find their seat,
Backports hop in when new features hide,
PEP 728 dances on code-generation's stride. ✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding TypedDict closed and extra_items support (PEP 728) to the codebase.
Linked Issues check ✅ Passed The PR fully implements the objectives from issue #2921: mapping additionalProperties: false to closed=True and additionalProperties with type boolean to extra_items=bool, without requiring extra flags.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing PEP 728 TypedDict support. No out-of-scope modifications were detected in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 96.88% which is sufficient. The required threshold is 80.00%.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 5, 2026

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Jan 5, 2026

CodSpeed Performance Report

Merging #2922 will not alter performance

Comparing feature/typeddict-closed-extra-items (a479686) with main (54c3ed9)

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

Summary

✅ 11 untouched
⏩ 98 skipped1

Footnotes

  1. 98 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Fix all issues with AI Agents 🤖
In @src/datamodel_code_generator/_types/generate_config_dict.py:
- Around line 6-8: Remove the unused TypedDict import from the stdlib typing
import: keep "from typing import TYPE_CHECKING, Any" (remove TypedDict) and rely
on the backport "from typing_extensions import NotRequired, TypedDict" already
present; update the import line(s) accordingly so only typing_extensions
provides TypedDict (needed for closed=True) and avoid the shadowed stdlib
symbol.

In @src/datamodel_code_generator/_types/parser_config_dicts.py:
- Around line 6-8: Remove the redundant TypedDict import from the typing import
list in parser_config_dicts.py: update the line that currently reads "from
typing import TYPE_CHECKING, Any, TypeAlias, TypedDict" to drop TypedDict so
only TYPE_CHECKING, Any, and TypeAlias remain, relying on the TypedDict imported
from typing_extensions; this removes the shadowed/unused symbol and keeps
NotRequired/TypedDict usage from typing_extensions intact.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/data/expected/main/input_model/config_class.py (1)

9-13: Remove redundant TypedDict import from typing.

Line 9 imports TypedDict from typing, but line 13 re-imports it from typing_extensions. Since typing_extensions.TypedDict is required for the closed=True parameter (PEP 728 backport), the import from typing on line 9 is unused and should be removed.

🔎 Proposed fix
-from typing import Any, Literal, TypeAlias, TypedDict
+from typing import Any, Literal, TypeAlias
🧹 Nitpick comments (2)
src/datamodel_code_generator/model/typed_dict.py (1)

128-139: Minor duplication of kwargs string computation.

The kwargs_str is computed in both _setup_closed_extra_items (stored as typed_dict_kwargs_suffix) and again in base_class. Consider reusing the pre-computed suffix:

🔎 Proposed simplification
     @property
     def base_class(self) -> str:
         """Get base class string with kwargs if needed.

         For PEP 728 support, includes closed=True or extra_items=X in the base class.
         """
         base = super().base_class
-        typed_dict_kwargs = self.extra_template_data.get("typed_dict_kwargs")
-        if typed_dict_kwargs:
-            kwargs_str = ", ".join(f"{k}={v}" for k, v in typed_dict_kwargs.items())
-            return f"{base}, {kwargs_str}"
+        kwargs_suffix = self.extra_template_data.get("typed_dict_kwargs_suffix")
+        if kwargs_suffix:
+            return f"{base}{kwargs_suffix}"
         return base
tests/main/jsonschema/test_main_jsonschema.py (1)

4262-4316: TypedDict closed/extra_items tests align with PEP 728; consider minor coverage + skip refinements

The three new tests correctly exercise the intended mapping:

  • typed_dict_closed.jsonTypedDict(..., closed=True) for additionalProperties: false (with both native 3.13 and 3.10 backport via different golden files).
  • typed_dict_extra_items.jsonTypedDict(..., extra_items=...) for schema-valued additionalProperties on a 3.13 target.

Two small, non-blocking suggestions:

  1. Backport coverage for extra_items as well
    If the implementation also backports extra_items via typing_extensions for < 3.13 (as it does for closed), a companion test would harden that path against regressions, mirroring test_main_typed_dict_closed_backport.

  2. Unify Black compatibility gating for the backport test (Lines 4280–4283)
    Instead of hard-coding black.__version__.split(".")[0] == "22", you can lean on the existing helper for consistency and future-proofing:

    Suggested tweak to the skip condition
    -@pytest.mark.skipif(
    -    black.__version__.split(".")[0] == "22",
    -    reason="Installed black doesn't support Python version 3.10",
    -)
    +@pytest.mark.skipif(
    +    not is_supported_in_black(PythonVersion.PY_310),
    +    reason="Installed black doesn't support Python 3.10 formatting",
    +)
    def test_main_typed_dict_closed_backport(output_file: Path) -> None:
        ...

    And, if you do want explicit extra_items backport coverage, something along these lines would stay consistent with the existing pattern:

    Optional extra_items backport test sketch
    +@pytest.mark.skipif(
    +    not is_supported_in_black(PythonVersion.PY_310),
    +    reason="Installed black doesn't support Python 3.10 formatting",
    +)
    +def test_main_typed_dict_extra_items_backport(output_file: Path) -> None:
    +    """Test TypedDict extra_items uses typing_extensions on Python < 3.13."""
    +    run_main_and_assert(
    +        input_path=JSON_SCHEMA_DATA_PATH / "typed_dict_extra_items.json",
    +        output_path=output_file,
    +        input_file_type=None,
    +        assert_func=assert_file_content,
    +        expected_file="typed_dict_extra_items_py310.py",
    +        extra_args=[
    +            "--output-model-type",
    +            "typing.TypedDict",
    +            "--target-python-version",
    +            "3.10",
    +        ],
    +    )
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 58e73ed and e9f2419.

⛔ Files ignored due to path filters (3)
  • src/datamodel_code_generator/model/template/TypedDictFunction.jinja2 is excluded by none and included by none
  • tests/data/jsonschema/typed_dict_closed.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/typed_dict_extra_items.json is excluded by !tests/data/**/*.json and included by none
📒 Files selected for processing (12)
  • src/datamodel_code_generator/_types/generate_config_dict.py
  • src/datamodel_code_generator/_types/parse_config_dict.py
  • src/datamodel_code_generator/_types/parser_config_dicts.py
  • src/datamodel_code_generator/format.py
  • src/datamodel_code_generator/model/typed_dict.py
  • src/datamodel_code_generator/parser/jsonschema.py
  • tests/data/expected/main/input_model/config_class.py
  • tests/data/expected/main/jsonschema/typed_dict_closed.py
  • tests/data/expected/main/jsonschema/typed_dict_closed_py310.py
  • tests/data/expected/main/jsonschema/typed_dict_extra_items.py
  • tests/main/jsonschema/test_main_jsonschema.py
  • tests/model/test_typed_dict.py
🧰 Additional context used
🧬 Code graph analysis (8)
tests/data/expected/main/jsonschema/typed_dict_extra_items.py (1)
src/datamodel_code_generator/model/typed_dict.py (1)
  • TypedDict (50-175)
src/datamodel_code_generator/_types/generate_config_dict.py (1)
src/datamodel_code_generator/model/typed_dict.py (1)
  • TypedDict (50-175)
tests/data/expected/main/jsonschema/typed_dict_closed.py (2)
src/datamodel_code_generator/model/typed_dict.py (1)
  • TypedDict (50-175)
tests/data/expected/main/jsonschema/typed_dict_closed_py310.py (1)
  • ClosedModel (10-12)
tests/model/test_typed_dict.py (1)
src/datamodel_code_generator/model/typed_dict.py (6)
  • imports (142-151)
  • imports (225-231)
  • DataModelField (178-231)
  • TypedDict (50-175)
  • _has_typed_dict_kwargs (119-121)
  • base_class (129-139)
src/datamodel_code_generator/parser/jsonschema.py (1)
src/datamodel_code_generator/format.py (1)
  • has_typed_dict_closed (132-134)
tests/data/expected/main/input_model/config_class.py (2)
src/datamodel_code_generator/model/typed_dict.py (1)
  • TypedDict (50-175)
src/datamodel_code_generator/enums.py (1)
  • DataclassArguments (15-27)
src/datamodel_code_generator/model/typed_dict.py (3)
src/datamodel_code_generator/model/base.py (3)
  • base_class (849-851)
  • imports (326-351)
  • imports (823-828)
src/datamodel_code_generator/types.py (1)
  • imports (620-674)
src/datamodel_code_generator/imports.py (2)
  • Import (20-38)
  • append (74-89)
tests/main/jsonschema/test_main_jsonschema.py (1)
tests/main/conftest.py (2)
  • output_file (99-101)
  • run_main_and_assert (245-409)
🪛 Ruff (0.14.10)
src/datamodel_code_generator/_types/generate_config_dict.py

8-8: Redefinition of unused TypedDict from line 6

(F811)

src/datamodel_code_generator/_types/parser_config_dicts.py

8-8: Redefinition of unused TypedDict from line 6

(F811)

tests/data/expected/main/input_model/config_class.py

13-13: Redefinition of unused TypedDict from line 9

(F811)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: 3.10 on macOS
  • GitHub Check: 3.14 on Windows
  • GitHub Check: 3.11 on macOS
  • GitHub Check: 3.13 on macOS
  • GitHub Check: 3.12 on macOS
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.14 on Ubuntu
  • GitHub Check: 3.13 on Windows
  • GitHub Check: benchmarks
  • GitHub Check: Analyze (python)
🔇 Additional comments (16)
tests/data/expected/main/jsonschema/typed_dict_extra_items.py (1)

1-12: LGTM!

The test expectation file correctly represents the expected output for Python 3.13+ where extra_items is natively supported in typing.TypedDict. The imports from typing and the class syntax are correct for PEP 728 semantics.

src/datamodel_code_generator/format.py (1)

131-135: LGTM!

The has_typed_dict_closed property correctly gates the PEP 728 feature (closed/extra_items) to Python 3.13+, following the established pattern for version-specific feature detection.

src/datamodel_code_generator/_types/parse_config_dict.py (1)

6-16: LGTM!

The use of typing_extensions.TypedDict with closed=True is correct for maintaining Python < 3.13 compatibility. This is a good example of dogfooding the PEP 728 support being implemented.

src/datamodel_code_generator/model/typed_dict.py (2)

92-116: LGTM - Clean PEP 728 setup logic.

The implementation correctly maps JSON Schema additionalProperties semantics to PEP 728 TypedDict parameters. The identity check additional_props is False is appropriate here since JSON Schema explicitly uses false for closed semantics.


141-151: LGTM - Correct backport import handling.

The logic correctly replaces typing.TypedDict with typing_extensions.TypedDict only when both conditions are met: the backport is required (Python < 3.13) and the TypedDict uses closed/extra_items kwargs.

src/datamodel_code_generator/_types/generate_config_dict.py (1)

41-41: LGTM - Closed TypedDict for config validation.

Using closed=True on configuration TypedDicts is a good practice as it helps catch typos in config keys at type-check time.

tests/data/expected/main/jsonschema/typed_dict_closed.py (1)

1-12: LGTM!

The test expectation correctly represents the generated output for Python 3.13+ where typing.TypedDict natively supports the closed parameter. The import from typing (not typing_extensions) is correct for this target version.

src/datamodel_code_generator/_types/parser_config_dicts.py (1)

43-43: LGTM - Consistent closed TypedDict hierarchy.

All parser config TypedDicts are now closed, which provides type-safety benefits by catching invalid config keys. The inheritance chain correctly declares closed=True at each level, and the new openapi_include_paths field is properly added to OpenAPIParserConfigDict.

Also applies to: 163-163, 169-170, 173-177

tests/data/expected/main/jsonschema/typed_dict_closed_py310.py (1)

1-12: LGTM!

This test fixture correctly demonstrates the expected output for a closed TypedDict on Python 3.10. The use of typing_extensions.TypedDict for the closed=True parameter is appropriate since PEP 728 features require a backport for Python versions before 3.13.

tests/data/expected/main/input_model/config_class.py (2)

45-56: LGTM!

The DataclassArguments TypedDict is correctly marked as closed=True, which aligns with the PEP 728 implementation for restricting additional keys in configuration dictionaries.


117-248: LGTM!

The GenerateConfig TypedDict with closed=True is appropriate for a configuration type that should not accept arbitrary extra keys.

src/datamodel_code_generator/parser/jsonschema.py (2)

1138-1157: LGTM! Clean implementation of PEP 728 TypedDict support.

The logic correctly maps JSON Schema additionalProperties semantics to TypedDict parameters:

  • additionalProperties: false → sets backport flag for closed=True
  • additionalProperties: { type: X } → extracts type hint for extra_items

The backport flag is appropriately set based on target_python_version.has_typed_dict_closed.


1150-1157: Verify behavior when _build_lightweight_type returns None.

When additionalProperties is a complex schema that _build_lightweight_type cannot resolve, neither additionalPropertiesType nor use_typeddict_backport is set. This means the TypedDict won't have an extra_items constraint in such cases.

This appears to be intentional graceful degradation, but please confirm this is the expected behavior for complex additionalProperties schemas that can't be resolved to a simple type hint.

tests/model/test_typed_dict.py (3)

5-9: LGTM! Imports support new PEP 728 test functionality.

The new imports are all necessary for testing TypedDict's closed and extra_items features:

  • defaultdict for test helper construction
  • IMPORT_TYPED_DICT and IMPORT_TYPED_DICT_BACKPORT constants for verifying import selection
  • TypedDict and Reference for creating test instances

40-48: LGTM! Well-designed test helper function.

The _create_typed_dict helper effectively reduces duplication across tests and clearly encapsulates the setup logic. The defaultdict structure correctly mirrors the TypedDict constructor's expectations for extra_template_data.


51-122: LGTM! Comprehensive test coverage for PEP 728 TypedDict features.

The test suite thoroughly validates the TypedDict implementation:

  • ✅ Closed and extra_items kwargs generation from schema properties
  • ✅ Base class composition with and without kwargs
  • ✅ Import selection (standard typing vs typing_extensions backport)
  • ✅ Edge case: additionalProperties: true correctly excluded from kwargs

The tests correctly verify string values in typed_dict_kwargs (e.g., "True", "bool") for template rendering and code-formatted typed_dict_kwargs_suffix values.

Note: Tests access private method _has_typed_dict_kwargs. While acceptable in unit tests, this coupling could make tests fragile if the implementation's internal structure changes.

Comment thread src/datamodel_code_generator/_types/generate_config_dict.py
Comment thread src/datamodel_code_generator/_types/parser_config_dicts.py
@koxudaxi koxudaxi force-pushed the feature/typeddict-closed-extra-items branch from e9f2419 to 3c39200 Compare January 5, 2026 15:50
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Fix all issues with AI Agents 🤖
In @tests/model/test_typed_dict.py:
- Around line 51-122: Add tests to cover the is_base_class=True path so that
TypedDict kwargs are not applied when the typed dict is a base class: create two
tests using _create_typed_dict with {"additionalProperties": False,
"is_base_class": True} and {"additionalPropertiesType": "str", "is_base_class":
True} respectively, and assert in both that td._has_typed_dict_kwargs is False,
td.extra_template_data.get("typed_dict_kwargs") is None, and td.base_class ==
"TypedDict"; this verifies the logic in
src/datamodel_code_generator/model/typed_dict.py (the branches around
is_base_class handling that skip setting closed=True or extra_items).

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/data/expected/main/input_model/config_class.py (1)

9-13: Remove unused TypedDict import from typing in generated output.

This generated file has a redundant TypedDict import on line 9 that is shadowed by line 13. This indicates the code generator may be producing unnecessary imports when generating closed TypedDicts.

🔎 Expected fix in generated output
-from typing import Any, Literal, TypeAlias, TypedDict
+from typing import Any, Literal, TypeAlias
♻️ Duplicate comments (2)
src/datamodel_code_generator/_types/generate_config_dict.py (1)

6-8: Remove unused TypedDict import from typing.

The TypedDict import on line 6 is shadowed by the import on line 8 from typing_extensions. Since closed=True requires the typing_extensions backport, only that import is needed.

🔎 Proposed fix
-from typing import TYPE_CHECKING, Any, TypedDict
+from typing import TYPE_CHECKING, Any
 
 from typing_extensions import NotRequired, TypedDict
src/datamodel_code_generator/_types/parser_config_dicts.py (1)

6-8: Remove unused TypedDict import from typing.

The TypedDict import on line 6 is shadowed by line 8's import from typing_extensions. Since closed=True requires the typing_extensions backport, only that import is needed.

🔎 Proposed fix
-from typing import TYPE_CHECKING, Any, TypeAlias, TypedDict
+from typing import TYPE_CHECKING, Any, TypeAlias
 
 from typing_extensions import NotRequired, TypedDict
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9f2419 and 3c39200.

⛔ Files ignored due to path filters (3)
  • src/datamodel_code_generator/model/template/TypedDictFunction.jinja2 is excluded by none and included by none
  • tests/data/jsonschema/typed_dict_closed.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/typed_dict_extra_items.json is excluded by !tests/data/**/*.json and included by none
📒 Files selected for processing (13)
  • src/datamodel_code_generator/_types/generate_config_dict.py
  • src/datamodel_code_generator/_types/parse_config_dict.py
  • src/datamodel_code_generator/_types/parser_config_dicts.py
  • src/datamodel_code_generator/format.py
  • src/datamodel_code_generator/input_model.py
  • src/datamodel_code_generator/model/typed_dict.py
  • src/datamodel_code_generator/parser/jsonschema.py
  • tests/data/expected/main/input_model/config_class.py
  • tests/data/expected/main/jsonschema/typed_dict_closed.py
  • tests/data/expected/main/jsonschema/typed_dict_closed_py310.py
  • tests/data/expected/main/jsonschema/typed_dict_extra_items.py
  • tests/main/jsonschema/test_main_jsonschema.py
  • tests/model/test_typed_dict.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/datamodel_code_generator/format.py
  • src/datamodel_code_generator/parser/jsonschema.py
  • tests/data/expected/main/jsonschema/typed_dict_closed.py
  • tests/main/jsonschema/test_main_jsonschema.py
  • tests/data/expected/main/jsonschema/typed_dict_closed_py310.py
  • tests/data/expected/main/jsonschema/typed_dict_extra_items.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2026-01-02T08:25:19.839Z
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2890
File: tests/data/expected/main/jsonschema/ref_nullable_with_constraint.py:14-15
Timestamp: 2026-01-02T08:25:19.839Z
Learning: The datamodel-code-generator currently generates RootModel subclasses with an explicit `root` field annotation (e.g., `class StringType(RootModel[str]): root: str`). This is existing behavior of the code generator and should not be flagged as an issue introduced by new changes.

Applied to files:

  • src/datamodel_code_generator/input_model.py
📚 Learning: 2025-12-25T09:22:22.481Z
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2799
File: src/datamodel_code_generator/model/pydantic/__init__.py:43-43
Timestamp: 2025-12-25T09:22:22.481Z
Learning: In datamodel-code-generator project, defensive `# noqa: PLC0415` directives should be kept on lazy imports (imports inside functions/methods) even when Ruff reports them as unused via RUF100, to prepare for potential future Ruff configuration changes that might enable the import-outside-top-level rule.

Applied to files:

  • src/datamodel_code_generator/_types/parser_config_dicts.py
  • src/datamodel_code_generator/_types/generate_config_dict.py
🧬 Code graph analysis (2)
src/datamodel_code_generator/_types/generate_config_dict.py (1)
src/datamodel_code_generator/model/typed_dict.py (1)
  • TypedDict (50-181)
tests/data/expected/main/input_model/config_class.py (2)
src/datamodel_code_generator/model/typed_dict.py (1)
  • TypedDict (50-181)
src/datamodel_code_generator/enums.py (1)
  • DataclassArguments (15-27)
🪛 Ruff (0.14.10)
src/datamodel_code_generator/_types/parser_config_dicts.py

8-8: Redefinition of unused TypedDict from line 6

(F811)

src/datamodel_code_generator/_types/generate_config_dict.py

8-8: Redefinition of unused TypedDict from line 6

(F811)

tests/data/expected/main/input_model/config_class.py

13-13: Redefinition of unused TypedDict from line 9

(F811)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: py312-pydantic1 on Ubuntu
  • GitHub Check: py312-isort6 on Ubuntu
  • GitHub Check: py312-black23 on Ubuntu
  • GitHub Check: 3.11 on Ubuntu
  • GitHub Check: py312-isort5 on Ubuntu
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.14 on Ubuntu
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.13 on Windows
  • GitHub Check: 3.14 on Windows
  • GitHub Check: 3.11 on Windows
  • GitHub Check: benchmarks
  • GitHub Check: Analyze (python)
🔇 Additional comments (15)
src/datamodel_code_generator/input_model.py (2)

656-657: LGTM: Base class marker for PEP 728 compatibility.

The x-is-base-class marker correctly prevents closed=True from being applied to TypedDict base classes, which is necessary because PEP 728 doesn't allow child TypedDicts to extend closed parents.


806-811: LGTM: Correct prioritization of base-class schemas.

The merging logic properly prioritizes schemas marked with x-is-base-class, ensuring that base-class variants are retained over non-base-class variants. This supports correct TypedDict inheritance resolution.

src/datamodel_code_generator/_types/parse_config_dict.py (2)

6-8: LGTM: Clean imports for closed TypedDict.

The imports are correctly structured, using typing_extensions.TypedDict to support the closed=True parameter from PEP 728.


16-16: LGTM: Properly closed TypedDict.

The closed=True parameter correctly restricts the TypedDict to only the declared fields, implementing PEP 728 semantics.

src/datamodel_code_generator/_types/generate_config_dict.py (1)

41-41: LGTM: Properly closed TypedDict.

The closed=True parameter correctly implements PEP 728 semantics for GenerateConfigDict.

tests/data/expected/main/input_model/config_class.py (1)

45-45: LGTM: Closed TypedDicts in test expectations.

Both DataclassArguments and GenerateConfig correctly use closed=True, demonstrating proper PEP 728 implementation in the expected test output.

Also applies to: 117-117

src/datamodel_code_generator/_types/parser_config_dicts.py (1)

163-163: LGTM: Properly closed TypedDicts with new field.

Both GraphQLParserConfigDict and OpenAPIParserConfigDict correctly use closed=True to implement PEP 728 semantics. The addition of openapi_include_paths field to OpenAPIParserConfigDict is appropriate.

Also applies to: 173-177

tests/model/test_typed_dict.py (2)

5-9: LGTM!

The new imports are appropriate and used throughout the test file for the PEP 728 TypedDict features.


40-48: LGTM!

The helper function is well-designed and provides a clean way to create TypedDict instances for testing with optional extra_template_data.

src/datamodel_code_generator/model/typed_dict.py (6)

19-19: LGTM!

The backport import addition is necessary for PEP 728 support on Python < 3.13.


92-93: LGTM!

The placement of _setup_closed_extra_items() after super().__init__() is correct, ensuring extra_template_data is available.


95-123: LGTM - Correct implementation of PEP 728 semantics.

The method correctly implements the mapping:

  • additionalProperties: falseclosed=True
  • additionalProperties: { type: X }extra_items=X

The exclusion of base classes (lines 113, 115) is correct per PEP 728, as child TypedDicts cannot extend a closed parent. The use of elif ensures closed and extra_items are mutually exclusive, which aligns with JSON Schema semantics where additionalProperties is either false or a schema object.


124-132: LGTM!

Both properties provide clean boolean checks for TypedDict kwargs presence and backport usage.


134-145: LGTM!

The base_class override correctly formats PEP 728 kwargs into the base class string (e.g., TypedDict, closed=True).


147-157: LGTM - Correct backport import logic.

The condition on line 152 correctly requires both _use_typeddict_backport and _has_typed_dict_kwargs to be true, ensuring the backport is only used for PEP 728 features on Python < 3.13.

The import replacement (lines 154-155) correctly filters out IMPORT_TYPED_DICT and adds IMPORT_TYPED_DICT_BACKPORT.

Comment thread tests/model/test_typed_dict.py Outdated
@koxudaxi koxudaxi force-pushed the feature/typeddict-closed-extra-items branch from 3c39200 to afb4178 Compare January 5, 2026 16:01
@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (58e73ed) to head (a479686).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #2922   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           93        92    -1     
  Lines        16913     16957   +44     
  Branches      1966      1976   +10     
=========================================
+ Hits         16913     16957   +44     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@koxudaxi koxudaxi merged commit 5acb178 into main Jan 5, 2026
39 checks passed
@koxudaxi koxudaxi deleted the feature/typeddict-closed-extra-items branch January 5, 2026 16:29
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 5, 2026

🎉 Released in 0.52.2

This PR is now available in the latest release. See the release notes for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add extra_items and closed support to TypedDict

1 participant