Skip to content

Conversation

@koxudaxi
Copy link
Owner

@koxudaxi koxudaxi commented Jan 10, 2026

Fixes: #2947

Summary by CodeRabbit

  • Bug Fixes

    • Required fields that have defaults now preserve factory-based defaults and no longer emit unnecessary ellipses.
    • List-based root defaults are now initialized via factories to ensure correct runtime construction.
  • New Examples

    • Added a generated Pydantic v2 example demonstrating enums, models, aliases, and factory defaults.
  • Tests

    • Tests parameterized to validate both Pydantic v1 and v2 output formats.

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

@coderabbitai
Copy link

coderabbitai bot commented Jan 10, 2026

📝 Walkthrough

Walkthrough

Tightened DataModelField stringification: default_factory is preserved when a required field has an explicit default, and the ellipsis ("...") for required fields is only emitted when the field is required and no default_factory is present.

Changes

Cohort / File(s) Summary
Field default_factory logic
src/datamodel_code_generator/model/pydantic/base_model.py
Adjusted DataModelField.str: compute default_factory only null when a field is required and has no default; emit ... for required fields only when default_factory is absent.
Pydantic v1 expected output
tests/data/expected/main/jsonschema/has_default_value.py
Replaced literal __root__ defaults with Field(default_factory=...) lambdas that construct elements via parse_obj.
Pydantic v2 expected output (new)
tests/data/expected/main/jsonschema/has_default_value_pydantic_v2.py
Added generated Pydantic v2 models demonstrating default_factory use for __root__ lists and other defaults/aliases.
Test parametrization
tests/main/jsonschema/test_main_jsonschema.py
Parameterized test to run against both pydantic v1 and v2 outputs; added parametrize decorator, updated signature, and pass --output-model-type via test parameters.

Sequence Diagram(s)

(Skipped — changes are focused on generator logic and test fixtures; no multi-component runtime flow to visualize.)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

safe-to-fix

Suggested reviewers

  • ilovelinux

Poem

🐰 I hopped through fields and munched a bug,
Tightened the logic with a tiny tug.
Defaults now wake with proper delight,
Lists and models tucked in tight.
🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Fix required list fields ignoring empty default values' accurately summarizes the main change: resolving how required list fields with empty defaults are generated, particularly fixing the over-eager default_factory generation issue reported in issue #2947.
Linked Issues check ✅ Passed The code changes fully address the requirements from issue #2947 [#2947]. The modifications to DataModelField.str tighten conditions for nullifying default_factory and injecting ellipsis, allowing default_factory to be produced only when truly needed and preventing incorrect model_validate([]) generation for required list fields with empty defaults.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing issue #2947. The core logic fix in base_model.py addresses the root cause, and test updates validate the fix for both Pydantic v1 and v2 models. No unrelated or out-of-scope modifications are present.
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.

✨ Finishing touches
  • 📝 Generate docstrings

📜 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 ad58a89 and 85fa705.

⛔ Files ignored due to path filters (1)
  • tests/data/jsonschema/has_default_value.json is excluded by !tests/data/**/*.json and included by none
📒 Files selected for processing (2)
  • tests/data/expected/main/jsonschema/has_default_value_pydantic_v2.py
  • tests/main/jsonschema/test_main_jsonschema.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/main/jsonschema/test_main_jsonschema.py
🧰 Additional context used
🧬 Code graph analysis (1)
tests/data/expected/main/jsonschema/has_default_value_pydantic_v2.py (1)
src/datamodel_code_generator/util.py (1)
  • model_validate (302-306)
⏰ 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). (14)
  • GitHub Check: 3.13 on macOS
  • GitHub Check: py312-isort5 on Ubuntu
  • GitHub Check: 3.14 on Windows
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.10 on macOS
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: 3.11 on macOS
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.13 on Windows
  • GitHub Check: 3.14 on macOS
  • GitHub Check: 3.12 on macOS
  • GitHub Check: 3.12 on Windows
  • GitHub Check: Analyze (python)
  • GitHub Check: benchmarks
🔇 Additional comments (6)
tests/data/expected/main/jsonschema/has_default_value_pydantic_v2.py (6)

1-9: LGTM!

The file header and imports are appropriate for a Pydantic v2 test expectation file. RootModel is correctly imported for v2-style root models.


12-16: LGTM!

Standard enum definition with appropriate member values.


19-24: LGTM!

ID and Pet classes are correctly defined with appropriate type annotations and defaults for Pydantic v2.


27-30: LGTM!

The default_factory pattern using model_validate is appropriate here since the default list contains actual values (['abc', 'efg']). This correctly constructs ID instances from the raw string values. This is distinct from the empty-list case that the PR fixes.


33-38: LGTM!

The default_factory with model_validate correctly converts the default dictionaries to Pet instances. This is the appropriate pattern for non-empty default lists containing nested model data.


41-48: LGTM!

The Person model is correctly defined:

  • default_factory=lambda: ID('abc') properly creates an ID instance.
  • String defaults for TeamType fields (lines 45-46) are valid since Pydantic v2 coerces them to the appropriate enum members during validation.
  • Aliased fields with None defaults correctly handle optional nested models.

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
Contributor

github-actions bot commented Jan 10, 2026

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

@codspeed-hq
Copy link

codspeed-hq bot commented Jan 10, 2026

CodSpeed Performance Report

Merging this PR will not alter performance

Comparing fix/required-list-default (85fa705) with main (4cbf3bf)

⚠️ 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 benchmarks
⏩ 98 skipped benchmarks1

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

codecov bot commented Jan 10, 2026

Codecov Report

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

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #2958   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           94        94           
  Lines        17738     17739    +1     
  Branches      2038      2038           
=========================================
+ Hits         17738     17739    +1     
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/required-list-default branch from a68f28b to ad58a89 Compare January 12, 2026 14:48
Copy link

@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/main/jsonschema/test_main_jsonschema.py:
- Around line 3257-3273: The parametrized case for "pydantic_v2.BaseModel" in
test_main_jsonschema_has_default_value is not guarded and will run in Pydantic
v1 CI; update the param list to replace the plain tuple for the v2 case with
pytest.param("pydantic_v2.BaseModel", "has_default_value_pydantic_v2.py",
marks=PYDANTIC_V2_SKIP) (you can also add ids for clarity), keeping the
pydantic.BaseModel entry unchanged and leaving the test function
test_main_jsonschema_has_default_value and run_main_and_assert usage as-is.
🧹 Nitpick comments (1)
tests/data/expected/main/jsonschema/has_default_value_pydantic_v2.py (1)

45-46: Consider generating enum member references instead of string literals.

The enum defaults use string literals ('Department') rather than enum member references (TeamType.Department). While Pydantic coerces these at runtime, static type checkers may flag this as a type mismatch.

This appears to be existing generator behavior and is out of scope for this PR, but could be addressed in a follow-up.

📜 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 ad6725a and ad58a89.

⛔ Files ignored due to path filters (1)
  • tests/data/jsonschema/has_default_value.json is excluded by !tests/data/**/*.json and included by none
📒 Files selected for processing (4)
  • src/datamodel_code_generator/model/pydantic/base_model.py
  • tests/data/expected/main/jsonschema/has_default_value.py
  • tests/data/expected/main/jsonschema/has_default_value_pydantic_v2.py
  • tests/main/jsonschema/test_main_jsonschema.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/data/expected/main/jsonschema/has_default_value.py
🧰 Additional context used
🧠 Learnings (1)
📚 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/model/pydantic/base_model.py
🧬 Code graph analysis (3)
src/datamodel_code_generator/model/pydantic/base_model.py (1)
src/datamodel_code_generator/parser/jsonschema.py (1)
  • has_default (430-432)
tests/main/jsonschema/test_main_jsonschema.py (1)
tests/main/conftest.py (2)
  • output_file (99-101)
  • run_main_and_assert (245-409)
tests/data/expected/main/jsonschema/has_default_value_pydantic_v2.py (2)
src/datamodel_code_generator/model/enum.py (1)
  • Enum (39-121)
src/datamodel_code_generator/util.py (1)
  • model_validate (302-306)
⏰ 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). (16)
  • GitHub Check: py312-pydantic1 on Ubuntu
  • GitHub Check: py312-black22 on Ubuntu
  • GitHub Check: 3.10 on macOS
  • GitHub Check: 3.14 on Windows
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.14 on macOS
  • GitHub Check: 3.10 on Ubuntu
  • GitHub Check: 3.11 on Ubuntu
  • GitHub Check: 3.11 on macOS
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.12 on macOS
  • GitHub Check: 3.13 on Windows
  • GitHub Check: 3.13 on macOS
  • GitHub Check: Analyze (python)
  • GitHub Check: benchmarks
🔇 Additional comments (3)
src/datamodel_code_generator/model/pydantic/base_model.py (2)

252-256: Correct handling of ellipsis for required fields with default_factory.

The additional and not default_factory condition ensures that Field(...) is only emitted when there's truly no default — preventing incorrect output like Field(..., default_factory=list) for fields that have computed default factories.


223-228: LGTM! Logic correctly preserves default_factory for required fields with explicit defaults.

The condition change from if self.required: to if self.required and not self.has_default: fixes the core issue — required fields with explicit defaults (e.g., empty lists []) will no longer have their default_factory nullified, allowing the computed factory from _get_default_as_pydantic_model() to be used. The has_default attribute is inherited from DataModelFieldBase and is properly accessible here.

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

27-38: These patterns correctly demonstrate the fix for empty list defaults.

The Family and FamilyPets models now properly use default_factory=lambda: [...] instead of the problematic default_factory=lambda: [Model.model_validate(v) for v in []] pattern. This aligns with the PR objective of generating simple empty-list defaults for optional list fields with [] specified.

@koxudaxi koxudaxi force-pushed the fix/required-list-default branch from ad58a89 to 85fa705 Compare January 12, 2026 15:00
@koxudaxi koxudaxi merged commit 88c7fe4 into main Jan 12, 2026
38 checks passed
@koxudaxi koxudaxi deleted the fix/required-list-default branch January 12, 2026 15:04
@github-actions
Copy link
Contributor

Breaking Change Analysis

Result: Breaking changes detected

Reasoning: This PR modifies the code generation logic to properly handle required fields that also have default values. Previously, the condition if self.required: would prevent default_factory from being set, but now it checks if self.required and not self.has_default:. This causes generated code to change from direct list assignments to Field(default_factory=...) for list-type defaults on required fields/root models. While this is a bug fix that produces more correct Python code (avoiding mutable default argument issues), it changes the structure of generated output, which affects users who regenerate their models and may need to update downstream code or tests.

Content for Release Notes

Code Generation Changes

  • Required fields with list defaults now use default_factory - Previously, required fields with list-type defaults (like __root__: list[ID] = ['abc', 'efg']) were generated with direct list assignments. Now they correctly use Field(default_factory=lambda: ...) which follows Python best practices for mutable defaults. This changes the structure of generated code for root models and similar patterns with list defaults. (Fix required list fields ignoring empty default values #2958)

    Before:

    class Family(BaseModel):
        __root__: list[ID] = ['abc', 'efg']

    After:

    class Family(BaseModel):
        __root__: list[ID] = Field(
            default_factory=lambda: [ID.parse_obj(v) for v in ['abc', 'efg']]
        )

This analysis was performed by Claude Code Action

@github-actions
Copy link
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.

Unexpected default_factory for list of object field with optional [] default results in validation failures during instantiation

2 participants