Skip to content

fix: wrap RootModel primitive defaults with default_factory#2714

Merged
koxudaxi merged 1 commit intomainfrom
fix/issue-2027-rootmodel-primitive-default
Dec 20, 2025
Merged

fix: wrap RootModel primitive defaults with default_factory#2714
koxudaxi merged 1 commit intomainfrom
fix/issue-2027-rootmodel-primitive-default

Conversation

@koxudaxi
Copy link
Copy Markdown
Owner

@koxudaxi koxudaxi commented Dec 20, 2025

Fixes: #2027

Summary by CodeRabbit

  • Bug Fixes

    • Simplified generated default factories for Pydantic models and RootModel fields by removing unnecessary intermediate validation calls.
  • New Features

    • Support for RootModel with primitive default values in OpenAPI code generation.
  • Tests

    • Added test coverage for OpenAPI RootModel defaults with primitive values.

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 20, 2025

Walkthrough

Updated default handling for Pydantic models: added a centralized WrappedDefault type, detect Pydantic RootModel references, and produce simpler default factories (direct constructors) with refined control flow for unions, dict/list defaults, and wrapped defaults.

Changes

Cohort / File(s) Summary
Core logic
src/datamodel_code_generator/model/pydantic/base_model.py
Import WrappedDefault, detect RootModel via BASE_CLASS == "pydantic.RootModel", handle WrappedDefault returns, adjust union and dict/list default handling, and emit direct-constructor default_factory lambdas for RootModel vs parse calls for other references.
Internal types
src/datamodel_code_generator/model/_types.py
Added WrappedDefault dataclass with custom __repr__ to pair a value with its type constructor representation.
Public re-exports
src/datamodel_code_generator/model/base.py
Removed local WrappedDefault definition and re-exported WrappedDefault from model._types via __all__.
JSON Schema test expectations
tests/data/expected/main/jsonschema/root_model_default_value.py, .../root_model_default_value_branches.py, .../root_model_default_value_no_annotated.py, .../root_model_default_value_non_root.py
Simplified default_factory expressions by removing model_validate wrapping and using direct constructors (e.g., CountType(10), NameType('default_name')).
OpenAPI test expectations
tests/data/expected/main/openapi/pydantic_v2_default_object/Nested.py, tests/data/expected/main/openapi/referenced_default.py, tests/data/expected/main/openapi/referenced_default_use_annotated.py, tests/data/expected/main/openapi/root_model_default_primitive.py
Replaced Type.model_validate(...) patterns with direct Type(...) constructors in default_factory. Added root_model_default_primitive.py defining Timeout (RootModel) and CrawlConfiguration with `timeout: Timeout
Test data & tests
tests/data/openapi/root_model_default_primitive.yaml, tests/main/openapi/test_main_openapi.py
Added OpenAPI schema for primitive RootModel default and a new test test_main_openapi_root_model_default_primitive (appears duplicated in insertion).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay special attention to src/datamodel_code_generator/model/pydantic/base_model.py for conditional branches around WrappedDefault, RootModel detection, unions, and dict/list handling.
  • Review WrappedDefault implementation and the re-export in model/base.py for API surface consistency.
  • Confirm the duplicated test insertion in tests/main/openapi/test_main_openapi.py is intentional.

Suggested labels

safe-to-fix

Suggested reviewers

  • ilovelinux

Poem

🐰 I nibble through defaults with delight,
Wrapped values now find the right light.
RootModels built with a single hop,
No extra validation stop.
Hooray — constructors and carrots, on top!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: wrap RootModel primitive defaults with default_factory' accurately describes the main change: wrapping primitive defaults with default_factory for RootModel types to resolve type mismatches.
Linked Issues check ✅ Passed The PR successfully addresses issue #2027 by ensuring primitive defaults are wrapped in RootModel constructors via default_factory, generating correct type-compatible defaults instead of mismatched primitive values.
Out of Scope Changes check ✅ Passed All changes directly support the core objective of fixing RootModel primitive defaults: the new WrappedDefault type, base_model.py enhancements, test files, and new test functions are all necessary to implement and validate the fix.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/issue-2027-rootmodel-primitive-default

📜 Recent 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 57215f7 and 525cdfd.

📒 Files selected for processing (13)
  • src/datamodel_code_generator/model/_types.py (1 hunks)
  • src/datamodel_code_generator/model/base.py (2 hunks)
  • src/datamodel_code_generator/model/pydantic/base_model.py (3 hunks)
  • tests/data/expected/main/jsonschema/root_model_default_value.py (1 hunks)
  • tests/data/expected/main/jsonschema/root_model_default_value_branches.py (1 hunks)
  • tests/data/expected/main/jsonschema/root_model_default_value_no_annotated.py (1 hunks)
  • tests/data/expected/main/jsonschema/root_model_default_value_non_root.py (1 hunks)
  • tests/data/expected/main/openapi/pydantic_v2_default_object/Nested.py (1 hunks)
  • tests/data/expected/main/openapi/referenced_default.py (1 hunks)
  • tests/data/expected/main/openapi/referenced_default_use_annotated.py (1 hunks)
  • tests/data/expected/main/openapi/root_model_default_primitive.py (1 hunks)
  • tests/data/openapi/root_model_default_primitive.yaml (1 hunks)
  • tests/main/openapi/test_main_openapi.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • tests/data/expected/main/jsonschema/root_model_default_value.py
  • tests/data/expected/main/openapi/referenced_default_use_annotated.py
  • tests/data/expected/main/openapi/root_model_default_primitive.py
  • tests/data/expected/main/jsonschema/root_model_default_value_non_root.py
  • tests/data/expected/main/jsonschema/root_model_default_value_branches.py
  • tests/data/expected/main/openapi/pydantic_v2_default_object/Nested.py
🧰 Additional context used
🧬 Code graph analysis (4)
tests/data/expected/main/openapi/referenced_default.py (2)
tests/data/expected/main/openapi/referenced_default_use_annotated.py (1)
  • ModelSettingB (12-13)
tests/data/expected/parser/openapi/openapi_parser_parse_modular/bar.py (1)
  • Field (6-7)
src/datamodel_code_generator/model/pydantic/base_model.py (4)
src/datamodel_code_generator/model/_types.py (1)
  • WrappedDefault (10-18)
src/datamodel_code_generator/reference.py (1)
  • reference (76-78)
src/datamodel_code_generator/types.py (1)
  • is_union (617-619)
src/datamodel_code_generator/model/base.py (2)
  • class_name (635-637)
  • class_name (640-644)
tests/data/expected/main/jsonschema/root_model_default_value_no_annotated.py (1)
tests/data/expected/main/jsonschema/root_model_default_value.py (2)
  • CountType (18-19)
  • NameType (22-23)
src/datamodel_code_generator/model/base.py (1)
src/datamodel_code_generator/model/_types.py (1)
  • WrappedDefault (10-18)
🪛 Checkov (3.2.334)
tests/data/openapi/root_model_default_primitive.yaml

[high] 1-20: Ensure that the global security field has rules defined

(CKV_OPENAPI_4)

⏰ 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). (12)
  • GitHub Check: benchmarks
  • GitHub Check: py312-pydantic1 on Ubuntu
  • GitHub Check: py312-isort6 on Ubuntu
  • GitHub Check: py312-black22 on Ubuntu
  • GitHub Check: 3.10 on Ubuntu
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.14 on Ubuntu
  • GitHub Check: 3.14 on Windows
  • GitHub Check: 3.13 on Windows
  • GitHub Check: Analyze (python)
🔇 Additional comments (8)
tests/data/expected/main/openapi/referenced_default.py (1)

16-16: LGTM: Simplified default factory for RootModel.

The change from ModelSettingB.model_validate(5) to ModelSettingB(5) is correct. RootModel types accept the wrapped primitive directly in their constructor, making this simpler and more idiomatic.

tests/data/openapi/root_model_default_primitive.yaml (1)

1-18: LGTM: Well-structured test case for RootModel default handling.

This OpenAPI spec correctly captures the issue scenario where a primitive default (3600) needs to be wrapped for a RootModel-based union type (Timeout | None).

Note: The Checkov security hint about missing global security rules is a false positive for test schemas and can be safely ignored.

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

9-18: LGTM: Clean abstraction for wrapped defaults.

The WrappedDefault dataclass is well-designed:

  • Simple data container with clear purpose
  • Custom __repr__ produces the expected constructor syntax (e.g., CountType(10))
  • Enables centralized handling of default value wrapping
tests/main/openapi/test_main_openapi.py (1)

3245-3254: LGTM: Comprehensive test for RootModel primitive default handling.

The test correctly verifies the fix for issue #2027, ensuring that RootModel types with primitive defaults in union contexts generate proper default factories.

src/datamodel_code_generator/model/base.py (1)

28-42: LGTM: Proper centralization of WrappedDefault type.

Moving WrappedDefault to _types.py and re-exporting it here maintains a clean public API while centralizing the type definition.

src/datamodel_code_generator/model/pydantic/base_model.py (2)

126-127: LGTM: Clean handling of WrappedDefault instances.

Checking for WrappedDefault instances and using their __repr__ in the lambda is the right approach for centralized default wrapping.


147-158: LGTM: Correct RootModel detection and default factory generation.

The logic correctly handles RootModel types:

  1. RootModel detection (line 148): Checking BASE_CLASS == "pydantic.RootModel" is appropriate given the class hierarchy
  2. Union handling (lines 149-154): Properly skips non-RootModel types with primitive defaults, allowing RootModel types to proceed
  3. Default factory generation (lines 156-158): Generates direct constructor calls for RootModel (Timeout(3600)) instead of parse method calls, which is correct since RootModel accepts the primitive value directly

This fix resolves the type mismatch issue described in #2027.

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

27-28: LGTM: Consistent simplified default factories for RootModel types.

The changes from model_validate(value) to direct constructor calls are correct for RootModel types (CountType(10), NameType('default_name')). This aligns with the fix implemented in the base_model.py logic.


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.

Comment thread src/datamodel_code_generator/model/pydantic/base_model.py Fixed
@codecov
Copy link
Copy Markdown

codecov Bot commented Dec 20, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.34%. Comparing base (e3a2cf5) to head (525cdfd).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2714   +/-   ##
=======================================
  Coverage   99.34%   99.34%           
=======================================
  Files          81       82    +1     
  Lines       11534    11555   +21     
  Branches     1387     1391    +4     
=======================================
+ Hits        11458    11479   +21     
  Misses         45       45           
  Partials       31       31           
Flag Coverage Δ
unittests 99.34% <100.00%> (+<0.01%) ⬆️

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.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Dec 20, 2025

CodSpeed Performance Report

Merging #2714 will not alter performance

Comparing fix/issue-2027-rootmodel-primitive-default (525cdfd) with main (92a32c0)

Summary

✅ 53 untouched
⏩ 10 skipped1

Footnotes

  1. 10 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.

@koxudaxi koxudaxi force-pushed the fix/issue-2027-rootmodel-primitive-default branch from 57215f7 to 1ffdcf1 Compare December 20, 2025 14:51
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.

Incompatible Default Value When Field is Generated as RootModel

2 participants