Skip to content

Fix array RootModel default value handling in parser#2963

Merged
koxudaxi merged 7 commits intomainfrom
fix/array-rootmodel-default-from-parser
Jan 12, 2026
Merged

Fix array RootModel default value handling in parser#2963
koxudaxi merged 7 commits intomainfrom
fix/array-rootmodel-default-from-parser

Conversation

@koxudaxi
Copy link
Copy Markdown
Owner

@koxudaxi koxudaxi commented Jan 12, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Improved default handling and nullable detection in generated models to avoid incorrect Optional types and ensure proper union root defaults.
  • Documentation

    • Updated CLI reference: processing-status union root now defaults to "COMPLETED" for clearer initialization.
  • Chores

    • CI coverage workflow adjusted to continue on error, display uncovered lines on failure, always upload HTML reports and coverage uploads, and fail explicitly if coverage checks fail.

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 12, 2026

Warning

Rate limit exceeded

@koxudaxi has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 20 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 b5b6cab and e47e393.

📒 Files selected for processing (1)
  • src/datamodel_code_generator/parser/jsonschema.py
📝 Walkthrough

Walkthrough

Refines default and nullable handling across root models, arrays, and synthetic enums; updates specific Field defaults from ellipsis to concrete values; and adjusts CI to always upload coverage artifacts and force Codecov upload while surfacing coverage failures.

Changes

Cohort / File(s) Summary
Workflow & Coverage
.github/workflows/test.yaml
Adds named coverage step with continue-on-error; prints uncovered lines on failure; uploads HTML coverage report unconditionally; forces Codecov upload to run always; adds final failure step when coverage check fails.
Field Default Propagation
docs/cli-reference/field-customization.md, tests/data/expected/main/jsonschema/titles_use_title_as_name.py
Changed ProcessingStatusUnionTitle.__root__ Field default from ... to 'COMPLETED', making the union root use a concrete default value.
Default & Nullability Logic (Pydantic model rendering)
src/datamodel_code_generator/model/pydantic/base_model.py
Altered DataModelField.__str__ logic: default_factory handling now guarded only by self.required, causing required fields to emit ... in Field arguments more aggressively.
Root Type & Array Field Parsing (JSON Schema parser)
src/datamodel_code_generator/parser/jsonschema.py
Tightened synthetic enum default copying; refined array field required/nullable determination and lint hints; introduced is_type_alias branch and computed default_value/has_default_override to control root field defaults and nullable propagation.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

breaking-change-analyzed

Poem

🐰 I hopped through schemas, neat and spry,

Replaced the ellipsis with a sky,
COMPLETED now sits proud and true,
Coverage uploads race on through,
A happy hop — code blooms anew!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main purpose of the changes, which focus on fixing default value handling for array RootModels in the parser logic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 12, 2026

📚 Docs Preview: https://pr-2963.datamodel-code-generator.pages.dev

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Jan 12, 2026

Merging this PR will not alter performance

⚠️ 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.

✅ 11 untouched benchmarks
⏩ 98 skipped benchmarks1


Comparing fix/array-rootmodel-default-from-parser (e47e393) with main (e717208)

Open in CodSpeed

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.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 12, 2026

Codecov Report

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

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #2963   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           94        94           
  Lines        17761     17778   +17     
  Branches      2039      2043    +4     
=========================================
+ Hits         17761     17778   +17     
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 force-pushed the fix/array-rootmodel-default-from-parser branch from 7f45fa3 to e9cd243 Compare January 12, 2026 16:24
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: 0

🧹 Nitpick comments (2)
.github/workflows/test.yaml (1)

122-150: Make the “uncovered lines” diagnostic step non-fatal to avoid masking logs.

If grep -v "100%" matches nothing, it exits non-zero and can mark the diagnostic step as failed (even though you already plan to fail at the final step). Consider ensuring this step never fails so the log is consistently printed and the following upload steps remain clean.

Proposed tweak
       - name: Show uncovered lines on failure
         if: steps.coverage.outcome == 'failure'
         run: |
           echo "::error::Coverage check failed. Lines not covered:"
-          .tox/coverage/bin/coverage report --show-missing --fail-under=0 | grep -v "100%"
+          .tox/coverage/bin/coverage report --show-missing --fail-under=0 | grep -v "100%" || true
src/datamodel_code_generator/parser/jsonschema.py (1)

3271-3334: parse_root_type: double-check default_factory-only cases don’t become default=None.

Similar to parse_array_fields, obj.has_default can be true because of default_factory in obj.extras. In multiple branches, default_value becomes obj.default (commonly None) when obj.has_default is true, which can unintentionally force a None default even when the intended behavior is “use default_factory”.

This is especially worth verifying because this method now also sets default= on data_model_root_type(...) (Line 3333-3334), so the model-level default could also be skewed.

If you confirm the renderer doesn’t strictly prioritize default_factory, consider the same “explicit default vs default_factory” guard here (compute has_explicit_default = "default" in get_fields_set(obj) and only use obj.default when that’s true; otherwise keep UNDEFINED and rely on default_factory in extras).

📜 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 e717208 and 57d1b8f.

⛔ Files ignored due to path filters (3)
  • docs/llms-full.txt is excluded by none and included by none
  • src/datamodel_code_generator/model/template/pydantic/BaseModel_root.jinja2 is excluded by none and included by none
  • src/datamodel_code_generator/model/template/pydantic_v2/RootModel.jinja2 is excluded by none and included by none
📒 Files selected for processing (5)
  • .github/workflows/test.yaml
  • docs/cli-reference/field-customization.md
  • src/datamodel_code_generator/model/pydantic/base_model.py
  • src/datamodel_code_generator/parser/jsonschema.py
  • tests/data/expected/main/jsonschema/titles_use_title_as_name.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
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:22.111Z
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.
📚 Learning: 2026-01-02T08:25:22.111Z
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:22.111Z
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:

  • tests/data/expected/main/jsonschema/titles_use_title_as_name.py
  • docs/cli-reference/field-customization.md
  • src/datamodel_code_generator/parser/jsonschema.py
  • src/datamodel_code_generator/model/pydantic/base_model.py
🧬 Code graph analysis (1)
src/datamodel_code_generator/parser/jsonschema.py (2)
src/datamodel_code_generator/model/base.py (1)
  • nullable (915-917)
src/datamodel_code_generator/types.py (1)
  • nullable (396-398)
🪛 Ruff (0.14.10)
src/datamodel_code_generator/parser/jsonschema.py

3059-3059: Unused noqa directive (non-enabled: PLR0912, PLR0915)

Remove unused noqa directive

(RUF100)

⏰ 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: 3.13 on Ubuntu
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.13 on macOS
  • GitHub Check: 3.10 on macOS
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.13 on Windows
  • GitHub Check: 3.12 on macOS
  • GitHub Check: 3.11 on macOS
  • GitHub Check: 3.14 on Windows
  • GitHub Check: Analyze (python)
  • GitHub Check: benchmarks
🔇 Additional comments (6)
docs/cli-reference/field-customization.md (1)

3912-3923: Doc example default now matches generated output (COMPLETED).

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

47-58: Expected output updated to concrete default for the union root.

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

888-909: Good: avoid accidentally marking synthetic enums as having a default.
Conditionally adding default only when original.has_default prevents downstream “required vs optional” logic from being skewed by synthetic objects.


3059-3152: No action needed. The # noqa: PLR0912, PLR0915 suppressions are appropriate for this method's complexity (22 conditional branches) and match the pattern used elsewhere in the codebase. The rendering layer already prevents outputting both default and default_factory together through explicit conditional checks (not field.has_default_factory_in_field in templates and data.pop("default_factory", None) in field rendering), so the suggested refactor is unnecessary.

Likely an incorrect or invalid review comment.

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

223-228: LGTM! Simplified condition ensures consistent behavior for required fields.

The change from checking self.required and not self.has_default to just self.required ensures that required fields always have default_factory = None, which aligns with Pydantic semantics where required fields should not have factory-generated defaults.

This is consistent with the downstream logic at lines 230-236 that already gates optional field processing with not self.required.


252-256: Consistent simplification of the required field condition.

The change from elif self.required and not default_factory to elif self.required is safe because the logic at line 223 guarantees that default_factory will always be None for required fields. When self.required is True, line 223 sets default_factory = None, and the only subsequent potential modification at lines 230-237 explicitly excludes required fields with its not self.required condition. Therefore, the additional and not default_factory check is redundant.

Test coverage for this edge case exists (e.g., test_base_model_nullable_required and test_base_model_strict_non_nullable_required), which verify that required fields with defaults are handled correctly.

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: 0

🧹 Nitpick comments (1)
src/datamodel_code_generator/parser/jsonschema.py (1)

3059-3059: Remove unused noqa directive.

Static analysis indicates that PLR0912 and PLR0915 are not enabled in your linter configuration, making this directive unnecessary.

Suggested fix
-    def parse_array_fields(  # noqa: PLR0912, PLR0915
+    def parse_array_fields(
📜 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 57d1b8f and b5b6cab.

📒 Files selected for processing (2)
  • .github/workflows/test.yaml
  • src/datamodel_code_generator/parser/jsonschema.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/test.yaml
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
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:22.111Z
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.
📚 Learning: 2026-01-02T08:25:22.111Z
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:22.111Z
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/parser/jsonschema.py
🧬 Code graph analysis (1)
src/datamodel_code_generator/parser/jsonschema.py (2)
src/datamodel_code_generator/model/base.py (1)
  • nullable (915-917)
src/datamodel_code_generator/types.py (1)
  • nullable (396-398)
🪛 Ruff (0.14.10)
src/datamodel_code_generator/parser/jsonschema.py

3059-3059: Unused noqa directive (non-enabled: PLR0912, PLR0915)

Remove unused noqa directive

(RUF100)

⏰ 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: 3.12 on Windows
  • GitHub Check: py312-black24 on Ubuntu
  • GitHub Check: py312-pydantic1 on Ubuntu
  • GitHub Check: 3.10 on Windows
  • GitHub Check: py312-black22 on Ubuntu
  • GitHub Check: benchmarks
  • GitHub Check: 3.13 on Windows
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: 3.12 on Ubuntu
  • GitHub Check: 3.14 on Windows
  • GitHub Check: 3.11 on Windows
  • GitHub Check: Analyze (python)
🔇 Additional comments (4)
src/datamodel_code_generator/parser/jsonschema.py (4)

900-909: LGTM!

Good fix for synthetic enum default propagation. The conditional unpacking **({"default": original.default} if original.has_default else {}) ensures that the default keyword is only passed when the original schema explicitly defines one, preventing has_default from being incorrectly set to True on the synthetic object.


3070-3084: LGTM - Improved nullable/required logic for array fields.

The refined logic correctly handles the interplay between defaults and nullability:

  • required is now derived solely from has_default (excluding force_optional_for_required_fields path)
  • The explicit nullable = False when obj.has_default (line 3082) prevents unintended Optional wrapping for arrays with default values

This aligns well with the PR objective of fixing array RootModel default value handling.


3298-3324: LGTM - Consistent default propagation to field and root type.

The computed default_value and has_default_override are correctly applied to both:

  • The data_model_field_type (lines 3303, 3314)
  • The data_model_root_type (line 3323)

The conditional on line 3323 default=default_value if has_default_override else UNDEFINED ensures the root type's default is only set when the override logic determined one should exist.


3269-3289: Well-structured default/nullable handling for root types.

The is_type_alias check (line 3269) correctly differentiates between type aliases and root models with defaults. The four-branch logic cleanly handles:

  1. force_optional_for_required_fields - always optional
  2. nullable schemas - not required, explicitly nullable
  3. Schemas with defaults (non-alias) - not required, explicitly non-nullable
  4. Required schemas - uses UNDEFINED sentinel for missing defaults

The explicit nullable = False on line 3282 ensures root models with defaults aren't incorrectly wrapped in Optional. The IS_ALIAS attribute is a standard ClassVar defined on all model types (base.py:631, overridden in type_alias.py:24) and correctly distinguishes type aliases from root models.

@koxudaxi koxudaxi merged commit 838b2a0 into main Jan 12, 2026
38 checks passed
@koxudaxi koxudaxi deleted the fix/array-rootmodel-default-from-parser branch January 12, 2026 17:26
@github-actions
Copy link
Copy Markdown
Contributor

Breaking Change Analysis

Result: No breaking changes detected

Reasoning: This PR is a bug fix that corrects handling of default values for RootModel/union types. Previously, default values specified in JSON schemas for union root types were being ignored (generating Field(...) instead of Field('COMPLETED', ...)). The fix makes the generator correctly apply these defaults. While generated output changes, this is expected corrective behavior - the new output now correctly reflects schema author intent. The Jinja2 template changes simplify the condition from (field.required and not field.has_default) to just field.required because the required flag is now computed correctly upstream. Custom templates using the old condition will still work since the underlying field properties remain compatible.


This analysis was performed by Claude Code Action

@github-actions
Copy link
Copy Markdown
Contributor

🎉 Released in 0.53.0

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant