Skip to content

fix #3034 and #3035 by introducing ValidatedDefault#3040

Merged
koxudaxi merged 15 commits intokoxudaxi:mainfrom
keyz:keyz-fix-3034
Mar 10, 2026
Merged

fix #3034 and #3035 by introducing ValidatedDefault#3040
koxudaxi merged 15 commits intokoxudaxi:mainfrom
keyz:keyz-fix-3034

Conversation

@keyz
Copy link
Copy Markdown
Contributor

@keyz keyz commented Mar 8, 2026

this is an attempt to fix #3034 and #3035 via introducing ValidatedDefault for structured defaults (that must be validated through the declared field type)

before this change, some union and type-alias defaults were emitted either as raw dict/list literals or validated through the wrong union branch

overall I think the fixture coverage is good, but there may be a cleaner fix than ValidatedDefault. please feel free to edit this PR or open a separate PR if you see a better solution!


as extra context: here are the CLI flags I use. I use the library to translate codex app-server json schema to pydantic models:

--input-file-type jsonschema
--output-model-type pydantic_v2.BaseModel
--target-pydantic-version 2.11
--target-python-version 3.12
--use-default-kwarg
--snake-case-field
--use-union-operator
--use-standard-collections
--field-constraints
--use-annotated
--reuse-model
--collapse-reuse-models
--enum-field-as-literal all
--use-title-as-name
--allow-population-by-field-name
--use-type-alias

Summary by CodeRabbit

  • New Features

    • Generated models can embed runtime-validated default values for complex types (unions, type aliases, lists, dicts), ensuring defaults conform to declared field types.
    • Introduces an opt-in capability flag to enable validated defaults; Pydantic v2 support reflects this capability.
  • Tests

    • Large test suite added to cover many type-alias and default-value scenarios across JSON Schema patterns and configuration combinations.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 8, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new ValidatedDefault wrapper and wiring so parser wraps certain complex/type-alias defaults; codegen for Pydantic v2 emits those as TypeAdapter(...).validate_python(...) expressions. Many tests and expected outputs for type-alias default scenarios were added.

Changes

Cohort / File(s) Summary
ValidatedDefault Type
src/datamodel_code_generator/model/_types.py
Add ValidatedDefault dataclass: value: Any, type_name: str, and __repr__ that renders TypeAdapter({type_name}).validate_python({value!r}).
Core exports & capability flags
src/datamodel_code_generator/model/base.py, src/datamodel_code_generator/model/pydantic_v2/base_model.py
Export ValidatedDefault; add SUPPORTS_VALIDATED_DEFAULT: ClassVar[bool] to DataModel (False) and Pydantic v2 BaseModel (True).
Pydantic codegen changes
src/datamodel_code_generator/model/pydantic_base.py
Treat ValidatedDefault similarly to WrappedDefault when emitting defaults; add TypeAdapter import when a field uses ValidatedDefault.
Parser: default-wrapping logic
src/datamodel_code_generator/parser/base.py
New helper functions to inspect types and a private __wrap_validated_default_values that wraps applicable defaults with ValidatedDefault (invoked in module processing and finalization).
Generated expected outputs
tests/data/expected/main/jsonschema/type_alias_*.py (many files)
New expected fixtures showing defaults rendered via TypeAdapter(...).validate_python(...) across alias chains, unions, lists, dicts, inline unions, recursive types, etc.
Tests
tests/main/jsonschema/test_main_jsonschema.py
Add ~16+ tests exercising type-alias default wrapping across flags/modes (use-type-alias, Pydantic v2, Python 3.12, Annotated), gated by Black version where applicable.

Sequence Diagram(s)

sequenceDiagram
    participant Parser
    participant Codegen
    participant Runtime
    Parser->>Parser: analyze field type & default\n(_needs_validated_default + helpers)
    alt default requires validation
        Parser->>Parser: wrap default -> ValidatedDefault(type_name, value)
    end
    Parser->>Codegen: produce model AST containing ValidatedDefault
    Codegen->>Codegen: add TypeAdapter import when needed
    Codegen->>Runtime: emit default as\nTypeAdapter(type_name).validate_python(value)
    Note right of Runtime: TypeAdapter validates and constructs\nruntime default instance
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

Suggested labels

breaking-change-analyzed

Suggested reviewers

  • ilovelinux

Poem

🐇
I hopped through alias tunnels, one by one,
Wrapped defaults snug until validation's done.
TypeAdapter hums, turning dicts to form,
A rabbit's nibble — defaults now conform.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.19% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 accurately describes the primary change: introducing ValidatedDefault to fix issues #3034 and #3035 related to raw dict defaults in type aliases.
Linked Issues check ✅ Passed The PR fully addresses the coding requirement from #3034: introducing ValidatedDefault to ensure structured defaults for unions/type aliases are validated against the correct union branch instead of emitting raw dict literals.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing ValidatedDefault for handling structured defaults in unions/type-aliases. Test fixtures demonstrate the feature across multiple scenarios but remain within scope.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ca51faaaef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/datamodel_code_generator/parser/base.py
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

Caution

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

⚠️ Outside diff range comments (1)
src/datamodel_code_generator/model/base.py (1)

28-41: ⚠️ Potential issue | 🟠 Major

Don't narrow model.base exports with this new __all__.

__all__ = ["ValidatedDefault", "WrappedDefault"] turns from datamodel_code_generator.model.base import * into a two-symbol export, which is a breaking API change for any existing consumer that relied on the module's other public names. If the goal is just to re-export the new wrapper types, avoid adding __all__ here or include the full intended public surface.

Suggested fix
 from datamodel_code_generator.model._types import ValidatedDefault, WrappedDefault
-
-__all__ = ["ValidatedDefault", "WrappedDefault"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/datamodel_code_generator/model/base.py` around lines 28 - 41, The new
module-level __all__ currently restricts exports to only ValidatedDefault and
WrappedDefault, breaking existing consumers of other public symbols from
model.base; either remove the __all__ declaration entirely or expand it to
include the full public surface (e.g., Reference, _BaseModel, ANY, NONE,
OPTIONAL_PREFIX, UNION_PREFIX, DataType, Nullable, chain_as_tuple,
get_optional_type along with ValidatedDefault and WrappedDefault) so that from
datamodel_code_generator.model.base import * continues to export the original
API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/datamodel_code_generator/parser/base.py`:
- Around line 480-484: The branch filtering uses data_type.data_types which can
be empty for aliases; change the non-none branch computation in the
resolved.is_union case to iterate over resolved.data_types (not
data_type.data_types) so it correctly builds non_none_branches, e.g. update the
tuple comprehension that defines non_none_branches to use resolved.data_types;
keep the subsequent checks that call
_uses_existing_model_factory_path(non_none_branches[0]) and the return that
calls _default_matches_plain_container_branch on resolved.data_types unchanged.

In `@tests/data/expected/main/jsonschema/type_alias_recursive_default_list.py`:
- Line 9: The fixture file containing the PEP 695 syntax (the line "type Alias =
list[Alias] | int") should be renamed from type_alias_recursive_default_list.py
to type_alias_recursive_default_list_py312.py to follow the repository
convention for Python 3.12-only fixtures; update any tests or references that
import or reference the old filename to the new filename
(type_alias_recursive_default_list_py312.py) so imports/fixtures point to the
renamed file.

---

Outside diff comments:
In `@src/datamodel_code_generator/model/base.py`:
- Around line 28-41: The new module-level __all__ currently restricts exports to
only ValidatedDefault and WrappedDefault, breaking existing consumers of other
public symbols from model.base; either remove the __all__ declaration entirely
or expand it to include the full public surface (e.g., Reference, _BaseModel,
ANY, NONE, OPTIONAL_PREFIX, UNION_PREFIX, DataType, Nullable, chain_as_tuple,
get_optional_type along with ValidatedDefault and WrappedDefault) so that from
datamodel_code_generator.model.base import * continues to export the original
API.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1e835353-fc2e-4e36-9d7a-5b3d7738fbbe

📥 Commits

Reviewing files that changed from the base of the PR and between 3c244da and ca51faa.

⛔ Files ignored due to path filters (16)
  • tests/data/jsonschema/type_alias_chain_model_default_object_ref.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_chain_union_default_object_ref.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_dict_union_default_object_ref.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_inline_dict_union_default_object.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_inline_list_union_default_object.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_inline_union_default_object.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_inline_union_default_object_one_of.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_inline_union_default_object_silent_wrong_branch.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_list_model_default_object_ref.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_list_union_default_object_ref.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_model_default_object_ref.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_recursive_default_list.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_union_default_object_ref.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_union_default_object_ref_any_of.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_union_default_object_ref_mixed_scalar.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_union_default_object_ref_one_of.json is excluded by !tests/data/**/*.json and included by none
📒 Files selected for processing (22)
  • src/datamodel_code_generator/model/_types.py
  • src/datamodel_code_generator/model/base.py
  • src/datamodel_code_generator/model/pydantic_base.py
  • src/datamodel_code_generator/model/pydantic_v2/base_model.py
  • src/datamodel_code_generator/parser/base.py
  • tests/data/expected/main/jsonschema/type_alias_chain_model_default_object_ref.py
  • tests/data/expected/main/jsonschema/type_alias_chain_union_default_object_ref.py
  • tests/data/expected/main/jsonschema/type_alias_dict_union_default_object_ref.py
  • tests/data/expected/main/jsonschema/type_alias_inline_dict_union_default_object.py
  • tests/data/expected/main/jsonschema/type_alias_inline_list_union_default_object.py
  • tests/data/expected/main/jsonschema/type_alias_inline_union_default_object.py
  • tests/data/expected/main/jsonschema/type_alias_inline_union_default_object_one_of.py
  • tests/data/expected/main/jsonschema/type_alias_inline_union_default_object_silent_wrong_branch.py
  • tests/data/expected/main/jsonschema/type_alias_list_model_default_object_ref.py
  • tests/data/expected/main/jsonschema/type_alias_list_union_default_object_ref.py
  • tests/data/expected/main/jsonschema/type_alias_model_default_object_ref.py
  • tests/data/expected/main/jsonschema/type_alias_recursive_default_list.py
  • tests/data/expected/main/jsonschema/type_alias_union_default_object_ref.py
  • tests/data/expected/main/jsonschema/type_alias_union_default_object_ref_any_of.py
  • tests/data/expected/main/jsonschema/type_alias_union_default_object_ref_mixed_scalar.py
  • tests/data/expected/main/jsonschema/type_alias_union_default_object_ref_one_of.py
  • tests/main/jsonschema/test_main_jsonschema.py

Comment thread src/datamodel_code_generator/parser/base.py
Comment thread src/datamodel_code_generator/parser/base.py Dismissed
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Mar 9, 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 keyz:keyz-fix-3034 (689c7d6) with main (95daa4e)

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.

@koxudaxi
Copy link
Copy Markdown
Owner

koxudaxi commented Mar 9, 2026

@keyz
Thanks for opening the issue and for putting together this PR.

Could you make one small design cleanup in this PR before merge?

The fix looks good overall, but I think the new validated-default logic in parser/base.py should use a model-level capability instead of checking concrete Pydantic v2 classes directly. That pattern already exists in this codebase (SUPPORTS_DISCRIMINATOR, SUPPORTS_WRAPPED_DEFAULT, etc.), so this would fit the current design better without turning this into a larger refactor.

Also, if the existing bot comments are valid, please address those here as well. In particular, the alias-union branch check (resolved.data_types vs data_type.data_types) looks worth fixing, and the comment about capturing ValidatedDefault.type_name before later type-override processing also seems worth addressing.

Copy link
Copy Markdown
Owner

@koxudaxi koxudaxi left a comment

Choose a reason for hiding this comment

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

Please check the comment

Comment thread src/datamodel_code_generator/parser/base.py Outdated
@koxudaxi
Copy link
Copy Markdown
Owner

koxudaxi commented Mar 9, 2026

I think ValidatedDefault.type_name is getting set too early in __wrap_validated_default_values().

That runs before later steps like type overrides, so the generated TypeAdapter(...) call can end up with the wrong type name. Please move this wrapping step to a point after the field type has been finalized, or otherwise avoid storing the rendered type name this early.

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.

Caution

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

⚠️ Outside diff range comments (1)
src/datamodel_code_generator/parser/base.py (1)

3286-3295: ⚠️ Potential issue | 🟠 Major

Wrap validated defaults after type finalization.

Line 3287 freezes ValidatedDefault.type_name before __apply_type_overrides() and __update_type_aliases(). If either pass rewrites the field type, the generated TypeAdapter(...) call points at the old symbol.

Suggested fix
         self.__set_default_enum_member(models)
         self.__wrap_root_model_default_values(models)
-        self.__wrap_validated_default_values(models)
         self.__sort_models(models, imports, use_deferred_annotations=config.use_deferred_annotations)
         self.__change_field_name(models)
         self.__apply_discriminator_type(models, imports)
         self.__set_one_literal_on_default(models)
         self.__fix_dataclass_field_ordering(models)
         models = self.__remove_overridden_models(models)
         self.__apply_type_overrides(models)
         self.__update_type_aliases(models)
+        self.__wrap_validated_default_values(models)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/datamodel_code_generator/parser/base.py` around lines 3286 - 3295, The
ValidatedDefault.type_name is being finalized too early by calling
__wrap_validated_default_values(models) before type-finalizing passes; move the
call (or re-run wrapping) so that __wrap_validated_default_values(models) is
executed after both __apply_type_overrides(models) and
__update_type_aliases(models) (i.e., ensure __apply_type_overrides and
__update_type_aliases run before __wrap_validated_default_values) so that the
TypeAdapter(...) generation uses the final, possibly rewritten field type names.
🧹 Nitpick comments (1)
src/datamodel_code_generator/parser/base.py (1)

415-456: Use model capabilities here instead of concrete pydantic_v2 classes.

_contains_model_reference() and _uses_existing_model_factory_path() hard-code pydantic_model_v2.BaseModel / RootModel, which pulls backend-specific knowledge back into parser/base.py. The rest of this file already relies on model capability flags for this kind of branching, so these helpers should do the same to keep the parser backend-agnostic.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/datamodel_code_generator/parser/base.py`:
- Around line 3286-3295: The ValidatedDefault.type_name is being finalized too
early by calling __wrap_validated_default_values(models) before type-finalizing
passes; move the call (or re-run wrapping) so that
__wrap_validated_default_values(models) is executed after both
__apply_type_overrides(models) and __update_type_aliases(models) (i.e., ensure
__apply_type_overrides and __update_type_aliases run before
__wrap_validated_default_values) so that the TypeAdapter(...) generation uses
the final, possibly rewritten field type names.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c8917c83-c3e0-48f9-a0da-f0403b1fee83

📥 Commits

Reviewing files that changed from the base of the PR and between ca51faa and d56b746.

📒 Files selected for processing (1)
  • src/datamodel_code_generator/parser/base.py

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

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

415-456: Use model-level capability flags instead of concrete class checks.

Per maintainer feedback, _contains_model_reference and _uses_existing_model_factory_path check concrete Pydantic v2 classes directly:

isinstance(resolved.reference.source, (pydantic_model_v2.BaseModel, pydantic_model_v2.RootModel))

This pattern differs from the codebase convention of using model-level capability flags (like SUPPORTS_DISCRIMINATOR, SUPPORTS_WRAPPED_DEFAULT). Consider introducing a capability check on the source model (e.g., checking for a SUPPORTS_VALIDATED_DEFAULT flag or a similar marker) to maintain consistency and allow other model types to opt-in.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/datamodel_code_generator/parser/base.py` around lines 415 - 456, Replace
the concrete Pydantic class checks in _contains_model_reference and
_uses_existing_model_factory_path with a model-level capability flag check on
the referenced model (e.g., test reference.source for a boolean marker like
SUPPORTS_VALIDATED_DEFAULT or another agreed-upon capability constant) instead
of isinstance(..., (pydantic_model_v2.BaseModel, pydantic_model_v2.RootModel));
update both functions to call the same capability predicate (e.g.,
hasattr(reference.source, "SUPPORTS_VALIDATED_DEFAULT") and truthiness) when
examining reference.source and reference of nested/data_types items so other
model types can opt-in without relying on concrete classes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/datamodel_code_generator/parser/base.py`:
- Around line 2240-2260: The bug is that __wrap_validated_default_values
computes ValidatedDefault.type_name using
_get_validated_default_type_name(model_field.data_type) before
__apply_type_overrides runs, producing wrong TypeAdapter names; fix by either
calling __wrap_validated_default_values after __apply_type_overrides (move its
invocation to after __apply_type_overrides) or change ValidatedDefault to defer
type_name resolution (store the original data_type or a callable/lazy resolver
instead of a concrete type_name and compute it during render), updating places
that read ValidatedDefault.type_name to call the resolver.

In `@tests/main/jsonschema/test_main_jsonschema.py`:
- Around line 5722-5990: Add a new regression test that exercises
post-finalization type rewrites by creating a test function (e.g.,
test_main_jsonschema_type_alias_post_finalization_rewrite) that calls
run_main_and_assert with an input JSON schema designed to change the rendered
field type after wrapping (e.g., JSON_SCHEMA_DATA_PATH /
"type_alias_post_finalization_rewrite.json") and an expected output file that
asserts the generated TypeAdapter uses the final type hint (e.g.,
"type_alias_post_finalization_rewrite.py"); use the same flags as the other
tests (--use-type-alias, --use-annotated, --target-python-version 3.12,
--output-model-type pydantic_v2.BaseModel, --target-pydantic-version 2.11) so
the test verifies the adapter string reflects the finalized type rather than a
stale name, and add the test next to the other type-alias tests using
run_main_and_assert and assert_file_content.
- Around line 5704-5719: The test function
test_main_jsonschema_type_alias_inline_union_default_object is missing the
Black-version skip guard; wrap or annotate this test with the same
pytest.mark.skipif used in sibling tests (checking
int(black.__version__.split(".")[0]) < 23) so it is skipped when Black major
version is less than 23, ensuring it only runs in environments that support the
new 'type' syntax generated by --use-type-alias.

---

Nitpick comments:
In `@src/datamodel_code_generator/parser/base.py`:
- Around line 415-456: Replace the concrete Pydantic class checks in
_contains_model_reference and _uses_existing_model_factory_path with a
model-level capability flag check on the referenced model (e.g., test
reference.source for a boolean marker like SUPPORTS_VALIDATED_DEFAULT or another
agreed-upon capability constant) instead of isinstance(...,
(pydantic_model_v2.BaseModel, pydantic_model_v2.RootModel)); update both
functions to call the same capability predicate (e.g., hasattr(reference.source,
"SUPPORTS_VALIDATED_DEFAULT") and truthiness) when examining reference.source
and reference of nested/data_types items so other model types can opt-in without
relying on concrete classes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bb5436bd-8ba5-4be1-83b2-b354e066f7f0

📥 Commits

Reviewing files that changed from the base of the PR and between d56b746 and 46c96f6.

📒 Files selected for processing (3)
  • src/datamodel_code_generator/model/base.py
  • src/datamodel_code_generator/parser/base.py
  • tests/main/jsonschema/test_main_jsonschema.py

Comment thread src/datamodel_code_generator/parser/base.py
Comment thread tests/main/jsonschema/test_main_jsonschema.py
Comment thread tests/main/jsonschema/test_main_jsonschema.py
@keyz
Copy link
Copy Markdown
Contributor Author

keyz commented Mar 10, 2026

thanks @koxudaxi! i pushed some new fixtures for --type-overrides, added a model-level capability check, and deferred the resolution. let me know if you have more feedback! also please feel free to edit my PR if that's easier :)

@keyz
Copy link
Copy Markdown
Contributor Author

keyz commented Mar 10, 2026

@codex review

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

🧹 Nitpick comments (1)
tests/main/jsonschema/test_main_jsonschema.py (1)

5594-6052: Add one non-Pydantic regression for the wrapped-default capability gate.

This block only exercises pydantic_v2.BaseModel. The PR objective also calls out moving wrapped-default support behind a model capability flag, so an unsupported backend could start emitting ValidatedDefault/TypeAdapter(...) without this suite noticing. Please add one companion case for a backend that should not support wrapped defaults and assert it keeps that backend’s native default emission.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/main/jsonschema/test_main_jsonschema.py` around lines 5594 - 6052, Add
a new test function alongside the existing ones that mirrors
test_main_jsonschema_type_alias_union_default_object_ref_* but targets a
non-Pydantic backend (call it e.g.
test_main_jsonschema_type_alias_union_default_object_ref_non_pydantic_backend)
that calls run_main_and_assert with the same input JSON
(type_alias_union_default_object_ref.json), but sets extra_args to include
"--use-type-alias", "--target-python-version", "3.12" and "--output-model-type"
set to a non-pydantic backend (e.g. "dataclasses.dataclass"); set expected_file
to a new back-end-specific expected output (e.g.
"type_alias_union_default_object_ref_dataclasses.py") and assert_file_content so
the test verifies the backend emits its native default (not
ValidatedDefault/TypeAdapter).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/datamodel_code_generator/parser/base.py`:
- Around line 440-451: The helper _uses_existing_model_factory_path is
inspecting union branch wrappers directly and must first unwrap any alias-backed
branches before checking .reference, .is_list, or .data_types; modify
_uses_existing_model_factory_path to, for each candidate, follow/resolve an
alias chain to the underlying/base DataType (the same resolution logic used by
_needs_validated_default) and then perform the existing checks (reference +
_supports_validated_default_model and list-with-single-branch checks) against
that resolved type; apply the identical alias-unwrapping change to the other
helper checks noted in the comment so all places treat alias->dict/list/Model
branches the same way.

---

Nitpick comments:
In `@tests/main/jsonschema/test_main_jsonschema.py`:
- Around line 5594-6052: Add a new test function alongside the existing ones
that mirrors test_main_jsonschema_type_alias_union_default_object_ref_* but
targets a non-Pydantic backend (call it e.g.
test_main_jsonschema_type_alias_union_default_object_ref_non_pydantic_backend)
that calls run_main_and_assert with the same input JSON
(type_alias_union_default_object_ref.json), but sets extra_args to include
"--use-type-alias", "--target-python-version", "3.12" and "--output-model-type"
set to a non-pydantic backend (e.g. "dataclasses.dataclass"); set expected_file
to a new back-end-specific expected output (e.g.
"type_alias_union_default_object_ref_dataclasses.py") and assert_file_content so
the test verifies the backend emits its native default (not
ValidatedDefault/TypeAdapter).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1f120291-e38e-4d6c-8bac-9bce3ac77911

📥 Commits

Reviewing files that changed from the base of the PR and between 46c96f6 and 54b947c.

⛔ Files ignored due to path filters (2)
  • tests/data/jsonschema/type_alias_inline_union_default_object_import_collision.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_inline_union_default_object_type_override.json is excluded by !tests/data/**/*.json and included by none
📒 Files selected for processing (4)
  • src/datamodel_code_generator/parser/base.py
  • tests/data/expected/main/jsonschema/type_alias_inline_union_default_object_import_collision.py
  • tests/data/expected/main/jsonschema/type_alias_inline_union_default_object_type_override.py
  • tests/main/jsonschema/test_main_jsonschema.py

Comment thread src/datamodel_code_generator/parser/base.py
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 54b947c421

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/datamodel_code_generator/parser/base.py Outdated
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

🧹 Nitpick comments (2)
tests/data/expected/main/jsonschema/type_alias_nullable_model_default_object_ref.py (1)

16-21: Same redundant | None pattern as in other fixtures.

Alias is A | None, so x: Alias | None is redundant. This appears to be a consistent pattern in generated fixtures. If intentional for schema fidelity, consider documenting this; otherwise, the generator could simplify to x: Alias.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tests/data/expected/main/jsonschema/type_alias_nullable_model_default_object_ref.py`
around lines 16 - 21, The type annotation for field x redundantly adds "| None"
on top of Alias which is already A | None; update the generator so it does not
duplicate optionality by removing the extra "| None" when Alias is already
nullable — i.e., change the produced annotation from "x: Alias | None" to "x:
Alias" (leaving default_factory and usage of TypeAdapter(Alias) unchanged) so
Model.x reflects Alias's nullability only once.
tests/data/expected/main/jsonschema/type_alias_nullable_list_model_default_object_ref.py (1)

16-21: Redundant | None in field type declaration.

Alias is defined as list[A] | None, so declaring x: Alias | None results in list[A] | None | None, which is semantically redundant. If the generator intentionally preserves this for schema fidelity, this may be acceptable, but otherwise consider emitting just x: Alias.

Note: The Ruff warning about type statement syntax is expected since this fixture targets Python 3.12 as indicated in the PR objectives.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tests/data/expected/main/jsonschema/type_alias_nullable_list_model_default_object_ref.py`
around lines 16 - 21, The field annotation on Model is redundant: Alias is
already defined as "list[A] | None", so change the Model.x annotation from
"Alias | None" to just "Alias" (keep the existing Field(default_factory=...) and
the TypeAdapter(Alias).validate_python([...]) call) so the type resolves to
"list[A] | None" without duplicating the optional; update the annotation on the
Model class accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/main/jsonschema/test_main_jsonschema.py`:
- Around line 5774-6123: The tests exercising “relevant flags” are missing the
--use-default-kwarg path; update each test function in this diff (e.g.,
test_main_jsonschema_type_alias_union_default_object_ref_any_of_relevant_flags,
test_main_jsonschema_type_alias_chain_union_default_object_ref_relevant_flags,
test_main_jsonschema_type_alias_union_default_object_ref_one_of_relevant_flags,
test_main_jsonschema_type_alias_inline_union_default_object_one_of_relevant_flags,
test_main_jsonschema_type_alias_inline_union_default_object_import_collision_relevant_flags,
test_main_jsonschema_type_alias_inline_union_default_object_type_override_relevant_flags,
test_main_jsonschema_type_alias_inline_union_default_object_silent_wrong_branch_relevant_flags,
test_main_jsonschema_type_alias_inline_list_union_default_object_relevant_flags,
test_main_jsonschema_type_alias_list_union_default_object_ref_relevant_flags,
test_main_jsonschema_type_alias_inline_dict_union_default_object_relevant_flags,
test_main_jsonschema_type_alias_dict_union_default_object_ref_relevant_flags,
test_main_jsonschema_type_alias_union_default_object_ref_dict_alias_branch_relevant_flags,
test_main_jsonschema_type_alias_union_default_object_ref_mixed_scalar_relevant_flags)
to include "--use-default-kwarg" in their extra_args list so the suite covers
the Field(default=...) rendering path.

---

Nitpick comments:
In
`@tests/data/expected/main/jsonschema/type_alias_nullable_list_model_default_object_ref.py`:
- Around line 16-21: The field annotation on Model is redundant: Alias is
already defined as "list[A] | None", so change the Model.x annotation from
"Alias | None" to just "Alias" (keep the existing Field(default_factory=...) and
the TypeAdapter(Alias).validate_python([...]) call) so the type resolves to
"list[A] | None" without duplicating the optional; update the annotation on the
Model class accordingly.

In
`@tests/data/expected/main/jsonschema/type_alias_nullable_model_default_object_ref.py`:
- Around line 16-21: The type annotation for field x redundantly adds "| None"
on top of Alias which is already A | None; update the generator so it does not
duplicate optionality by removing the extra "| None" when Alias is already
nullable — i.e., change the produced annotation from "x: Alias | None" to "x:
Alias" (leaving default_factory and usage of TypeAdapter(Alias) unchanged) so
Model.x reflects Alias's nullability only once.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8b549caf-4202-4fb9-87ba-cc869cd4c80b

📥 Commits

Reviewing files that changed from the base of the PR and between 54b947c and 31833bf.

⛔ Files ignored due to path filters (3)
  • tests/data/jsonschema/type_alias_nullable_list_model_default_object_ref.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_nullable_model_default_object_ref.json is excluded by !tests/data/**/*.json and included by none
  • tests/data/jsonschema/type_alias_union_default_object_ref_dict_alias_branch.json is excluded by !tests/data/**/*.json and included by none
📒 Files selected for processing (5)
  • src/datamodel_code_generator/parser/base.py
  • tests/data/expected/main/jsonschema/type_alias_nullable_list_model_default_object_ref.py
  • tests/data/expected/main/jsonschema/type_alias_nullable_model_default_object_ref.py
  • tests/data/expected/main/jsonschema/type_alias_union_default_object_ref_dict_alias_branch.py
  • tests/main/jsonschema/test_main_jsonschema.py

Comment thread tests/main/jsonschema/test_main_jsonschema.py
koxudaxi
koxudaxi previously approved these changes Mar 10, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 10, 2026

Codecov Report

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

Additional details and impacted files
@@            Coverage Diff             @@
##              main     #3040    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           85        85            
  Lines        17692     17883   +191     
  Branches      2068      2091    +23     
==========================================
+ Hits         17692     17883   +191     
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 77af774 into koxudaxi:main Mar 10, 2026
38 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

Breaking Change Analysis

Result: No breaking changes detected

Reasoning: This PR is a bug fix for issues #3034 and #3035. It introduces ValidatedDefault to correctly handle union/type-alias defaults that were previously validated through the wrong union branch or emitted as raw literals. Key factors: 1) All test changes are new additions (21 new test files), no existing test outputs were modified, indicating the fix targets previously uncovered scenarios. 2) No template changes required - the new ValidatedDefault class integrates seamlessly through its repr method. 3) No CLI/API changes. 4) Existing generated code remains valid - users only see changes if they regenerate. 5) The change improves correctness by using TypeAdapter(UnionType).validate_python() instead of picking a potentially wrong union branch. This is a code generation improvement/fix, not a breaking change.


This analysis was performed by Claude Code Action

@github-actions
Copy link
Copy Markdown
Contributor

🎉 Released in 0.55.0

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

butvinm added a commit to butvinm/datamodel-code-generator that referenced this pull request Mar 13, 2026
…tructured defaults

Replace the ValidatedDefault/TypeAdapter mechanism from koxudaxi#3040 and the
original model_validate() codegen with Pydantic's native validate_default=True.

Instead of generating complex default_factory lambdas like:
  default_factory=lambda: A.model_validate({'type': 'b'})
  default_factory=lambda: TypeAdapter(A | B).validate_python({'type': 'b'})

Now generates:
  Field({'type': 'b'}, validate_default=True)

This lets Pydantic handle union branch selection, dict-to-model coercion,
and RootModel wrapping at runtime. Fixes koxudaxi#3034, fixes koxudaxi#3035.

Removed:
- ValidatedDefault class and SUPPORTS_VALIDATED_DEFAULT flag
- TypeAdapter import in generated code
- 70-line branch-matching loop in _get_default_as_pydantic_model
- Helper functions: _supports_validated_default_model,
  _uses_existing_model_factory_path, _default_matches_plain_container_branch,
  _get_validated_default_type_name

Added:
- _needs_validate_default: simple check (is default dict/list/scalar
  and does the type contain a model reference?)
- __set_validate_default_on_fields: sets extras["validate_default"] = True

Co-Authored-By: Claude Opus 4.6 <[email protected]>
koxudaxi pushed a commit that referenced this pull request Mar 14, 2026
* Use validate_default=True instead of model_validate/TypeAdapter for structured defaults

Replace the ValidatedDefault/TypeAdapter mechanism from #3040 and the
original model_validate() codegen with Pydantic's native validate_default=True.

Instead of generating complex default_factory lambdas like:
  default_factory=lambda: A.model_validate({'type': 'b'})
  default_factory=lambda: TypeAdapter(A | B).validate_python({'type': 'b'})

Now generates:
  Field({'type': 'b'}, validate_default=True)

This lets Pydantic handle union branch selection, dict-to-model coercion,
and RootModel wrapping at runtime. Fixes #3034, fixes #3035.

Removed:
- ValidatedDefault class and SUPPORTS_VALIDATED_DEFAULT flag
- TypeAdapter import in generated code
- 70-line branch-matching loop in _get_default_as_pydantic_model
- Helper functions: _supports_validated_default_model,
  _uses_existing_model_factory_path, _default_matches_plain_container_branch,
  _get_validated_default_type_name

Added:
- _needs_validate_default: simple check (is default dict/list/scalar
  and does the type contain a model reference?)
- __set_validate_default_on_fields: sets extras["validate_default"] = True

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* Remove dead None guard from _needs_validate_default to fix coverage

The caller already filters None/UNDEFINED defaults before calling this
function, so the None check was unreachable. Removed the unused `default`
parameter entirely.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* Remove WrappedDefault in favor of validate_default=True for RootModel defaults

WrappedDefault wrapped RootModel scalar defaults (e.g., CountType(10)) in
default_factory lambdas. Since validate_default=True handles this natively,
the entire mechanism is redundant.

Removed: WrappedDefault class, _types.py module, SUPPORTS_WRAPPED_DEFAULT
flag, __wrap_root_model_default_values method, _get_default_as_pydantic_model
method.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* Make GraphQL scalar types extend TypeAliasBase

_DataTypeScalarBase generated the same type alias syntax as TypeAliasBase
but didn't inherit from it, causing _unwrap_type_alias to not see through
scalar references. This led to unnecessary validate_default=True on fields
like `String | None = 'default_user'`.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* Fix required fields with validate_default losing their default value

When a required field had validate_default=True set (e.g., empty list
default on list[Container]), the rendering logic prepended ... instead
of the actual default value. Now skips the ... override when
validate_default is set, since the default is needed for Pydantic to
validate at runtime.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* Fix inaccurate docstring in test_needs_validate_default test

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 4, 2026

📢 Related PR Released: 0.56.0

PR #3050, which references this PR, has been released. 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.

--use-type-alias emits raw dict defaults for $ref object unions

3 participants