Skip to content

Create type aliases for inline types with title when use-title-as-name is enabled#2889

Merged
koxudaxi merged 1 commit intomainfrom
fix/use-title-as-name-inline-types
Jan 2, 2026
Merged

Create type aliases for inline types with title when use-title-as-name is enabled#2889
koxudaxi merged 1 commit intomainfrom
fix/use-title-as-name-inline-types

Conversation

@koxudaxi
Copy link
Copy Markdown
Owner

@koxudaxi koxudaxi commented Jan 1, 2026

Fixes: #2887

Summary by CodeRabbit

  • New Features

    • Creates named aliases for inline types when a title is present, improving generated type names and readability.
    • Better handling of anonymous object schemas with additionalProperties to produce clearer dict typings.
    • Wraps union-typed fields in small named models to preserve titles and adjust default/value parsing semantics.
  • Tests

    • Added tests validating titled inline type aliasing and the union-wrapper behavior.

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 1, 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 33 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 0b487bf and c4ee616.

⛔ Files ignored due to path filters (1)
  • tests/data/jsonschema/use_title_as_name_inline_types.json is excluded by !tests/data/**/*.json and included by none
📒 Files selected for processing (7)
  • docs/cli-reference/field-customization.md
  • src/datamodel_code_generator/__init__.py
  • src/datamodel_code_generator/parser/jsonschema.py
  • tests/data/expected/main/jsonschema/titles_use_title_as_name.py
  • tests/data/expected/main/jsonschema/use_title_as_name_inline_types.py
  • tests/data/expected/main/jsonschema/use_title_as_name_inline_types_pydantic.py
  • tests/main/jsonschema/test_main_jsonschema.py
📝 Walkthrough

Walkthrough

The JSONSchema parser now creates named type aliases for inline schemas with titles (arrays, dicts with additionalProperties, enums, oneOf/anyOf) by routing them to parse_root_type; parse_root_type gained dict handling for anonymous objects. Tests and docs updated to reflect generated wrapper models and alias usage.

Changes

Cohort / File(s) Summary
Core Parser Logic
src/datamodel_code_generator/parser/jsonschema.py
Added _should_create_type_alias_for_title(item, name) and use in parse_item/property parsing to route titled inline types to parse_root_type. Extended parse_root_type to handle inline objects-as-dicts and adjusted annotations.
Parser Init / Pydantic handling
src/datamodel_code_generator/__init__.py
Adjusted Pydantic v2 config merging: collect parser config fields, build filtered options from GenerateConfig, merge with additional_options, and pass to model_validate.
Tests / Expected Outputs
tests/main/jsonschema/test_main_jsonschema.py, tests/data/expected/main/jsonschema/*
Added tests for --use-title-as-name with inline types (TypedDict and Pydantic paths). Updated expected outputs to include top-level type aliases (MyArrayName, MyObjectName, etc.) and a new wrapper model (ProcessingStatusUnionTitle) with updated field default handling.
Generated Pydantic examples
tests/data/expected/main/jsonschema/use_title_as_name_inline_types_pydantic.py
New Pydantic example showing RootModel/Enum/StrEnum wrappers for inline titled types and a Foo model referencing them.
Docs / Examples
docs/cli-reference/field-customization.md
Updated example to show ProcessingStatusUnionTitle wrapper and ProcessingTaskTitle field using default_factory with parse_obj.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

breaking-change-analyzed

Suggested reviewers

  • ilovelinux

Poem

🐇 I hop through schema lines at dawn,
I spot the titles, name them on.
Arrays, dicts, unions—no longer stray,
I tuck them in aliases to stay.
A nibble, a hop, code saved the day.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main objective: creating type aliases for inline types with titles when use-title-as-name is enabled, which matches the primary changes in the changeset.
Linked Issues check ✅ Passed The PR successfully implements the requirements from issue #2887 by adding the _should_create_type_alias_for_title helper method to identify when titled inline types should become type aliases, integrating this logic into the parsing flow, and adding comprehensive tests validating that type aliases are created for arrays, objects, enums, and unions with titles.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the use-title-as-name feature for inline types. The modification to config merging in init.py is a necessary supporting change for proper configuration handling during parsing.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

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

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Jan 1, 2026

CodSpeed Performance Report

Merging #2889 will degrade performance by 16.34%

Comparing fix/use-title-as-name-inline-types (c4ee616) with main (94bb621)

⚠️ 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 regressions
⏩ 98 skipped1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Benchmarks breakdown

Mode Benchmark BASE HEAD Efficiency
WallTime test_perf_stripe_style_pydantic_v2 1.7 s 2 s -14.74%
WallTime test_perf_large_models_pydantic_v2 3 s 3.6 s -16.34%
WallTime test_perf_all_options_enabled 5.7 s 6.6 s -13.62%
WallTime test_perf_aws_style_openapi_pydantic_v2 1.7 s 1.9 s -13.85%
WallTime test_perf_deep_nested 5.2 s 6 s -13.56%
WallTime test_perf_multiple_files_input 3.2 s 3.7 s -14.15%
WallTime test_perf_graphql_style_pydantic_v2 702.2 ms 816.2 ms -13.97%
WallTime test_perf_duplicate_names 845.5 ms 1,001.6 ms -15.58%
WallTime test_perf_kubernetes_style_pydantic_v2 2.2 s 2.6 s -15.39%
WallTime test_perf_complex_refs 1.7 s 2 s -15.03%
WallTime test_perf_openapi_large 2.5 s 2.9 s -13.56%

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.

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 (3)
tests/main/jsonschema/test_main_jsonschema.py (1)

3053-3077: Test wiring correctly exercises the inline-title alias bugfix

The test setup (schema path, expected file, and CLI args: --use-title-as-name, TypedDict, Python 3.13, union operator, standard collections, skip-root-model) is consistent with the behavior described in issue #2887 and the new expected snapshot. This should give good coverage for titled inline array/dict/enum/oneOf/anyOf schemas being turned into named type aliases rather than inlined types.

You may want to double‑check that your formatter/black and CI configuration fully support Python 3.13 and type statements for this test, or gate it similarly to other 3.13‑specific tests if needed.

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

1-33: Snapshot matches intended 3.13 type-alias output; verify Ruff/target version

The expected file structure and type aliases look correct for the 3.13‑targeted TypedDict test and align well with the PR goal of naming inline titled schemas.

Static analysis is flagging the type statements as invalid under Python 3.10; if Ruff (or other tooling) is configured with a pre‑3.12 target, you may need to either:

  • bump the target version for this part of the tree, or
  • exclude these generated‑snapshot files from such checks

to avoid spurious lint failures while still testing 3.13‑only syntax.

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

2938-2991: New dict handling in parse_root_type looks correct; remove unused noqa

  • The elif obj.is_object and not obj.properties ... and isinstance(obj.additionalProperties, JsonSchemaObject) branch brings root‑level handling of anonymous “object with additionalProperties” in line with the inline object path, including honoring x-python-type via _get_python_type_flags. This is needed so titled inline dict schemas can be turned into named aliases via parse_root_type.
  • Ruff reports # noqa: PLR0912, PLR0914, PLR0915 on parse_root_type as unused; you can drop the comment (or the unused codes) to satisfy RUF100.
Proposed lint-only change
-    def parse_root_type(  # noqa: PLR0912, PLR0914, PLR0915
+    def parse_root_type(
         self,
         name: str,
         obj: JsonSchemaObject,
         path: list[str],
     ) -> DataType:
📜 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 94bb621 and 113ec0f.

⛔ Files ignored due to path filters (1)
  • tests/data/jsonschema/use_title_as_name_inline_types.json is excluded by !tests/data/**/*.json and included by none
📒 Files selected for processing (5)
  • docs/cli-reference/field-customization.md
  • src/datamodel_code_generator/parser/jsonschema.py
  • tests/data/expected/main/jsonschema/titles_use_title_as_name.py
  • tests/data/expected/main/jsonschema/use_title_as_name_inline_types.py
  • tests/main/jsonschema/test_main_jsonschema.py
🧰 Additional context used
🧬 Code graph analysis (4)
tests/data/expected/main/jsonschema/use_title_as_name_inline_types.py (1)
src/datamodel_code_generator/model/typed_dict.py (1)
  • TypedDict (49-114)
tests/main/jsonschema/test_main_jsonschema.py (2)
tests/test_main_kr.py (1)
  • output_file (44-46)
tests/main/conftest.py (2)
  • output_file (98-100)
  • run_main_and_assert (244-408)
tests/data/expected/main/jsonschema/titles_use_title_as_name.py (2)
tests/data/expected/parser/openapi/openapi_parser_parse_modular/bar.py (1)
  • Field (6-7)
src/datamodel_code_generator/parser/jsonschema.py (1)
  • parse_obj (3524-3569)
src/datamodel_code_generator/parser/jsonschema.py (1)
src/datamodel_code_generator/parser/base.py (1)
  • data_type (1053-1055)
🪛 Ruff (0.14.10)
tests/data/expected/main/jsonschema/use_title_as_name_inline_types.py

9-9: Cannot use type alias statement on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


12-12: Cannot use type alias statement on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


15-15: Cannot use type alias statement on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


18-18: Cannot use type alias statement on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


21-21: Cannot use type alias statement on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


24-24: Cannot use type alias statement on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)

src/datamodel_code_generator/parser/jsonschema.py

2938-2938: Unused noqa directive (non-enabled: PLR0912, PLR0914, 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). (11)
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.13 on macOS
  • GitHub Check: 3.12 on macOS
  • GitHub Check: py312-isort5 on Ubuntu
  • GitHub Check: 3.10 on macOS
  • GitHub Check: 3.13 on Windows
  • GitHub Check: 3.14 on Windows
  • GitHub Check: benchmarks
  • GitHub Check: Analyze (python)
🔇 Additional comments (3)
tests/data/expected/main/jsonschema/titles_use_title_as_name.py (1)

47-50: Inline union now correctly wrapped in a titled root model

ProcessingStatusUnionTitle as a root model plus using default_factory=lambda: ProcessingStatusUnionTitle.parse_obj('COMPLETED') preserves the previous default semantics while giving the union a reusable, titled type, which aligns with the --use-title-as-name intent. The updated processing_status_union annotation and default look consistent with the schema.

Also applies to: 54-57

docs/cli-reference/field-customization.md (1)

3716-3778: Docs snippet correctly updated to reflect new aliasing behavior

The updated --use-title-as-name example now shows ProcessingStatusUnionTitle and the processing_status_union field using that type, matching the generator’s new behavior and the test expectations.

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

2639-2676: Helper and early short‑circuit correctly centralize title‑based aliasing

The new _should_create_type_alias_for_title plus the early check in parse_item cleanly route titled inline arrays, dicts (additionalProperties only), oneOf/anyOf unions, and literal enums through parse_root_type instead of inlining them. The logic mirrors existing enum/union handling (using _extract_const_enum_from_combined and should_parse_enum_as_literal) and respects ignore_enum_constraints, so behavior for non‑titled or non‑literal enums stays unchanged while fixing the “use-title-as-name ignored for inline types” bug.

Also applies to: 2678-2695

@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 1, 2026

Codecov Report

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

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2889   +/-   ##
=======================================
  Coverage   99.37%   99.37%           
=======================================
  Files          92       92           
  Lines       16121    16152   +31     
  Branches     1898     1906    +8     
=======================================
+ Hits        16020    16051   +31     
  Misses         52       52           
  Partials       49       49           
Flag Coverage Δ
unittests 99.37% <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.

@hgl
Copy link
Copy Markdown

hgl commented Jan 2, 2026

Thanks again for implementing it so quickly. I tried to build this PR myself, and I couldn't make the unit tests pass. It turned out the extra argument used in model_validate is only added in v2.12.5. Not sure what the solution should be.

koxudaxi added a commit that referenced this pull request Jan 2, 2026
The extra='ignore' parameter in model_validate was added in Pydantic v2.12.5.
This change filters fields manually to maintain compatibility with older
Pydantic v2 versions.

Fixes issue reported in #2889 comment.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
koxudaxi added a commit that referenced this pull request Jan 2, 2026
The extra='ignore' parameter in model_validate was added in Pydantic v2.12.5.
This change filters fields manually to maintain compatibility with older
Pydantic v2 versions.

Fixes issue reported in #2889 comment.
@koxudaxi koxudaxi force-pushed the fix/use-title-as-name-inline-types branch 2 times, most recently from 6b95f84 to 0b487bf Compare January 2, 2026 04:18
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)

2938-2938: Remove unused noqa directive.

Static analysis indicates the noqa codes PLR0912, PLR0914, PLR0915 are not triggered for this function. Consider removing the unused directive.

🔎 Proposed fix
-    def parse_root_type(  # noqa: PLR0912, PLR0914, PLR0915
+    def parse_root_type(
📜 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 837ae89 and 0b487bf.

⛔ Files ignored due to path filters (1)
  • tests/data/jsonschema/use_title_as_name_inline_types.json is excluded by !tests/data/**/*.json and included by none
📒 Files selected for processing (7)
  • docs/cli-reference/field-customization.md
  • src/datamodel_code_generator/__init__.py
  • src/datamodel_code_generator/parser/jsonschema.py
  • tests/data/expected/main/jsonschema/titles_use_title_as_name.py
  • tests/data/expected/main/jsonschema/use_title_as_name_inline_types.py
  • tests/data/expected/main/jsonschema/use_title_as_name_inline_types_pydantic.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 (5)
src/datamodel_code_generator/__init__.py (1)
src/datamodel_code_generator/util.py (3)
  • is_pydantic_v2 (52-57)
  • model_dump (254-258)
  • model_validate (261-265)
src/datamodel_code_generator/parser/jsonschema.py (3)
src/datamodel_code_generator/model/base.py (3)
  • name (827-829)
  • nullable (903-905)
  • path (908-910)
src/datamodel_code_generator/parser/graphql.py (1)
  • should_parse_enum_as_literal (231-237)
src/datamodel_code_generator/parser/base.py (1)
  • data_type (1053-1055)
tests/data/expected/main/jsonschema/titles_use_title_as_name.py (2)
tests/data/expected/parser/openapi/openapi_parser_parse_modular/bar.py (1)
  • Field (6-7)
src/datamodel_code_generator/parser/jsonschema.py (1)
  • parse_obj (3524-3569)
tests/data/expected/main/jsonschema/use_title_as_name_inline_types_pydantic.py (3)
src/datamodel_code_generator/model/enum.py (1)
  • Enum (39-121)
tests/data/expected/parser/openapi/openapi_parser_parse_modular/bar.py (1)
  • Field (6-7)
tests/data/expected/main/jsonschema/use_title_as_name_inline_types.py (1)
  • Foo (27-33)
tests/data/expected/main/jsonschema/use_title_as_name_inline_types.py (2)
src/datamodel_code_generator/model/typed_dict.py (1)
  • TypedDict (49-114)
tests/data/expected/main/jsonschema/use_title_as_name_inline_types_pydantic.py (7)
  • MyArrayName (17-18)
  • MyObjectName (21-22)
  • MyEnumName (25-27)
  • MyOneOfName (30-31)
  • MyAnyOfName (34-35)
  • MyOneOfConstName (38-40)
  • Foo (43-49)
🪛 Ruff (0.14.10)
src/datamodel_code_generator/parser/jsonschema.py

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

Remove unused noqa directive

(RUF100)

tests/data/expected/main/jsonschema/use_title_as_name_inline_types.py

9-9: Cannot use type alias statement on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


12-12: Cannot use type alias statement on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


15-15: Cannot use type alias statement on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


18-18: Cannot use type alias statement on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


21-21: Cannot use type alias statement on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)


24-24: Cannot use type alias statement on Python 3.10 (syntax was added in Python 3.12)

(invalid-syntax)

🔇 Additional comments (9)
src/datamodel_code_generator/parser/jsonschema.py (3)

2639-2676: Well-structured helper for determining type alias creation.

The logic correctly identifies the cases where a type alias should be created:

  1. Arrays with title
  2. anyOf/oneOf unions (excluding const-based enums that would be parsed as literals)
  3. Objects with only additionalProperties (dict types)
  4. Enums parsed as literals

The method properly reuses existing methods like _extract_const_enum_from_combined and should_parse_enum_as_literal to maintain consistency with the parsing logic.


2693-2695: LGTM!

The integration point is correctly placed after the title-based name sanitization, ensuring that titled inline types are routed to parse_root_type to generate proper type aliases.


2985-2991: LGTM!

The new dict handling branch correctly extends parse_root_type to handle inline objects with only additionalProperties. This enables proper type alias generation for titled dict types like type MyObjectName = dict[str, str].

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

1-49: LGTM!

The expected output correctly demonstrates the fix for issue #2887. Titled inline types now generate named type aliases:

  • MyArrayName as RootModel[list[str]]
  • MyObjectName as RootModel[dict[str, str]]
  • MyEnumName as Enum
  • MyOneOfName and MyAnyOfName as union RootModels
  • MyOneOfConstName as StrEnum for const-based oneOf

The Foo model correctly references these named types instead of using inline type definitions.

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

9-24: Verify Python version target for type alias syntax.

The type statement syntax (lines 9, 12, 15, 18, 21, 24) is only available in Python 3.12+. This is flagged by static analysis as invalid for Python 3.10.

If this test is intended for Python 3.12+ targets, this is correct. Otherwise, for broader compatibility, the traditional TypeAlias annotation style would be needed:

from typing import TypeAlias
MyArrayName: TypeAlias = list[str]

Please confirm this test targets Python 3.12+ specifically, or if broader compatibility is required.

src/datamodel_code_generator/__init__.py (1)

460-470: LGTM!

The updated Pydantic v2 config creation logic correctly:

  1. Retrieves expected fields from config_class.model_fields.keys()
  2. Filters generate_config.model_dump() to only include parser-relevant fields
  3. Excludes fields already present in additional_options to allow overrides
  4. Merges with additional_options using the | operator

This approach properly handles the field filtering without using the extra argument in model_validate, which addresses the Pydantic v2.12.5 compatibility concern mentioned in the PR comments.

docs/cli-reference/field-customization.md (1)

3762-3772: The example code shown is correct and doesn't require changes. The parse_obj() method on line 3770 is the appropriate Pydantic v1 API, which is what the code generator produces by default. The datamodel-code-generator tool's default output_model_type is PydanticBaseModel (Pydantic v1). When users opt into Pydantic v2 output via --output-model-type pydantic_v2.BaseModel, the code generator automatically generates model_validate() instead. The example demonstrates the default behavior correctly and does not contain a Pydantic API compatibility issue.

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

47-51: LGTM! Wrapper model follows expected pattern.

The new ProcessingStatusUnionTitle model correctly wraps the inline union type using the __root__ pattern, consistent with other wrapper models in the codebase (e.g., Kind, NestedCommentTitle). This achieves the PR objective of creating named type aliases for inline titled schemas.


54-57: Verify parse_obj behavior with union types and string defaults.

The updated field uses a default_factory that calls ProcessingStatusUnionTitle.parse_obj('COMPLETED'). Since ProcessingStatusUnionTitle.__root__ is a union of ProcessingStatusDetail | ExtendedProcessingTask | ProcessingStatusTitle, we need to verify:

  1. That Pydantic v1's parse_obj correctly discriminates the string 'COMPLETED' to the ProcessingStatusTitle enum member (rather than attempting to parse it as the object types).
  2. That this pattern is compatible with both Pydantic v1 and v2 (the PR comments mention a compatibility issue with v2.12.5 regarding model_validate and the extra argument).

Additionally, note that this change modifies the public API—consumers will now receive a ProcessingStatusUnionTitle wrapper instead of the raw union type. Ensure this breaking change is intentional and documented.

Run the following script to verify the behavior:

#!/bin/bash
# Description: Test parse_obj behavior with union types containing enums

# Create a test script to validate parse_obj behavior
cat > /tmp/test_parse_obj.py << 'EOF'
from enum import Enum
from pydantic import BaseModel, Field

class MyEnum(Enum):
    COMPLETED = 'COMPLETED'
    PENDING = 'PENDING'

class MyObject(BaseModel):
    id: int

class WrapperUnion(BaseModel):
    __root__: MyObject | MyEnum

# Test 1: Can parse_obj handle string for enum in union?
try:
    result = WrapperUnion.parse_obj('COMPLETED')
    print(f"✓ parse_obj('COMPLETED') succeeded: {result}")
    print(f"  Root value: {result.__root__}")
    print(f"  Root type: {type(result.__root__)}")
except Exception as e:
    print(f"✗ parse_obj('COMPLETED') failed: {e}")

# Test 2: Can it be used in default_factory?
class Container(BaseModel):
    field: WrapperUnion | None = Field(
        default_factory=lambda: WrapperUnion.parse_obj('COMPLETED')
    )

try:
    instance = Container()
    print(f"\n✓ default_factory succeeded: {instance}")
    print(f"  Field value: {instance.field}")
    print(f"  Field root: {instance.field.__root__ if instance.field else None}")
except Exception as e:
    print(f"\n✗ default_factory failed: {e}")

EOF

# Run with Python and pydantic
python /tmp/test_parse_obj.py

@koxudaxi koxudaxi force-pushed the fix/use-title-as-name-inline-types branch from 0b487bf to f13f7dc Compare January 2, 2026 04:29
…e is enabled

When use_title_as_name is enabled and inline types (array, dict, enum as literal,
oneOf/anyOf unions) have a title, type aliases are now created instead of using
inline types directly.

Also fixes Pydantic v2 compatibility issue where model_validate's extra='ignore'
parameter requires Pydantic v2.12.5+.

Fixes: #2887
@koxudaxi koxudaxi force-pushed the fix/use-title-as-name-inline-types branch from f13f7dc to c4ee616 Compare January 2, 2026 04:34
@koxudaxi
Copy link
Copy Markdown
Owner Author

koxudaxi commented Jan 2, 2026

@hgl
Thanks for catching this! I've updated the implementation to filter fields manually instead of using extra="ignore", so it now works with all Pydantic v2 versions.

@koxudaxi koxudaxi merged commit 1d221da into main Jan 2, 2026
37 of 38 checks passed
@koxudaxi koxudaxi deleted the fix/use-title-as-name-inline-types branch January 2, 2026 05:14
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 2, 2026

Breaking Change Analysis

Result: Breaking changes detected

Reasoning: This PR modifies the generated code output when using the --use-title-as-name flag. The changes include: (1) Union fields with titles now get wrapped in separate named models instead of being inline union types, (2) Inline types like arrays, dicts, and enums with titles now generate named type aliases, and (3) Default value handling changes from simple values to default_factory lambdas for wrapped unions. These are breaking changes because existing code that uses --use-title-as-name will generate different output, potentially breaking downstream code that depends on the previous structure (e.g., code accessing union field values directly without going through a wrapper model's root attribute).

Content for Release Notes

Code Generation Changes

  • Union fields with titles now wrapped in named models when --use-title-as-name is enabled - Previously, union-typed fields with a title were generated as inline union types (e.g., TypeA | TypeB | TypeC | None). Now they generate a separate wrapper model using the title name, and the field references this wrapper type (e.g., ProcessingStatusUnionTitle | None). This affects code that directly accesses union field values, as they now need to access the .root attribute (Pydantic v2) or .__root__ (Pydantic v1) of the wrapper model. (Create type aliases for inline types with title when use-title-as-name is enabled #2889)

    Before:

    class ProcessingTaskTitle(BaseModel):
        processing_status_union: (
            ProcessingStatusDetail | ExtendedProcessingTask | ProcessingStatusTitle | None
        ) = Field('COMPLETED', title='Processing Status Union Title')

    After:

    class ProcessingStatusUnionTitle(BaseModel):
        __root__: (
            ProcessingStatusDetail | ExtendedProcessingTask | ProcessingStatusTitle
        ) = Field(..., title='Processing Status Union Title')
    
    class ProcessingTaskTitle(BaseModel):
        processing_status_union: ProcessingStatusUnionTitle | None = Field(
            default_factory=lambda: ProcessingStatusUnionTitle.parse_obj('COMPLETED'),
            title='Processing Status Union Title',
        )
  • Inline types with titles now generate named type aliases when --use-title-as-name is enabled - Arrays, dicts, enums-as-literals, and oneOf/anyOf unions that have a title in the schema now generate named type aliases or RootModel classes instead of being inlined. This improves readability but changes the generated type structure. For TypedDict output, generates type MyArrayName = list[str]. For Pydantic output, generates class MyArrayName(RootModel[list[str]]). (Create type aliases for inline types with title when use-title-as-name is enabled #2889)

  • Default value handling changed for wrapped union fields - Fields that previously had simple default values now use default_factory with a lambda that calls parse_obj() (Pydantic v1) or model_validate() (Pydantic v2) to construct the wrapper model. Code that introspects field defaults will see a factory function instead of a direct value. (Create type aliases for inline types with title when use-title-as-name is enabled #2889)


This analysis was performed by Claude Code Action

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 2, 2026

🎉 Released in 0.52.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.

use-title-as-name sometimes get ignored

2 participants