Add ReadOnly support for TypedDict with --use-frozen-field#2813
Add ReadOnly support for TypedDict with --use-frozen-field#2813
Conversation
📝 WalkthroughWalkthroughThis PR adds support for Python 3.13's Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
|
📚 Docs Preview: https://pr-2813.datamodel-code-generator.pages.dev |
CodSpeed Performance ReportMerging #2813 will not alter performanceComparing Summary
Benchmarks breakdown
Footnotes
|
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
tests/main/jsonschema/test_main_jsonschema.py (1)
5686-5725: TypedDict frozen-field test is well‑structured; consider explicit 3.12 coverageThe parametrized
test_main_use_frozen_field_typed_dictcleanly exercises the three code paths (nativetyping.ReadOnly, backportedtyping_extensions.ReadOnly, and the 3.10NotRequired+ReadOnlycombination), and the docstring accurately reflects the intended behavior. The use ofBLACK_PY313_SKIPon the 3.13 case is a good guard against formatter mismatches.One possible enhancement: since Python 3.11–3.12 share the backport path, adding a
"3.12"row (reusing theuse_frozen_field_typed_dict_py311.pyexpected file if output is identical) would give direct coverage for that intermediate target as well. Not required for correctness, but it would make the version matrix more explicit.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
src/datamodel_code_generator/format.pysrc/datamodel_code_generator/model/__init__.pysrc/datamodel_code_generator/model/imports.pysrc/datamodel_code_generator/model/typed_dict.pysrc/datamodel_code_generator/types.pytests/data/expected/main/jsonschema/use_frozen_field_typed_dict.pytests/data/expected/main/jsonschema/use_frozen_field_typed_dict_py310.pytests/data/expected/main/jsonschema/use_frozen_field_typed_dict_py311.pytests/main/jsonschema/test_main_jsonschema.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-25T09:22:57.664Z
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2799
File: src/datamodel_code_generator/util.py:49-66
Timestamp: 2025-12-25T09:22:57.664Z
Learning: In datamodel-code-generator, the is_pydantic_v2() and is_pydantic_v2_11() functions in src/datamodel_code_generator/util.py intentionally use global variable caching (_is_v2, _is_v2_11) on top of lru_cache for performance optimization. This dual-layer caching eliminates function call overhead and cache lookup overhead for frequently-called version checks. The PLW0603 linter warnings should be suppressed with # noqa: PLW0603 as this is a deliberate design choice.
Applied to files:
src/datamodel_code_generator/format.py
🧬 Code graph analysis (6)
src/datamodel_code_generator/model/imports.py (1)
src/datamodel_code_generator/imports.py (2)
Import(20-38)from_full_path(35-38)
tests/data/expected/main/jsonschema/use_frozen_field_typed_dict_py311.py (4)
src/datamodel_code_generator/model/typed_dict.py (1)
TypedDict(49-114)tests/data/expected/main/jsonschema/use_frozen_field_typed_dict.py (1)
User(10-14)tests/data/expected/main/jsonschema/use_frozen_field_typed_dict_py310.py (1)
User(12-16)src/datamodel_code_generator/model/base.py (1)
name(826-828)
src/datamodel_code_generator/model/__init__.py (2)
src/datamodel_code_generator/format.py (2)
has_typed_dict_read_only(113-115)has_typed_dict_non_required(108-110)src/datamodel_code_generator/model/typed_dict.py (4)
DataModelField(117-170)DataModelFieldReadOnlyBackport(173-179)DataModelFieldBackport(182-189)TypedDict(49-114)
tests/data/expected/main/jsonschema/use_frozen_field_typed_dict.py (4)
src/datamodel_code_generator/model/typed_dict.py (1)
TypedDict(49-114)tests/data/expected/main/jsonschema/use_frozen_field_typed_dict_py310.py (1)
User(12-16)tests/data/expected/main/jsonschema/use_frozen_field_typed_dict_py311.py (1)
User(12-16)src/datamodel_code_generator/model/base.py (1)
name(826-828)
tests/data/expected/main/jsonschema/use_frozen_field_typed_dict_py310.py (3)
src/datamodel_code_generator/model/typed_dict.py (1)
TypedDict(49-114)tests/data/expected/main/jsonschema/use_frozen_field_typed_dict.py (1)
User(10-14)tests/data/expected/main/jsonschema/use_frozen_field_typed_dict_py311.py (1)
User(12-16)
src/datamodel_code_generator/model/typed_dict.py (2)
src/datamodel_code_generator/types.py (1)
type_hint(567-656)src/datamodel_code_generator/model/base.py (2)
type_hint(276-294)fall_back_to_nullable(440-442)
🔇 Additional comments (10)
tests/data/expected/main/jsonschema/use_frozen_field_typed_dict_py311.py (1)
1-16: LGTM! Test fixture correctly represents Python 3.11 target version.The import strategy is correct:
NotRequiredfromtyping(available natively in Python 3.11+) andReadOnlyfromtyping_extensions(backport for pre-3.13 versions). The TypedDict structure properly demonstrates ReadOnly field wrapping.tests/data/expected/main/jsonschema/use_frozen_field_typed_dict_py310.py (1)
1-16: LGTM! Test fixture correctly represents Python 3.10 target version.The imports are accurate for Python 3.10:
TypedDictfromtyping(available since 3.8) and bothNotRequiredandReadOnlyfromtyping_extensions(as backports). The structure is consistent with the other version-specific fixtures.src/datamodel_code_generator/model/__init__.py (1)
127-136: LGTM! Version-aware field model selection is correct.The three-way conditional properly handles the capability matrix:
- Python 3.13+: native
ReadOnlyandNotRequired- Python 3.11-3.12: native
NotRequired, backportReadOnly- Python 3.10: backport both
The refactored approach with an explicit local variable improves readability compared to inline conditionals.
src/datamodel_code_generator/model/imports.py (1)
17-18: LGTM! New import constants follow established patterns.The
IMPORT_READ_ONLYandIMPORT_READ_ONLY_BACKPORTconstants mirror the structure ofIMPORT_NOT_REQUIRED/IMPORT_NOT_REQUIRED_BACKPORTabove them, maintaining consistency in the codebase.src/datamodel_code_generator/format.py (2)
89-91: LGTM! Version check follows established pattern.The
_is_py_313_or_laterimplementation is consistent with other version checks in the class and correctly usescached_propertyfor performance optimization.
112-115: LGTM! Property correctly exposes ReadOnly capability.The
has_typed_dict_read_onlyproperty appropriately delegates to_is_py_313_or_laterand the docstring correctly references PEP 705 (TypedDict ReadOnly).src/datamodel_code_generator/types.py (1)
89-90: LGTM! Constants follow established pattern.The
READ_ONLYandREAD_ONLY_PREFIXconstants mirror theNOT_REQUIRED/NOT_REQUIRED_PREFIXpattern above, maintaining consistency. These constants support the construction ofReadOnly[...]type hint wrappers.tests/data/expected/main/jsonschema/use_frozen_field_typed_dict.py (1)
1-14: LGTM! Test fixture correctly represents Python 3.13+ target version.The imports are accurate for Python 3.13+: all three items (
NotRequired,ReadOnly,TypedDict) now come fromtypingas Python 3.13 introduces nativeReadOnlysupport (PEP 705). This fixture completes the version matrix along with the py310 and py311 variants.tests/main/jsonschema/test_main_jsonschema.py (1)
28-40: ImportingBLACK_PY313_SKIPlooks consistent with existing skip markersUsing a shared skip marker for Python 3.13/Black-specific behavior aligns with the other BLACK-related fixtures (e.g.,
LEGACY_BLACK_SKIP,MSGSPEC_LEGACY_BLACK_SKIP) and keeps version/formatter gating in one place. No changes needed here.src/datamodel_code_generator/model/typed_dict.py (1)
13-21: ReadOnly support for TypedDict fields is wired correctly across Python versionsThe changes in
DataModelFieldand its backport variants look consistent and correct:
- The
_read_onlyflag is scoped toTypedDictparents and gated on bothuse_frozen_fieldandread_only, so existing schemas withoutreadOnlyor without the CLI flag remain unaffected.- The type hint wrapping order (
ReadOnly[...]inner, thenNotRequired[...]outer) is sensible: required read-only fields becomeReadOnly[T], and non‑required read-only fields becomeNotRequired[ReadOnly[T]].fall_back_to_nullablestill preventsOptional[...]generation for non‑required TypedDict fields, so optionality is expressed viaNotRequiredrather thanOptional, even in the read‑only case.- Imports are only added when needed:
- Base
DataModelField(3.13+) usesIMPORT_NOT_REQUIREDandIMPORT_READ_ONLYfromtyping.DataModelFieldReadOnlyBackport(3.11–3.12) reusestyping.NotRequiredbut switchesDEFAULT_READ_ONLY_IMPORTStoIMPORT_READ_ONLY_BACKPORT(typing_extensions.ReadOnly).DataModelFieldBackport(3.10) correctly backports bothNotRequiredandReadOnlyfromtyping_extensions.Overall, this cleanly connects the Python-version–aware field classes to the new
ReadOnlycapability without disturbing existing TypedDict behavior.Also applies to: 118-171, 173-189
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2813 +/- ##
=======================================
Coverage 99.51% 99.51%
=======================================
Files 89 89
Lines 13855 13886 +31
Branches 1634 1637 +3
=======================================
+ Hits 13788 13819 +31
Misses 36 36
Partials 31 31
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Breaking Change AnalysisResult: No breaking changes detected Reasoning: This PR adds ReadOnly support for TypedDict when using --use-frozen-field, extending an existing feature to a new output model type. Previously, --use-frozen-field had no effect on TypedDict output - fields were generated without any ReadOnly wrapper. Now, when both --use-frozen-field and --output-model-type typing.TypedDict are used, readOnly schema fields are wrapped with ReadOnly[]. This is purely additive new functionality that requires explicit opt-in via the existing --use-frozen-field flag. No existing behavior is changed, no templates were modified, and no CLI options were altered. Users who don't use --use-frozen-field with TypedDict see no change. This analysis was performed by Claude Code Action |
|
Thank you so much for implementing it so swiftly. |
Fixes: #2808
Summary by CodeRabbit
New Features
Tests
✏️ Tip: You can customize this high-level summary in your review settings.