Skip to content

Add --use-generic-base-class option for DRY model config#2726

Merged
koxudaxi merged 1 commit intomainfrom
fix/use-generic-base-class
Dec 22, 2025
Merged

Add --use-generic-base-class option for DRY model config#2726
koxudaxi merged 1 commit intomainfrom
fix/use-generic-base-class

Conversation

@koxudaxi
Copy link
Copy Markdown
Owner

@koxudaxi koxudaxi commented Dec 21, 2025

Fixes: #2501

Summary by CodeRabbit

  • New Features

    • Added a CLI/config flag to generate a shared generic base class so generated models can share common configuration (reduces repetition).
  • Documentation

    • CLI reference and quick-reference updated with the new option, usage and examples; documentation counts adjusted. (Duplicate doc blocks were introduced.)
  • Tests

    • Extensive tests added covering shared-base generation across schemas, formats, inheritance and module-splitting. (Some test entries duplicated.)

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 21, 2025

Walkthrough

Adds a new CLI flag --use-generic-base-class, wires it through CLI metadata, Config, public generate() API, and parser constructors; parser can synthesize a shared generic base-class model and update models/imports; model factories for Pydantic v2 and msgspec added; tests and docs for the feature were included (some duplicates).

Changes

Cohort / File(s) Summary
Documentation
docs/cli-reference/index.md, docs/cli-reference/model-customization.md, docs/cli-reference/quick-reference.md
Add --use-generic-base-class to option lists and examples; update counts and wording; duplicate doc blocks present.
CLI / Config
src/datamodel_code_generator/arguments.py, src/datamodel_code_generator/cli_options.py, src/datamodel_code_generator/__main__.py
New CLI option metadata and help text; add Config.use_generic_base_class: bool = False; propagate config flag into generation flow.
Public API
src/datamodel_code_generator/__init__.py
generate() gains use_generic_base_class: bool = False parameter and forwards it to the parser.
Parser core
src/datamodel_code_generator/parser/base.py
Parser accepts use_generic_base_class, stores generic_base_class_config, and adds __apply_generic_base_class(...) to create/insert a shared base-class model, update target models' base_classes and imports; invoked during model processing.
Parser subclasses
src/datamodel_code_generator/parser/jsonschema.py, src/datamodel_code_generator/parser/openapi.py, src/datamodel_code_generator/parser/graphql.py
Constructors extended to accept and forward use_generic_base_class to base Parser.
Model core / factories
src/datamodel_code_generator/model/base.py, src/datamodel_code_generator/model/pydantic_v2/base_model.py, src/datamodel_code_generator/model/msgspec.py, src/datamodel_code_generator/model/enum.py, src/datamodel_code_generator/model/type_alias.py, src/datamodel_code_generator/model/pydantic_v2/imports.py
Add GENERIC_BASE_CLASS_PATH/GENERIC_BASE_CLASS_NAME; introduce DataModel.create_base_class_model(...) API and SUPPORTS_GENERIC_BASE_CLASS flags; implement base-class factory logic for Pydantic v2 and msgspec; add import constant for pydantic BaseModel.
Tests — fixtures & expected
tests/data/jsonschema/*, tests/data/expected/main/jsonschema/use_generic_base_class*.py, tests/data/expected/main/jsonschema/*/*
Add JSON Schema fixtures and many golden expected outputs covering shared base-class generation across scenarios and modes.
Tests — test cases
tests/main/jsonschema/test_main_jsonschema.py, tests/main/conftest.py
Add pytest cases exercising --use-generic-base-class across scenarios; expose EXPECTED_MAIN_PATH; duplicate test blocks present.
sequenceDiagram
    autonumber
    participant CLI as CLI / User
    participant Main as __main__ / generate()
    participant Parser as Parser (JsonSchema/OpenAPI/GraphQL)
    participant Processor as Model Processor
    participant Factory as DataModel.create_base_class_model
    participant Renderer as Code Renderer

    CLI->>Main: invoke with --use-generic-base-class
    Main->>Parser: instantiate Parser(..., use_generic_base_class=True)
    Parser->>Processor: build per-module models and collect config
    Processor->>Factory: request shared base-class creation with collected config
    Factory-->>Processor: return base-class model (or None)
    Processor->>Processor: insert base model, update base_classes & imports
    Processor->>Renderer: render updated modules
    Renderer-->>CLI: write generated files
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Files needing extra attention:
    • src/datamodel_code_generator/parser/base.py — insertion ordering, import adjustments, and correctness of __apply_generic_base_class.
    • src/datamodel_code_generator/model/pydantic_v2/base_model.py and src/datamodel_code_generator/model/msgspec.pycreate_base_class_model implementation, alias/import management.
    • Tests & docs — duplicated blocks and golden files; validate intended single copies and expected outputs.
    • API surface changes: signature additions to generate() and parser constructors.

Possibly related PRs

Suggested labels

safe-to-fix

Suggested reviewers

  • ilovelinux

Poem

🐇 I stitched a base for models to share,
No copied configs left hiding there,
One rabbit class keeps settings in line,
Models inherit tidy and fine,
A tiny hop toward DRY design!

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 'Add --use-generic-base-class option for DRY model config' clearly and concisely summarizes the main change: introducing a new CLI option to avoid repeating model configuration across generated models.
Linked Issues check ✅ Passed The pull request fully implements the core requirement from issue #2501: creating a shared base class with model_config to eliminate duplicate configuration blocks across models, allowing all generated models to inherit from this single base.
Out of Scope Changes check ✅ Passed All changes are within scope: documentation updates reflect the new option, CLI and parser infrastructure wired to support the flag, model generation logic creates and applies the shared base class, and comprehensive tests validate the feature across multiple scenarios.
Docstring Coverage ✅ Passed Docstring coverage is 94.74% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/use-generic-base-class

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.

@codecov
Copy link
Copy Markdown

codecov Bot commented Dec 21, 2025

Codecov Report

❌ Patch coverage is 99.33333% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 99.35%. Comparing base (7f1f7af) to head (40d67fe).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/datamodel_code_generator/parser/base.py 98.61% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2726      +/-   ##
==========================================
- Coverage   99.35%   99.35%   -0.01%     
==========================================
  Files          83       83              
  Lines       11865    12004     +139     
  Branches     1433     1452      +19     
==========================================
+ Hits        11789    11927     +138     
  Misses         45       45              
- Partials       31       32       +1     
Flag Coverage Δ
unittests 99.35% <99.33%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Dec 21, 2025

CodSpeed Performance Report

Merging #2726 will not alter performance

Comparing fix/use-generic-base-class (40d67fe) with main (7f1f7af)

Summary

✅ 70 untouched
⏩ 10 skipped1

Footnotes

  1. 10 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

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 (6)
tests/data/expected/main/jsonschema/use_generic_base_class.py (2)

23-27: Consider optimizing redundant config declarations.

Bar explicitly sets extra='forbid' even though it's already defined in ProjectBaseModel. While this provides clarity, the generator could optimize by omitting model_config when it matches the parent's configuration exactly.

Compare with Baz (lines 30-31), which correctly inherits the base config without redundant declaration.


34-40: Same redundant config pattern as Bar.

Test also explicitly declares extra='forbid' when it matches the ProjectBaseModel default. This is the same optimization opportunity noted for Bar.

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

648-648: Remove unused noqa directive.

Per the static analysis hint, the noqa directive for PLR0912, PLR0913, PLR0915 is no longer needed on this line.

🔎 Proposed fix
-    def __init__(  # noqa: PLR0912, PLR0913, PLR0915
+    def __init__(  # noqa: PLR0913

2042-2059: Consider setting file_path on the generated ProjectBaseModel.

The ProjectBaseModel is created without a file_path, while the generated models in the same module have their file_path set. This inconsistency might cause issues with import resolution or source tracking.

🔎 Proposed fix
             project_base_model = self.data_model_type(
                 reference=project_base_ref,
                 fields=[],
                 custom_template_dir=self.custom_template_dir,
                 extra_template_data=project_extra_data,
                 keyword_only=self.keyword_only,
                 treat_dot_as_module=self.treat_dot_as_module,
+                path=models[0].file_path if models and models[0].file_path else None,
             )
docs/cli-reference/quick-reference.md (1)

100-100: Clarify description; avoid “Test …” phrasing for user docs

Both quick-reference entries describe the option as “Test --use-generic-base-class…”, which reads like an internal test note rather than user-facing help. Consider rephrasing to match the CLI help text, e.g., that it “generates a shared base class with model configuration (e.g., extra='forbid') instead of repeating the configuration in each model, keeping code DRY.”

Also applies to: 264-264

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

31-31: Rephrase --use-generic-base-class docs to be descriptive, not test-oriented

Both the options table row and the section intro describe this as “Test --use-generic-base-class…”, which reads like an internal test case rather than feature documentation. It would be clearer to:

  • Describe what the option does (e.g., “Generate a shared base model (like ProjectBaseModel) that centralizes common model_config so individual models don’t repeat it.”).
  • Optionally note that per-model overrides (like extra='allow' for specific schemas) still generate their own model_config, as shown in the example.

Aligning the wording with the CLI help in arguments.py will make the behavior easier to understand.

Also applies to: 5168-5171

📜 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 8a10962 and 48bd55b.

📒 Files selected for processing (21)
  • docs/cli-reference/index.md (2 hunks)
  • docs/cli-reference/model-customization.md (2 hunks)
  • docs/cli-reference/quick-reference.md (2 hunks)
  • src/datamodel_code_generator/__init__.py (2 hunks)
  • src/datamodel_code_generator/__main__.py (2 hunks)
  • src/datamodel_code_generator/arguments.py (1 hunks)
  • src/datamodel_code_generator/cli_options.py (1 hunks)
  • src/datamodel_code_generator/parser/base.py (5 hunks)
  • src/datamodel_code_generator/parser/graphql.py (2 hunks)
  • src/datamodel_code_generator/parser/jsonschema.py (2 hunks)
  • src/datamodel_code_generator/parser/openapi.py (2 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class.py (1 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_allow_extra.py (1 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_attr_docstrings.py (1 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_dataclass.py (1 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_enum_only.py (1 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_frozen.py (1 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_populate_by_name.py (1 hunks)
  • tests/data/jsonschema/use_generic_base_class_enum_only.json (1 hunks)
  • tests/data/jsonschema/use_generic_base_class_simple.json (1 hunks)
  • tests/main/jsonschema/test_main_jsonschema.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (9)
tests/data/expected/main/jsonschema/use_generic_base_class_dataclass.py (4)
tests/data/expected/main/jsonschema/use_generic_base_class_allow_extra.py (1)
  • Simple (16-17)
tests/data/expected/main/jsonschema/use_generic_base_class_attr_docstrings.py (1)
  • Simple (16-17)
tests/data/expected/main/jsonschema/use_generic_base_class_frozen.py (1)
  • Simple (16-17)
tests/data/expected/main/jsonschema/use_generic_base_class_populate_by_name.py (1)
  • Simple (16-17)
tests/data/expected/main/jsonschema/use_generic_base_class_frozen.py (5)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (26-41)
tests/data/expected/main/jsonschema/use_generic_base_class_allow_extra.py (2)
  • ProjectBaseModel (10-13)
  • Simple (16-17)
tests/data/expected/main/jsonschema/use_generic_base_class_attr_docstrings.py (2)
  • ProjectBaseModel (10-13)
  • Simple (16-17)
tests/data/expected/main/jsonschema/use_generic_base_class_populate_by_name.py (2)
  • ProjectBaseModel (10-13)
  • Simple (16-17)
tests/data/expected/main/jsonschema/use_generic_base_class_dataclass.py (1)
  • Simple (11-12)
tests/data/expected/main/jsonschema/use_generic_base_class_populate_by_name.py (1)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (26-41)
tests/data/expected/main/jsonschema/use_generic_base_class_attr_docstrings.py (1)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (26-41)
tests/data/expected/main/jsonschema/use_generic_base_class_enum_only.py (1)
src/datamodel_code_generator/model/enum.py (1)
  • Enum (39-120)
tests/data/expected/main/jsonschema/use_generic_base_class_allow_extra.py (1)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (26-41)
tests/main/jsonschema/test_main_jsonschema.py (2)
tests/main/conftest.py (2)
  • output_file (98-100)
  • run_main_and_assert (244-408)
tests/test_main_kr.py (1)
  • output_file (44-46)
tests/data/expected/main/jsonschema/use_generic_base_class.py (1)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (26-41)
src/datamodel_code_generator/parser/base.py (2)
src/datamodel_code_generator/model/base.py (4)
  • DataModel (468-737)
  • path (716-718)
  • name (651-653)
  • BaseClassDataType (461-462)
src/datamodel_code_generator/reference.py (3)
  • ModelResolver (492-940)
  • Reference (141-200)
  • reference (76-78)
🪛 Ruff (0.14.8)
src/datamodel_code_generator/parser/base.py

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

10-12: Correct handling for dataclass output.

The absence of a ProjectBaseModel is appropriate since dataclasses don't support Pydantic's ConfigDict. The feature correctly skips base class generation for this output model type.

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

10-17: Well-structured base class pattern.

The ProjectBaseModel correctly centralizes the populate_by_name configuration, and the Simple model inherits it without repeating the config. This demonstrates the DRY principle that the feature aims to achieve.

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

10-17: LGTM!

The frozen configuration is correctly centralized in ProjectBaseModel, demonstrating proper use of the generic base class pattern for immutable models.

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

10-17: LGTM!

The use_attribute_docstrings configuration is properly centralized in the ProjectBaseModel, allowing all derived models to inherit this behavior.

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

10-20: LGTM!

The ProjectBaseModel correctly establishes a shared extra='forbid' configuration, and Foo properly overrides it with extra='allow' to customize its behavior.

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

13-14: Verify necessity of placeholder Model for enum-only schemas.

When the schema contains only enum definitions (no actual object models), the generator creates a generic Model(RootModel[Any]) placeholder. Consider whether this is necessary or if the generator should skip model creation entirely for enum-only schemas.

#!/bin/bash
# Check if this placeholder model generation is pre-existing behavior or new to this PR
# by looking for similar patterns in other test files

echo "=== Searching for other enum-only test cases ==="
fd -e json . tests/data/jsonschema | xargs grep -l '"enum"' | head -5

echo -e "\n=== Checking expected outputs for enum-only patterns ==="
rg -l "class Model\(RootModel\[Any\]\)" tests/data/expected/
src/datamodel_code_generator/parser/base.py (2)

820-851: LGTM!

The conditional routing of configuration options to either generic_base_class_config or extra_template_data[ALL_MODEL] is implemented correctly and follows the existing pattern.


2665-2666: LGTM!

The invocation of __apply_generic_base_class is correctly placed after model processing and before import collection, ensuring the ProjectBaseModel is included in the final output.

src/datamodel_code_generator/__init__.py (1)

406-406: LGTM!

The use_generic_base_class parameter is correctly added to the generate() function signature and properly passed through to the parser instantiation.

Also applies to: 654-654

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

1-17: LGTM!

The expected output correctly demonstrates the --use-generic-base-class feature: ProjectBaseModel centralizes the model_config, and derived classes inherit it without repetition.

src/datamodel_code_generator/cli_options.py (1)

103-103: LGTM!

The CLI option metadata for --use-generic-base-class is correctly added under the Model Customization category, following the established pattern.

docs/cli-reference/index.md (1)

14-14: LGTM!

The documentation index is correctly updated with the incremented option count and the new --use-generic-base-class entry in proper alphabetical order.

Also applies to: 170-170

src/datamodel_code_generator/arguments.py (1)

272-278: New CLI flag wiring looks consistent

The --use-generic-base-class option is added in the right group with clear help text, uses the standard store_true + default=None pattern, and matches the Config field name. This should integrate cleanly with existing arg parsing and pyproject overrides.

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

180-279: OpenAPI parser correctly forwards use_generic_base_class

Adding use_generic_base_class as a keyword-only parameter and passing it through to super().__init__ keeps OpenAPI generation in sync with the base JsonSchema parser’s configuration, without breaking existing call sites.

Also applies to: 282-376

src/datamodel_code_generator/__main__.py (1)

394-394: Config and generator wiring for use_generic_base_class looks correct

The new Config.use_generic_base_class field and its propagation into generate() via run_generate_from_config follow the existing pattern for boolean options. With argparse using default=None, CLI usage will override pyproject config when set, and otherwise respect the config value.

Also applies to: 700-701

tests/main/jsonschema/test_main_jsonschema.py (1)

3715-3841: Excellent test coverage for the new --use-generic-base-class feature!

The test suite comprehensively covers:

  • Basic usage with --extra-fields forbid
  • Interaction with other flags (populate-by-field-name, allow-extra-fields, faux-immutability, attribute-docstrings)
  • Edge cases (dataclasses where the feature has no effect, enum-only schemas)

All tests follow the established patterns in the file and include clear docstrings. The first test includes the @pytest.mark.cli_doc decorator for documentation generation, which is appropriate for a new CLI option.

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

534-534: LGTM: Parameter addition is correct.

The use_generic_base_class parameter is properly typed and has a sensible default value (False) that maintains backward compatibility.


630-630: LGTM: Parameter properly forwarded to parent class.

The parameter is correctly passed to the parent class super().__init__() call, following the same pattern as other configuration parameters.

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

122-122: LGTM: Parameter addition is correct and consistent.

The use_generic_base_class parameter is properly typed with a backward-compatible default value (False), matching the implementation in JsonSchemaParser.


217-217: LGTM: Parameter properly forwarded to parent class.

The parameter forwarding is correct and consistent with the pattern used in JsonSchemaParser, ensuring uniform behavior across parser types.

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

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

648-648: Remove unused noqa directive.

Static analysis indicates the noqa directive for PLR0912, PLR0913, PLR0915 is no longer needed on this line.

🔎 Proposed fix
-    def __init__(  # noqa: PLR0912, PLR0913, PLR0915
+    def __init__(

2025-2025: Consider making the base class name configurable in the future.

The ProjectBaseModel name is currently hardcoded. While acceptable for the initial implementation, users may want to customize this name to match their project conventions.

📜 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 48bd55b and dbde8e2.

📒 Files selected for processing (4)
  • src/datamodel_code_generator/parser/base.py (5 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_with_inheritance.py (1 hunks)
  • tests/data/jsonschema/use_generic_base_class_with_inheritance.json (1 hunks)
  • tests/main/jsonschema/test_main_jsonschema.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/data/expected/main/jsonschema/use_generic_base_class_with_inheritance.py (2)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (26-41)
src/datamodel_code_generator/model/base.py (1)
  • name (651-653)
🪛 Ruff (0.14.8)
src/datamodel_code_generator/parser/base.py

648-648: Unused noqa directive (non-enabled: PLR0912, PLR0913, 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). (10)
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: 3.10 on Ubuntu
  • GitHub Check: py312-black22 on Ubuntu
  • GitHub Check: py312-isort6 on Ubuntu
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.14 on Windows
  • GitHub Check: Analyze (python)
  • GitHub Check: benchmarks
🔇 Additional comments (7)
tests/data/expected/main/jsonschema/use_generic_base_class_with_inheritance.py (1)

12-27: LGTM! Expected output correctly demonstrates the inheritance preservation.

The test fixture properly validates the key behavior of --use-generic-base-class:

  • ProjectBaseModel is generated with the shared model_config
  • Animal inherits only from ProjectBaseModel (no schema-defined parent)
  • Dog correctly inherits from both ProjectBaseModel and Animal, preserving the schema-defined inheritance from the allOf while prepending the generic base
  • Model(RootModel[Any]) is correctly excluded from the transformation
src/datamodel_code_generator/parser/base.py (4)

820-851: LGTM! Config routing logic is clean and consistent.

The conditional routing pattern correctly separates per-model configuration from the shared base class configuration when use_generic_base_class is enabled.


2010-2069: Well-implemented inheritance preservation logic.

The implementation correctly addresses the schema inheritance concern:

  • Line 2065 properly detects schema-defined inheritance by checking any(bc.reference for bc in model.base_classes)
  • Lines 2066-2067 use insert(0, ...) to prepend ProjectBaseModel while preserving existing base classes
  • Lines 2068-2069 only replace when there's no schema inheritance to preserve

This ensures models like Dog (which inherits from Animal via allOf) correctly become Dog(ProjectBaseModel, Animal) rather than losing the Animal parent.


2042-2059: LGTM! ProjectBaseModel creation is properly configured.

The synthetic reference path #/ProjectBaseModel and model instantiation with the collected configuration options are correctly implemented.


2669-2670: LGTM! Method invocation is correctly positioned.

Calling __apply_generic_base_class after model processing but before import finalization ensures the ProjectBaseModel is properly integrated into the module structure.

tests/data/jsonschema/use_generic_base_class_with_inheritance.json (1)

1-28: LGTM! Well-designed test fixture for inheritance scenario.

The JSON Schema correctly uses allOf with $ref to establish the Dog extends Animal inheritance relationship. This provides good coverage for validating that --use-generic-base-class preserves schema-defined inheritance while prepending the shared base class.

tests/main/jsonschema/test_main_jsonschema.py (1)

3715-3858: LGTM! Comprehensive test coverage for the new feature.

The test suite thoroughly validates the --use-generic-base-class feature across multiple scenarios:

  • Basic functionality with various Pydantic v2 config options (forbid, populate by name, allow extra, frozen)
  • Edge cases like enum-only schemas and dataclass compatibility
  • Integration with existing features like attribute docstrings and schema inheritance

The tests follow established patterns in the file and have clear, descriptive docstrings.

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

♻️ Duplicate comments (1)
src/datamodel_code_generator/parser/base.py (1)

2063-2073: The existing review comment appears to be incorrect.

The logic here correctly handles three cases:

  1. Model inherits from another base model (line 2068-2069): Do nothing, because the parent will have ProjectBaseModel in its hierarchy, giving transitive inheritance.
  2. Model has schema-defined parents (line 2070-2071): Use insert(0, project_base_dt) to prepend ProjectBaseModel, preserving existing base classes.
  3. Model has no parents (line 2072-2073): Set ProjectBaseModel as the sole base.

The insert(0, ...) at line 2071 means schema-defined inheritance (e.g., via allOf) is augmented, not replaced. Only models with no parents get replacement (line 2073), which is correct.

The test file confirms this: Dog inherits from Animal, which inherits from ProjectBaseModel. When processing Dog, has_base_model_parent is True (Animal is in the set), so we hit the pass clause and preserve Dog's inheritance from Animal.

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

648-648: Optional cleanup: Remove unused noqa directive.

Ruff reports that the codes in this noqa directive (PLR0912, PLR0913, PLR0915) are not enabled in your linter configuration, making the directive unnecessary.

🔎 Proposed fix
-    def __init__(  # noqa: PLR0912, PLR0913, PLR0915
+    def __init__(
         self,
         source: str | Path | list[Path] | ParseResult,
📜 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 dbde8e2 and a058902.

📒 Files selected for processing (2)
  • src/datamodel_code_generator/parser/base.py (5 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_with_inheritance.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/data/expected/main/jsonschema/use_generic_base_class_with_inheritance.py (2)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (26-41)
src/datamodel_code_generator/model/base.py (1)
  • name (651-653)
🪛 Ruff (0.14.8)
src/datamodel_code_generator/parser/base.py

648-648: Unused noqa directive (non-enabled: PLR0912, PLR0913, 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). (10)
  • GitHub Check: CodeQL
  • GitHub Check: benchmarks
  • GitHub Check: py312-pydantic1 on Ubuntu
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: 3.11 on Ubuntu
  • GitHub Check: py312-isort6 on Ubuntu
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.12 on Windows
  • GitHub Check: Analyze (python)
🔇 Additional comments (3)
tests/data/expected/main/jsonschema/use_generic_base_class_with_inheritance.py (1)

22-27: LGTM! The inheritance hierarchy is correct.

The inheritance chain Dog -> Animal -> ProjectBaseModel -> BaseModel is valid and won't cause MRO issues. The GitHub Advanced Security warning appears to be a false positive—there's no multiple inheritance or diamond problem here that could break method resolution order.

The design correctly demonstrates the feature: Animal gets ProjectBaseModel as its base (which carries the shared config), and Dog inherits from Animal, transitively receiving the config without needing to reference ProjectBaseModel directly.

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

648-671: LGTM! Clean feature flag integration.

The constructor changes properly introduce the use_generic_base_class flag with backward-compatible defaults, and the config routing logic (lines 823-851) consistently channels model configuration to either the shared generic_base_class_config or per-model extra_template_data based on the flag.

Also applies to: 820-851


2673-2673: LGTM! Correct placement in the processing pipeline.

The call to __apply_generic_base_class is correctly positioned after all model processing (deduplication, sorting, inheritance resolution) but before final code generation. This ensures the shared base class is available when imports and final output are rendered.

@koxudaxi koxudaxi force-pushed the fix/use-generic-base-class branch 3 times, most recently from 1910851 to 76d207b Compare December 21, 2025 12:44
Comment thread src/datamodel_code_generator/model/base.py Dismissed
Comment thread src/datamodel_code_generator/model/base.py Dismissed
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 (5)
src/datamodel_code_generator/model/base.py (1)

712-726: Clean template method pattern for base class factory.

The method correctly returns None by default, allowing subclasses to override with their implementation. The ARG003 noqa directives are appropriate since these parameters are intentionally unused in the base class.

However, the FBT001 and FBT002 noqa directives on lines 718-719 are unused per Ruff since these are keyword-only parameters (after * in signature would make them positional-only restricted, but here they have defaults making them keyword arguments).

🔎 Remove unused noqa directives
     @classmethod
     def create_base_class_model(
         cls,
         config: dict[str, Any],  # noqa: ARG003
         reference: Reference,  # noqa: ARG003
         custom_template_dir: Path | None = None,  # noqa: ARG003
-        keyword_only: bool = False,  # noqa: ARG003, FBT001, FBT002
-        treat_dot_as_module: bool = False,  # noqa: ARG003, FBT001, FBT002
+        keyword_only: bool = False,  # noqa: ARG003
+        treat_dot_as_module: bool = False,  # noqa: ARG003
     ) -> tuple[DataModel, str] | None:
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)

276-313: Well-implemented generic base class factory.

The implementation correctly:

  1. Creates a BaseModel with configuration from ALL_MODEL template data
  2. Aliases pydantic.BaseModel as _BaseModel to avoid name collision with the generated class
  3. Filters existing imports and adds the aliased import

Minor: The FBT001/FBT002 noqa directives on lines 282-283 are unused per Ruff.

🔎 Remove unused noqa directives
     @classmethod
     def create_base_class_model(
         cls,
         config: dict[str, Any],
         reference: Reference,
         custom_template_dir: Path | None = None,
-        keyword_only: bool = False,  # noqa: FBT001, FBT002
-        treat_dot_as_module: bool = False,  # noqa: FBT001, FBT002
+        keyword_only: bool = False,
+        treat_dot_as_module: bool = False,
     ) -> tuple[BaseModel, str] | None:
src/datamodel_code_generator/parser/base.py (3)

2060-2061: Consider adding a clarifying comment for the pass statement.

The pass on line 2061 is intentional (transitive inheritance), but a brief comment would improve readability for future maintainers.

🔎 Add clarifying comment
                 if has_target_model_parent:
-                    pass
+                    pass  # Base class will be inherited through the parent model
                 elif parent_refs:

2055-2065: Inheritance logic correctly preserves schema-defined parents.

The three-case logic is sound:

  1. Model already inherits from another target model → skip (base class inherited transitively)
  2. Model has other parent refs → prepend base class (preserves schema inheritance)
  3. Model has no parents → set base class as sole parent

One minor note: base_class_dt is shared across all target models. Currently this is safe since BaseClassDataType with only type set has no parent relationships that would cross-contaminate models. However, future changes that introduce parent tracking on base class types could make this problematic. Creating a new instance per model would be more defensive.


2046-2047: Consider logging a warning when --use-generic-base-class is used with unsupported model types.

The feature currently has no effect for dataclasses and other non-Pydantic v2 model types, but users receive no feedback about this. While the silent return is intentional design (as evidenced by tests like test_main_use_generic_base_class_dataclass), adding a warning would improve user experience. The codebase already uses the warnings.warn() pattern in similar scenarios (e.g., openapi.py, jsonschema.py), so adding one here would be consistent.

📜 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 a058902 and 76d207b.

📒 Files selected for processing (17)
  • docs/cli-reference/model-customization.md (2 hunks)
  • docs/cli-reference/quick-reference.md (2 hunks)
  • src/datamodel_code_generator/arguments.py (1 hunks)
  • src/datamodel_code_generator/model/base.py (2 hunks)
  • src/datamodel_code_generator/model/pydantic_v2/base_model.py (2 hunks)
  • src/datamodel_code_generator/parser/base.py (6 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class.py (1 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_allow_extra.py (1 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_attr_docstrings.py (1 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_frozen.py (1 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_module_split/__init__.py (1 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_module_split/animal.py (1 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_module_split/dog.py (1 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_module_split/model.py (1 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_populate_by_name.py (1 hunks)
  • tests/data/expected/main/jsonschema/use_generic_base_class_with_inheritance.py (1 hunks)
  • tests/main/jsonschema/test_main_jsonschema.py (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tests/data/expected/main/jsonschema/use_generic_base_class_module_split/init.py
🚧 Files skipped from review as they are similar to previous changes (7)
  • docs/cli-reference/quick-reference.md
  • docs/cli-reference/model-customization.md
  • tests/data/expected/main/jsonschema/use_generic_base_class_with_inheritance.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_attr_docstrings.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_frozen.py
  • src/datamodel_code_generator/arguments.py
  • tests/data/expected/main/jsonschema/use_generic_base_class.py
🧰 Additional context used
🧬 Code graph analysis (2)
src/datamodel_code_generator/model/pydantic_v2/base_model.py (2)
src/datamodel_code_generator/model/base.py (3)
  • imports (283-302)
  • imports (637-642)
  • BaseClassDataType (463-464)
src/datamodel_code_generator/imports.py (1)
  • Import (20-38)
tests/data/expected/main/jsonschema/use_generic_base_class_populate_by_name.py (2)
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)
  • BaseModel (170-314)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (26-41)
🪛 GitHub Check: CodeQL
src/datamodel_code_generator/model/base.py

[notice] 52-52: Unused global variable
The global variable 'GENERIC_BASE_CLASS_PATH' is not used.


[notice] 53-53: Unused global variable
The global variable 'GENERIC_BASE_CLASS_NAME' is not used.

🪛 Ruff (0.14.8)
src/datamodel_code_generator/model/pydantic_v2/base_model.py

282-282: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)


283-283: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/model/base.py

718-718: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)


719-719: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/parser/base.py

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

1-18: LGTM! Test expectation correctly demonstrates the DRY pattern.

This expected output file correctly demonstrates the new --use-generic-base-class feature:

  1. Proper aliasing (line 7): Pydantic's BaseModel is imported as _BaseModel to avoid naming conflicts with the custom base class.
  2. Centralized configuration (lines 11-14): The custom BaseModel defines model_config once with populate_by_name=True.
  3. DRY inheritance (lines 17-18): The Simple model inherits from the custom BaseModel and automatically receives the configuration without repeating the model_config block.

This achieves the objective from issue #2501 of eliminating repeated model_config blocks across generated models.

tests/data/expected/main/jsonschema/use_generic_base_class_allow_extra.py (4)

1-4: LGTM! Metadata header is correct.

The generated file header correctly documents the generator, input schema filename, and timestamp.


5-8: LGTM! Import structure correctly supports Pydantic v2.

The imports properly alias _BaseModel to avoid collision with the custom BaseModel class, and use Pydantic v2's ConfigDict pattern.


17-18: LGTM! Demonstrates the DRY benefit of the feature.

The Simple class correctly inherits from the custom BaseModel, gaining the shared model_config without repetition. This validates the PR objective of eliminating redundant configuration blocks.


11-14: LGTM! Correctly implements shared base class with DRY pattern.

The BaseModel class correctly uses Pydantic v2's model_config = ConfigDict(extra='allow') pattern, which allows additional fields on model instances. By defining this configuration once in the shared base class, all inheriting models avoid repeating the configuration.

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

52-53: LGTM! Constants for generic base class feature.

These constants are properly imported and used in src/datamodel_code_generator/parser/base.py. The CodeQL warning about unused globals is a false positive at the file level since these are exported for use elsewhere.

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

822-853: Config routing for generic base class is correct.

The conditional logic properly routes configuration options to either generic_base_class_config (when the feature is enabled) or extra_template_data[ALL_MODEL] (legacy behavior). This ensures backward compatibility while enabling the DRY pattern.

tests/main/jsonschema/test_main_jsonschema.py (1)

3716-3878: LGTM! Comprehensive test coverage for the new feature.

The nine new test functions provide thorough coverage of the --use-generic-base-class feature across different scenarios:

  • Basic usage with extra='forbid'
  • Interaction with other flags (populate_by_name, allow_extra, frozen, attribute_docstrings)
  • Different output types (dataclass)
  • Edge cases (enum-only schemas, inheritance, module-split)

The tests are well-structured, properly documented with cli_doc markers, and follow the existing test patterns consistently.

tests/data/expected/main/jsonschema/use_generic_base_class_module_split/dog.py (1)

13-16: This appears to be intentional design for module-split mode, not a violation of DRY principles

The duplicate BaseModel definitions in dog.py and animal.py are expected in module-split mode. The test docstring confirms this is intentional: "Test --use-generic-base-class with module split mode (cross-module inheritance)." When modules are split, each module defines its own base class to remain self-contained. Unlike the non-split version which uses a single BaseModel, this module-split pattern trades DRY compliance for module independence.

The alternative of importing BaseModel from animal.py in dog.py would reduce duplication but would create a cross-module dependency that conflicts with the intended module-split design goal.

@koxudaxi koxudaxi force-pushed the fix/use-generic-base-class branch from 76d207b to 4983a03 Compare December 21, 2025 12:57
Comment thread src/datamodel_code_generator/model/msgspec.py Dismissed
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

♻️ Duplicate comments (2)
src/datamodel_code_generator/model/base.py (1)

52-53: Unused constants flagged by static analysis.

GENERIC_BASE_CLASS_PATH and GENERIC_BASE_CLASS_NAME are defined but not used in this file. If these are intended for use elsewhere, ensure they're imported and used appropriately. Otherwise, consider removing them.

#!/bin/bash
# Check if these constants are used anywhere in the codebase
echo "=== Searching for GENERIC_BASE_CLASS_PATH usage ==="
rg -n "GENERIC_BASE_CLASS_PATH" --type py

echo ""
echo "=== Searching for GENERIC_BASE_CLASS_NAME usage ==="
rg -n "GENERIC_BASE_CLASS_NAME" --type py
src/datamodel_code_generator/model/msgspec.py (1)

23-23: Cyclic import already flagged by static analysis.

This cyclic import pattern exists and has been previously noted. The import is required for the BaseClassDataType usage in the new create_base_class_model method.

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

713-727: LGTM with optional cleanup.

The base implementation correctly provides a default None return, allowing model-specific subclasses to override with their implementations. The method signature appropriately matches the concrete implementations in pydantic_v2/base_model.py and msgspec.py.

Optional: Remove unused noqa directives

Ruff indicates the FBT001, FBT002 noqa directives on lines 719-720 are unused (likely the rules aren't enabled). You could simplify to just ARG003:

-        keyword_only: bool = False,  # noqa: ARG003, FBT001, FBT002
-        treat_dot_as_module: bool = False,  # noqa: ARG003, FBT001, FBT002
+        keyword_only: bool = False,  # noqa: ARG003
+        treat_dot_as_module: bool = False,  # noqa: ARG003
src/datamodel_code_generator/model/msgspec.py (2)

163-164: Remove unused noqa directives.

Static analysis indicates the FBT001 and FBT002 rules are not enabled, making these noqa directives unnecessary.

🔎 Proposed fix
-        keyword_only: bool = False,  # noqa: FBT001, FBT002
-        treat_dot_as_module: bool = False,  # noqa: FBT001, FBT002
+        keyword_only: bool = False,
+        treat_dot_as_module: bool = False,

165-203: Return type includes None but the method never returns None.

The return type is declared as tuple[Struct, str] | None, but the method always returns (base_model, base_class_name). Either simplify the return type to tuple[Struct, str], or add documentation explaining when None would be returned (e.g., for future extensibility or consistency with a protocol).

🔎 Proposed simplification

If None is not expected, simplify the return type:

     ) -> tuple[Struct, str] | None:
+    ) -> tuple[Struct, str]:

Alternatively, if the | None is intentional for interface consistency with other implementations, consider adding a docstring note explaining this.

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

282-283: Remove unused noqa directives.

Static analysis indicates the FBT001 and FBT002 rules are not enabled, making these noqa directives unnecessary.

🔎 Proposed fix
-        keyword_only: bool = False,  # noqa: FBT001, FBT002
-        treat_dot_as_module: bool = False,  # noqa: FBT001, FBT002
+        keyword_only: bool = False,
+        treat_dot_as_module: bool = False,

284-284: Return type includes None but the method never returns None.

Same as in msgspec.py: the return type is tuple[BaseModel, str] | None, but the method always returns a tuple. Consider simplifying to tuple[BaseModel, str] unless None is intentionally reserved for future use or protocol conformance.

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

650-650: Consider removing the unused noqa directive.

The static analysis tool indicates that the suppression codes PLR0912, PLR0913, PLR0915 are not currently enabled in your linter configuration.

🔎 Cleanup suggestion
-    def __init__(  # noqa: PLR0912, PLR0913, PLR0915
+    def __init__(
📜 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 76d207b and 00008bc.

📒 Files selected for processing (12)
  • docs/cli-reference/model-customization.md
  • docs/cli-reference/quick-reference.md
  • src/datamodel_code_generator/arguments.py
  • src/datamodel_code_generator/model/base.py
  • src/datamodel_code_generator/model/enum.py
  • src/datamodel_code_generator/model/msgspec.py
  • src/datamodel_code_generator/model/pydantic_v2/base_model.py
  • src/datamodel_code_generator/model/type_alias.py
  • src/datamodel_code_generator/parser/base.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_msgspec.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_msgspec_forbid.py
  • tests/main/jsonschema/test_main_jsonschema.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/cli-reference/quick-reference.md
  • src/datamodel_code_generator/arguments.py
🧰 Additional context used
🧬 Code graph analysis (2)
src/datamodel_code_generator/model/base.py (3)
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)
  • create_base_class_model (277-314)
src/datamodel_code_generator/model/msgspec.py (1)
  • create_base_class_model (158-203)
src/datamodel_code_generator/reference.py (2)
  • reference (76-78)
  • Reference (141-200)
tests/main/jsonschema/test_main_jsonschema.py (1)
tests/main/conftest.py (2)
  • output_file (98-100)
  • output_dir (104-106)
🪛 GitHub Check: CodeQL
src/datamodel_code_generator/model/base.py

[notice] 52-52: Unused global variable
The global variable 'GENERIC_BASE_CLASS_PATH' is not used.


[notice] 53-53: Unused global variable
The global variable 'GENERIC_BASE_CLASS_NAME' is not used.

src/datamodel_code_generator/model/msgspec.py

[notice] 23-23: Cyclic import
Import of module datamodel_code_generator.model.base begins an import cycle.

🪛 Ruff (0.14.8)
src/datamodel_code_generator/model/pydantic_v2/base_model.py

282-282: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)


283-283: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/parser/base.py

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

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/model/base.py

719-719: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)


720-720: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/model/msgspec.py

163-163: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)


164-164: Unused noqa directive (non-enabled: FBT001, FBT002)

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). (10)
  • GitHub Check: 3.12 on macOS
  • GitHub Check: py312-black23 on Ubuntu
  • GitHub Check: py312-isort5 on Ubuntu
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.11 on Windows
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.13 on Windows
  • GitHub Check: Analyze (python)
  • GitHub Check: benchmarks
🔇 Additional comments (10)
src/datamodel_code_generator/model/enum.py (1)

45-45: LGTM! The flag correctly indicates Enum doesn't support generic base classes.

Setting SUPPORTS_GENERIC_BASE_CLASS = False is correct because Enum types have a fixed inheritance hierarchy (must inherit from enum.Enum or its variants) and cannot participate in the generic base class pattern. The attribute is consistently defined across DataModel subclasses—the base class defaults to True, while special types like Enum and TypeAlias that cannot use the generic pattern override it to False. The parser properly checks this flag to filter which models receive the generic base class treatment.

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

24-25: LGTM!

The addition of SUPPORTS_GENERIC_BASE_CLASS: ClassVar[bool] = False correctly indicates that type aliases don't support the generic base class feature, which makes sense since they're aliases rather than model classes.

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

1-19: LGTM!

The expected output correctly demonstrates the generic base class pattern for msgspec: a shared Struct base class with frozen=True configuration, and Simple inheriting from it without repeating the configuration.

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

1-19: LGTM!

The expected output correctly demonstrates the generic base class pattern for msgspec with both forbid_unknown_fields=True and frozen=True configurations consolidated in the shared Struct base class.

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

479-480: LGTM!

The addition of SUPPORTS_GENERIC_BASE_CLASS: ClassVar[bool] = True as default for DataModel provides a clean extension point. Model types that don't support generic base classes (like TypeAliasBase) can override this to False.

tests/main/jsonschema/test_main_jsonschema.py (3)

30-30: LGTM: New import for test path constant.

The EXPECTED_MAIN_PATH import is correctly added to support the module split test at line 3877.


3716-3737: Well-structured CLI documentation test for the new feature.

The test properly uses the @pytest.mark.cli_doc decorator for documentation and follows the established pattern for testing CLI options with run_main_and_assert.


3862-3878: Good coverage for module split mode with generic base class.

This test verifies the cross-module inheritance scenario, which is a critical edge case for the DRY base class feature when models are split across multiple files.

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

10-10: LGTM: Import reorganization for new functionality.

Moving defaultdict to runtime imports and adding Import, ALL_MODEL, UNDEFINED, and BaseClassDataType supports the new create_base_class_model method.

Also applies to: 16-17


306-312: Well-implemented import aliasing to avoid name conflicts.

The logic correctly:

  1. Filters out existing pydantic.BaseModel imports
  2. Adds the aliased import as _BaseModel

This ensures the generated base class inherits from the aliased import without circular naming issues.

Comment thread docs/cli-reference/model-customization.md Outdated
Comment thread docs/cli-reference/model-customization.md Outdated
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: 0

♻️ Duplicate comments (1)
src/datamodel_code_generator/parser/base.py (1)

2049-2057: Cross-module inheritance may create redundant base classes.

When a model in one module inherits from a model in another module and both modules apply the generic base class, the child ends up with duplicate inheritance. For example, if Dog (module2) inherits from Animal (module1):

  • module1 generates: BaseModel, Animal(BaseModel)
  • module2 generates: BaseModel, Dog(BaseModel, Animal)

Result: Dog inherits from both module2.BaseModel and Animal (which already inherits from module1.BaseModel), creating redundant and potentially confusing inheritance.

Root cause: Line 2051 checks has_target_model_parent = any(ref.source in target_model_set ...) which only considers parents within the current module. It does not check whether any ancestor from another module already has the generic base class applied.

Suggested fix: Before adding the generic base at line 2055, check whether any ancestor (transitively, across modules) already inherits from a model with a name matching the generic base class (e.g., "BaseModel" or "Struct"). If so, skip adding it to avoid duplication.

Note: This issue was previously flagged in past reviews; confirming it remains unresolved in the current implementation.

🧹 Nitpick comments (4)
src/datamodel_code_generator/model/msgspec.py (1)

163-164: Remove unused noqa directives.

Ruff reports that FBT001 and FBT002 are not enabled in your configuration, making these suppression comments unnecessary.

🔎 Cleanup suggestion
-    def create_base_class_model(
-        cls,
-        config: dict[str, Any],
-        reference: Reference,
-        custom_template_dir: Path | None = None,
-        keyword_only: bool = False,  # noqa: FBT001, FBT002
-        treat_dot_as_module: bool = False,  # noqa: FBT001, FBT002
-    ) -> Struct | None:
+    def create_base_class_model(
+        cls,
+        config: dict[str, Any],
+        reference: Reference,
+        custom_template_dir: Path | None = None,
+        keyword_only: bool = False,
+        treat_dot_as_module: bool = False,
+    ) -> Struct | None:
src/datamodel_code_generator/model/base.py (1)

719-720: Remove unused noqa directives.

The FBT001 and FBT002 suppressions are unnecessary since these rules are not enabled.

🔎 Cleanup suggestion
     @classmethod
     def create_base_class_model(
         cls,
         config: dict[str, Any],  # noqa: ARG003
         reference: Reference,  # noqa: ARG003
         custom_template_dir: Path | None = None,  # noqa: ARG003
-        keyword_only: bool = False,  # noqa: ARG003, FBT001, FBT002
-        treat_dot_as_module: bool = False,  # noqa: ARG003, FBT001, FBT002
+        keyword_only: bool = False,  # noqa: ARG003
+        treat_dot_as_module: bool = False,  # noqa: ARG003
     ) -> DataModel | None:
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)

282-283: Remove unused noqa directives.

The FBT001 and FBT002 suppressions are not needed.

🔎 Cleanup suggestion
     @classmethod
     def create_base_class_model(
         cls,
         config: dict[str, Any],
         reference: Reference,
         custom_template_dir: Path | None = None,
-        keyword_only: bool = False,  # noqa: FBT001, FBT002
-        treat_dot_as_module: bool = False,  # noqa: FBT001, FBT002
+        keyword_only: bool = False,
+        treat_dot_as_module: bool = False,
     ) -> BaseModel | None:
src/datamodel_code_generator/parser/base.py (1)

650-650: Remove unused noqa directive.

Ruff indicates the PLR0912, PLR0913, and PLR0915 suppressions are not necessary.

🔎 Cleanup suggestion
-    def __init__(  # noqa: PLR0912, PLR0913, PLR0915
+    def __init__(
         self,
         source: str | Path | list[Path] | ParseResult,
📜 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 00008bc and 4ad628b.

📒 Files selected for processing (4)
  • src/datamodel_code_generator/model/base.py
  • src/datamodel_code_generator/model/msgspec.py
  • src/datamodel_code_generator/model/pydantic_v2/base_model.py
  • src/datamodel_code_generator/parser/base.py
🧰 Additional context used
🧬 Code graph analysis (3)
src/datamodel_code_generator/parser/base.py (2)
src/datamodel_code_generator/model/msgspec.py (1)
  • create_base_class_model (158-206)
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)
  • create_base_class_model (277-317)
src/datamodel_code_generator/model/msgspec.py (5)
src/datamodel_code_generator/model/base.py (4)
  • BaseClassDataType (463-464)
  • create_base_class_model (714-727)
  • path (735-737)
  • name (654-656)
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)
  • create_base_class_model (277-317)
src/datamodel_code_generator/reference.py (3)
  • reference (76-78)
  • Reference (141-200)
  • get (831-833)
src/datamodel_code_generator/model/typed_dict.py (1)
  • key (125-129)
src/datamodel_code_generator/imports.py (2)
  • append (74-89)
  • Import (20-38)
src/datamodel_code_generator/model/base.py (3)
src/datamodel_code_generator/model/msgspec.py (1)
  • create_base_class_model (158-206)
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)
  • create_base_class_model (277-317)
src/datamodel_code_generator/reference.py (2)
  • reference (76-78)
  • Reference (141-200)
🪛 GitHub Check: CodeQL
src/datamodel_code_generator/model/msgspec.py

[notice] 23-23: Cyclic import
Import of module datamodel_code_generator.model.base begins an import cycle.

src/datamodel_code_generator/model/base.py

[notice] 52-52: Unused global variable
The global variable 'GENERIC_BASE_CLASS_PATH' is not used.


[notice] 53-53: Unused global variable
The global variable 'GENERIC_BASE_CLASS_NAME' is not used.

🪛 Ruff (0.14.8)
src/datamodel_code_generator/model/pydantic_v2/base_model.py

282-282: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)


283-283: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/parser/base.py

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

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/model/msgspec.py

163-163: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)


164-164: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/model/base.py

719-719: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)


720-720: Unused noqa directive (non-enabled: FBT001, FBT002)

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). (2)
  • GitHub Check: benchmarks
  • GitHub Check: 3.10 on Windows
🔇 Additional comments (7)
src/datamodel_code_generator/model/msgspec.py (2)

157-206: LGTM – Msgspec base class factory is well-structured.

The method correctly creates a generic Struct base class with aliased imports to avoid naming conflicts, and the config mapping appropriately translates Pydantic-style options to msgspec equivalents while ignoring unsupported options.


23-23: Remove the cyclic import concern—no import cycle exists.

The import at line 23 is unidirectional: msgspec.py imports from base.py, while base.py contains no imports of msgspec or other model submodules. This dependency structure is clean and poses no initialization risk.

Likely an incorrect or invalid review comment.

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

52-53: False positive: Constants are used in parser/base.py.

Static analysis flags these as unused because it doesn't track cross-module imports. Lines 57-58 in src/datamodel_code_generator/parser/base.py import both constants.


479-480: LGTM – Clean extension mechanism for generic base class support.

Converting IS_ALIAS to a ClassVar and adding SUPPORTS_GENERIC_BASE_CLASS provides a clear, type-safe way for subclasses to declare capabilities. The default create_base_class_model returning None allows model types to opt in gracefully.

Also applies to: 713-727

src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)

276-317: LGTM – Pydantic v2 base class generation is solid.

The method correctly creates a ConfigDict-based BaseModel with aliased imports, and the import-filtering logic at lines 310-314 prevents conflicts between the aliased _BaseModel and any existing BaseModel imports.

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

822-853: LGTM – Config routing logic correctly isolates generic base class settings.

The conditional checks cleanly divert shared configuration options into generic_base_class_config when the feature is enabled, preventing them from being applied per-model via ALL_MODEL scope.


2012-2057: Approve with caveat: Core generic base class application is sound, but cross-module inheritance needs refinement.

The method correctly creates a shared base class model, inserts it at the start of each module's model list, and updates target models to inherit from it. The logic at lines 2052-2057 appropriately skips adding the base when a parent in the same module already has it. However, the cross-module inheritance concern noted above should be addressed to avoid redundant base classes in multi-module scenarios.

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 (4)
src/datamodel_code_generator/parser/base.py (3)

650-650: Remove unused noqa directive codes.

The noqa directive includes codes PLR0912, PLR0913, and PLR0915 that are not actually triggered by linters, as indicated by static analysis.

🔎 Proposed fix
-    def __init__(  # noqa: PLR0912, PLR0913, PLR0915
+    def __init__(

825-854: Consider extracting the repeated conditional routing pattern.

The same pattern appears five times for different config keys. While functional, this could be refactored into a helper method for better maintainability.

🔎 Proposed refactor
+    def _route_config(self, key: str, value: Any) -> None:
+        """Route configuration to generic base class config or per-model template data."""
+        if self.use_generic_base_class:
+            self.generic_base_class_config[key] = value
+        else:
+            self.extra_template_data[ALL_MODEL][key] = value
+
     if allow_population_by_field_name:
-        if use_generic_base_class:
-            self.generic_base_class_config["allow_population_by_field_name"] = True
-        else:
-            self.extra_template_data[ALL_MODEL]["allow_population_by_field_name"] = True
+        self._route_config("allow_population_by_field_name", True)

     if allow_extra_fields:
-        if use_generic_base_class:
-            self.generic_base_class_config["allow_extra_fields"] = True
-        else:
-            self.extra_template_data[ALL_MODEL]["allow_extra_fields"] = True
+        self._route_config("allow_extra_fields", True)
 
     if extra_fields:
-        if use_generic_base_class:
-            self.generic_base_class_config["extra_fields"] = extra_fields
-        else:
-            self.extra_template_data[ALL_MODEL]["extra_fields"] = extra_fields
+        self._route_config("extra_fields", extra_fields)
 
     if enable_faux_immutability:
-        if use_generic_base_class:
-            self.generic_base_class_config["allow_mutation"] = False
-        else:
-            self.extra_template_data[ALL_MODEL]["allow_mutation"] = False
+        self._route_config("allow_mutation", False)
 
     if use_attribute_docstrings:
-        if use_generic_base_class:
-            self.generic_base_class_config["use_attribute_docstrings"] = True
-        else:
-            self.extra_template_data[ALL_MODEL]["use_attribute_docstrings"] = True
+        self._route_config("use_attribute_docstrings", True)

2012-2012: Remove unused noqa directive.

Static analysis indicates PLR0912 is not triggered.

🔎 Proposed fix
-    def __apply_generic_base_class(  # noqa: PLR0912
+    def __apply_generic_base_class(
tests/main/jsonschema/test_main_jsonschema.py (1)

3716-3935: Consider adding unit tests for base class generation logic.

While the integration tests provide excellent end-to-end validation, consider supplementing them with unit tests that directly test the base class generation logic. This would provide:

  • Faster test execution
  • More granular coverage of edge cases
  • Easier debugging when tests fail

For example, unit tests could verify:

  • Correct model_config generation for different options
  • Proper inheritance chain setup
  • Base class naming and import handling
📜 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 8a96a8e and 4bd4de8.

📒 Files selected for processing (9)
  • src/datamodel_code_generator/parser/base.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/__init__.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/animal.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/dog.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/mammal.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/model.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_module_split/dog.py
  • tests/data/jsonschema/use_generic_base_class_deep_inheritance.json
  • tests/main/jsonschema/test_main_jsonschema.py
✅ Files skipped from review due to trivial changes (1)
  • tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/init.py
🧰 Additional context used
🧬 Code graph analysis (5)
tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/animal.py (3)
src/datamodel_code_generator/reference.py (1)
  • _BaseModel (81-138)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (26-41)
src/datamodel_code_generator/model/base.py (1)
  • name (654-656)
tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/dog.py (2)
tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/mammal.py (1)
  • Mammal (10-11)
tests/data/expected/main/jsonschema/use_generic_base_class_module_split/dog.py (1)
  • Dog (10-11)
src/datamodel_code_generator/parser/base.py (3)
src/datamodel_code_generator/model/base.py (5)
  • DataModel (470-756)
  • path (735-737)
  • name (654-656)
  • create_base_class_model (714-727)
  • BaseClassDataType (463-464)
src/datamodel_code_generator/model/msgspec.py (1)
  • create_base_class_model (158-206)
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)
  • create_base_class_model (277-317)
tests/data/expected/main/jsonschema/use_generic_base_class_module_split/dog.py (2)
tests/data/expected/main/jsonschema/use_generic_base_class_module_split/animal.py (1)
  • Animal (17-18)
tests/data/expected/main/jsonschema/use_generic_base_class_with_inheritance.py (2)
  • Animal (23-24)
  • Dog (27-28)
tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/mammal.py (1)
tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/animal.py (1)
  • Animal (17-18)
🪛 Ruff (0.14.8)
src/datamodel_code_generator/parser/base.py

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

Remove unused noqa directive

(RUF100)


2012-2012: Unused noqa directive (non-enabled: PLR0912)

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). (10)
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.10 on Ubuntu
  • GitHub Check: 3.11 on Windows
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: 3.12 on Ubuntu
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.13 on Windows
  • GitHub Check: 3.14 on Ubuntu
  • GitHub Check: benchmarks
  • GitHub Check: Analyze (python)
🔇 Additional comments (14)
tests/data/expected/main/jsonschema/use_generic_base_class_module_split/dog.py (3)

5-7: LGTM: Import structure is correct.

The from __future__ import annotations enables modern union type syntax, and the relative import from .animal correctly reflects the module split layout.


10-11: LGTM: Inheritance pattern correctly demonstrates the feature.

The Dog class correctly inherits from Animal (which inherits from BaseModel with model_config), achieving the DRY objective of the --use-generic-base-class feature by avoiding repeated configuration blocks.


1-3: No action needed. The generation metadata is correct.

The test configuration confirms that use_generic_base_class_with_inheritance.json is the correct input schema file for this test case. The output directory is named use_generic_base_class_module_split to reflect the test mode (module split with --module-split-mode single), not the input filename. The generation header accurately identifies the source schema file.

Likely an incorrect or invalid review comment.

tests/data/jsonschema/use_generic_base_class_deep_inheritance.json (1)

1-43: LGTM! Clean test schema for deep inheritance.

The JSON schema correctly establishes a three-level inheritance chain (Animal → Mammal → Dog) using allOf references, which effectively tests the generic base class feature with deep inheritance scenarios.

src/datamodel_code_generator/parser/base.py (4)

57-58: LGTM! Necessary imports for the generic base class feature.


673-673: LGTM! Parameter and state initialization are correct.

The new use_generic_base_class parameter and generic_base_class_config dictionary are properly initialized and follow existing patterns in the codebase.

Also applies to: 822-823


2075-2086: LGTM! Base class inheritance logic correctly handles schema-defined inheritance.

The logic properly distinguishes between three cases:

  1. Models with a target_model_parent inherit the base class through their parent chain (no action needed)
  2. Models with schema-defined parents get the base class prepended (line 2083)
  3. Models without parents get the base class as their only base (line 2085)

This preserves schema-defined inheritance relationships while adding the generic base class, addressing the concern from previous reviews about replacing existing base classes.


2685-2685: LGTM! Correct placement of generic base class application.

The method is invoked after all per-module processing but before rendering, ensuring the generic base class transformation is applied to the final model structures.

tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/mammal.py (1)

1-11: LGTM! Generated code correctly implements inheritance.

The Mammal class properly inherits from Animal (imported from the sibling module) and defines its own warm_blooded field, demonstrating the deep inheritance chain.

tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/model.py (1)

1-13: LGTM! Standard RootModel implementation.

The generated RootModel correctly uses Pydantic's RootModel[Any] pattern for schema-level models.

tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/dog.py (1)

1-11: LGTM! Deep inheritance chain correctly completed.

The Dog class properly inherits from Mammal, which completes the three-level inheritance chain (Dog → Mammal → Animal → BaseModel), demonstrating that the generic base class feature works correctly with deep inheritance.

tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/animal.py (1)

11-18: LGTM! Excellent demonstration of the DRY pattern.

The generic BaseModel is defined once in the root module with model_config = ConfigDict(extra='forbid'), and Animal inherits from it. This allows the entire inheritance chain (Animal → Mammal → Dog) to share the configuration without repetition, which is the core benefit of the --use-generic-base-class feature.

tests/main/jsonschema/test_main_jsonschema.py (2)

30-30: LGTM! Import addition supports new test cases.

The EXPECTED_MAIN_PATH import is correctly added and used in the module split test scenarios (lines 3877, 3896).


3716-3935: Comprehensive test coverage for the new feature.

The 12 new test functions provide excellent coverage of the --use-generic-base-class feature across different scenarios:

  • Various model types (Pydantic v2, dataclasses, msgspec)
  • Different configuration options (populate_by_name, allow_extra, frozen, etc.)
  • Edge cases (enum_only, with_inheritance, deep_inheritance, module_split)

The tests are well-structured, follow existing patterns, and include clear docstrings. The main test correctly uses the @pytest.mark.cli_doc decorator for documentation generation.

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

♻️ Duplicate comments (2)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/model.py (1)

11-14: Duplicate BaseModel definition (see animal.py comment).

This is another instance of the duplicated BaseModel class. The same issue noted in animal.py applies here: three modules define identical BaseModel classes, which contradicts the DRY principle that --use-generic-base-class aims to enforce.

tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/vehicle.py (1)

11-14: Duplicate BaseModel definition (see animal.py comment).

This is the third instance of the duplicated BaseModel class across the multi-root test suite. As noted in animal.py, having separate BaseModel definitions in each root module undermines the feature's goal of centralizing shared configuration.

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

2074-2084: Refactor to avoid duplicate BaseModel definitions across modules.

When multiple root modules exist (e.g., Animal/Dog in one module, Vehicle/Car in another), lines 2074-2084 create a separate BaseModel class for each root module beyond the first. This results in identical class definitions being repeated across modules, as evidenced by the test expectations where animal.py, model.py, and vehicle.py each define their own BaseModel(_BaseModel) with extra='forbid'.

This duplication contradicts the DRY principle and the stated goal of --use-generic-base-class to eliminate repeated model_config blocks. Consider one of these approaches:

  1. Shared module approach: Create a single BaseModel in a shared module (e.g., _base.py) and have all root modules import from it.
  2. Single root constraint: Document that --use-generic-base-class only supports single-root schemas and validate/error when multiple roots are detected.
  3. Primary root strategy: Insert BaseModel only in the first root module and ensure all other modules can import it via relative imports.
📜 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 4bd4de8 and a8797e0.

📒 Files selected for processing (9)
  • src/datamodel_code_generator/parser/base.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/__init__.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/animal.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/car.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/dog.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/model.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/vehicle.py
  • tests/data/jsonschema/use_generic_base_class_multi_root.json
  • tests/main/jsonschema/test_main_jsonschema.py
✅ Files skipped from review due to trivial changes (1)
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/init.py
🧰 Additional context used
🧬 Code graph analysis (6)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/dog.py (1)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/animal.py (1)
  • Animal (17-18)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/vehicle.py (4)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/animal.py (1)
  • BaseModel (11-14)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/model.py (1)
  • BaseModel (11-14)
src/datamodel_code_generator/reference.py (1)
  • _BaseModel (81-138)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (26-41)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/model.py (4)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/animal.py (1)
  • BaseModel (11-14)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/vehicle.py (1)
  • BaseModel (11-14)
src/datamodel_code_generator/reference.py (1)
  • _BaseModel (81-138)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (26-41)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/car.py (1)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/vehicle.py (1)
  • Vehicle (17-18)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/animal.py (5)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/model.py (1)
  • BaseModel (11-14)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/vehicle.py (1)
  • BaseModel (11-14)
src/datamodel_code_generator/reference.py (1)
  • _BaseModel (81-138)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (26-41)
src/datamodel_code_generator/model/base.py (1)
  • name (654-656)
tests/main/jsonschema/test_main_jsonschema.py (2)
tests/main/conftest.py (2)
  • output_file (98-100)
  • run_main_and_assert (244-408)
tests/test_main_kr.py (1)
  • output_file (44-46)
🪛 Ruff (0.14.8)
src/datamodel_code_generator/parser/base.py

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

Remove unused noqa directive

(RUF100)


2012-2012: Unused noqa directive (non-enabled: PLR0912)

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). (10)
  • GitHub Check: benchmarks
  • GitHub Check: Analyze (python)
  • GitHub Check: 3.13 on macOS
  • GitHub Check: 3.10 on macOS
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.12 on macOS
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.13 on Windows
  • GitHub Check: 3.14 on Windows
🔇 Additional comments (7)
tests/data/jsonschema/use_generic_base_class_multi_root.json (1)

1-43: LGTM! Multi-root test schema is well-structured.

This schema effectively defines two independent inheritance hierarchies (Animal→Dog and Vehicle→Car) to test the generic base class feature with multiple root models. The structure appropriately exercises the code paths in the parser that handle multi-root scenarios.

tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/dog.py (1)

1-11: LGTM! Generated model correctly inherits from Animal.

The Dog class properly extends Animal with the breed field and uses relative imports from the sibling module. The inheritance structure matches the schema definition.

tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/car.py (1)

1-11: LGTM! Generated model correctly inherits from Vehicle.

The Car class properly extends Vehicle with the brand field, mirroring the Animal→Dog inheritance pattern in the parallel hierarchy.

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

650-650: Remove unused noqa directive.

Static analysis reports that the PLR0912, PLR0913, and PLR0915 codes in the noqa comment are not enabled and therefore unnecessary. Cleaning up unused suppressions improves code clarity.

🔎 Proposed fix
-    def __init__(  # noqa: PLR0912, PLR0913, PLR0915
+    def __init__(
⛔ Skipped due to learnings
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2681
File: tests/cli_doc/test_cli_doc_coverage.py:82-82
Timestamp: 2025-12-18T13:43:06.494Z
Learning: In datamodel-code-generator project, Ruff preview mode is enabled via `lint.preview = true` in pyproject.toml. This enables preview rules like PLR6301 (no-self-use), so `noqa: PLR6301` directives are necessary and should not be removed even if RUF100 suggests they are unused.

2012-2012: Remove unused noqa directive.

Static analysis reports that the PLR0912 code in the noqa comment is not enabled and therefore unnecessary.

🔎 Proposed fix
-    def __apply_generic_base_class(  # noqa: PLR0912
+    def __apply_generic_base_class(
⛔ Skipped due to learnings
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2681
File: tests/cli_doc/test_cli_doc_coverage.py:82-82
Timestamp: 2025-12-18T13:43:06.494Z
Learning: In datamodel-code-generator project, Ruff preview mode is enabled via `lint.preview = true` in pyproject.toml. This enables preview rules like PLR6301 (no-self-use), so `noqa: PLR6301` directives are necessary and should not be removed even if RUF100 suggests they are unused.
tests/main/jsonschema/test_main_jsonschema.py (2)

30-30: LGTM! Import is properly used.

The EXPECTED_MAIN_PATH import is correctly added and used by the new --use-generic-base-class tests that generate module-split outputs.


3716-3954: Excellent comprehensive test coverage for the new feature!

The test suite for --use-generic-base-class is well-structured and thorough:

Comprehensive scenarios covered:

  • Basic usage with different config options (forbid, allow, frozen, populate_by_name)
  • Edge cases (enum-only, dataclass no-op)
  • Complex inheritance (simple, deep, multi-root)
  • Module split mode (cross-module base class)
  • Multiple model types (Pydantic v2, msgspec)

Good test organization:

  • Clear, descriptive docstrings
  • Consistent test patterns
  • CLI documentation annotation on the primary test
  • Proper use of expected output files

The test coverage thoroughly validates the feature works correctly across different configurations and edge cases.

Comment thread tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/animal.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: 0

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

2012-2012: Consider removing unused noqa directive.

Static analysis indicates the noqa directive for PLR0912 (too many branches) may be unused on this line.

📜 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 a8797e0 and fb669db.

📒 Files selected for processing (8)
  • src/datamodel_code_generator/parser/base.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_circular/__init__.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_circular/_internal.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_circular/friend.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_circular/model.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_circular/person.py
  • tests/data/jsonschema/use_generic_base_class_circular.json
  • tests/main/jsonschema/test_main_jsonschema.py
✅ Files skipped from review due to trivial changes (1)
  • tests/data/expected/main/jsonschema/use_generic_base_class_circular/init.py
🧰 Additional context used
🧬 Code graph analysis (3)
tests/data/expected/main/jsonschema/use_generic_base_class_circular/person.py (1)
tests/data/expected/main/jsonschema/use_generic_base_class_circular/_internal.py (1)
  • Person (22-24)
src/datamodel_code_generator/parser/base.py (3)
src/datamodel_code_generator/model/base.py (5)
  • DataModel (470-756)
  • path (735-737)
  • name (654-656)
  • create_base_class_model (714-727)
  • BaseClassDataType (463-464)
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)
  • create_base_class_model (277-317)
src/datamodel_code_generator/model/msgspec.py (1)
  • create_base_class_model (158-206)
tests/data/expected/main/jsonschema/use_generic_base_class_circular/friend.py (1)
tests/data/expected/main/jsonschema/use_generic_base_class_circular/_internal.py (1)
  • Friend (17-19)
🪛 Ruff (0.14.8)
src/datamodel_code_generator/parser/base.py

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

Remove unused noqa directive

(RUF100)


2012-2012: Unused noqa directive (non-enabled: PLR0912)

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). (8)
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.13 on macOS
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.13 on Windows
  • GitHub Check: 3.14 on Windows
  • GitHub Check: Analyze (python)
  • GitHub Check: benchmarks
🔇 Additional comments (20)
src/datamodel_code_generator/parser/base.py (7)

55-65: LGTM!

The new imports GENERIC_BASE_CLASS_NAME and GENERIC_BASE_CLASS_PATH are correctly added and used in the __apply_generic_base_class method.


822-853: LGTM!

The config routing logic correctly diverts model configuration options to generic_base_class_config when use_generic_base_class is enabled. The pattern is consistent across all five options, and the fallback to per-model template data (extra_template_data[ALL_MODEL]) preserves backward compatibility.


2074-2084: Multi-root module handling addresses past review concern.

This code correctly creates separate base class models for each independent root module, resolving the cross-module inheritance issue flagged in previous reviews. Each module now gets its own BaseModel definition with proper file_path assignment.


2696-2697: LGTM!

The invocation of __apply_generic_base_class is correctly placed after all other model processing steps (sorting, discriminator handling, etc.) and before import collection. This ensures the generic base class is applied to the final model state.


650-650: Keep the noqa directive — it prevents rule violations that would otherwise occur.

The rules PLR0912 (too-many-statements), PLR0913 (too-many-arguments), and PLR0915 (too-many-branches) are actively enforced for this source file (no per-file ignores configured), so the noqa directive is necessary and should not be removed.


2086-2096: Sharing a single BaseClassDataType instance is intentional and safe.

The same base_class_dt instance is reused across target models for memory efficiency. While the shared reference object does have its children list mutated when each model adds itself during initialization, this is the intended design for tracking parent-child relationships between models. The type and reference fields of the BaseClassDataType instance itself are never reassigned, so creating separate instances per model would be unnecessary overhead without providing additional safety.


2088-2096: Untested code path for models with non-target base classes.

Line 2093-2094 (elif parent_refs:) handles models that have existing base classes but none are target models. The # pragma: no cover indicates this branch lacks test coverage.

Consider adding a test case with a schema where a model inherits from a non-target type combined with --use-generic-base-class to ensure this path works correctly.

tests/main/jsonschema/test_main_jsonschema.py (6)

30-30: LGTM!

The import of EXPECTED_MAIN_PATH is correctly added to support the new module-split test expectations.


3716-3737: LGTM!

Well-structured test with appropriate CLI documentation marker. The test covers the basic --use-generic-base-class functionality with Pydantic v2.


3740-3805: LGTM!

Good test coverage for the various model configuration options (--allow-population-by-field-name, --allow-extra-fields, --enable-faux-immutability, --use-attribute-docstrings) combined with --use-generic-base-class. These tests ensure the feature works correctly with different configuration combinations.


3808-3841: LGTM!

These tests appropriately verify edge cases: dataclass output (where the feature should have no effect) and enum-only schemas (where no ProjectBaseModel should be generated).


3844-3935: LGTM!

Comprehensive test coverage for inheritance scenarios: simple inheritance, module-split mode, deep inheritance chains, multiple independent inheritance trees, and circular references. The tests use expected_directory appropriately for module-split outputs.


3938-3972: LGTM!

Good coverage for msgspec.Struct support with the --use-generic-base-class feature, testing both the basic case and the --extra-fields forbid variant.

tests/data/jsonschema/use_generic_base_class_circular.json (1)

1-21: LGTM!

Well-designed test schema that properly establishes circular references between Person and Friend definitions. This is an effective test case for verifying the --use-generic-base-class feature handles circular dependencies correctly.

tests/data/expected/main/jsonschema/use_generic_base_class_circular/friend.py (1)

1-7: LGTM!

Correct expected output structure for the Friend model. The pattern of importing from ._internal and re-exporting via __all__ is the standard approach for handling circular references in module-split mode.

tests/data/expected/main/jsonschema/use_generic_base_class_circular/person.py (1)

1-7: LGTM!

Consistent with the friend.py structure, correctly re-exporting Person from the internal module.

tests/data/expected/main/jsonschema/use_generic_base_class_circular/model.py (1)

1-18: LGTM!

This expected output correctly demonstrates the --use-generic-base-class feature. The shared BaseModel class defines model_config = ConfigDict(extra='forbid') once, and Model inherits from it without repeating the configuration. This achieves the DRY principle as described in the PR objectives.

tests/data/expected/main/jsonschema/use_generic_base_class_circular/_internal.py (3)

1-8: LGTM!

The imports are well-structured. Using BaseModel as _BaseModel correctly avoids name collision with the custom BaseModel class defined below, and from __future__ import annotations properly enables forward references for the circular dependency between Friend and Person.


11-14: LGTM!

This is the core of the --use-generic-base-class feature. The custom BaseModel with shared model_config eliminates the need to repeat configuration in every generated model, directly addressing the DRY objective from issue #2501.


17-24: LGTM!

The circular reference between Friend and Person is correctly handled via from __future__ import annotations. Both classes properly inherit from the custom BaseModel, ensuring they share the extra='forbid' configuration without repetition.

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 (2)
src/datamodel_code_generator/parser/base.py (2)

2012-2100: Consider refactoring to reduce complexity.

This method has 22 local variables (exceeding the limit of 15) and handles multiple responsibilities:

  • Collecting target models across modules
  • Finding root modules
  • Creating the base class model
  • Applying the base class to target models
  • Managing imports

While the logic appears correct, consider extracting helper methods to improve maintainability:

  • _collect_target_models_by_module()
  • _find_root_modules()
  • _apply_base_class_to_models()
  • _update_imports_for_base_class()

2098-2098: Simplify complex import path construction.

The nested ternary expression is difficult to read. Consider refactoring for clarity:

🔎 Proposed refactor
-                from_ = f"{from_}{first_root_module[-1].replace('.py', '')}" if from_.endswith(".") else f"{from_}.{first_root_module[-1].replace('.py', '')}"
+                module_name = first_root_module[-1].replace('.py', '')
+                from_ = f"{from_}{module_name}" if from_.endswith(".") else f"{from_}.{module_name}"
📜 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 fb669db and 43e1f4c.

📒 Files selected for processing (4)
  • src/datamodel_code_generator/parser/base.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_circular/model.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/animal.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/model.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/model.py
🧰 Additional context used
🧬 Code graph analysis (2)
tests/data/expected/main/jsonschema/use_generic_base_class_circular/model.py (2)
tests/data/expected/main/jsonschema/use_generic_base_class_circular/_internal.py (1)
  • BaseModel (11-14)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/model.py (1)
  • Model (10-11)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/animal.py (2)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/vehicle.py (1)
  • BaseModel (11-14)
src/datamodel_code_generator/model/base.py (1)
  • name (654-656)
🪛 GitHub Actions: Lint
src/datamodel_code_generator/parser/base.py

[error] 2012-2012: ruff: PLR0914 Too many local variables (22/15).


[error] 2084-2084: SLF001 Private member accessed: _additional_imports.


[error] 2084-2084: SLF001 Private member accessed: _additional_imports.


[error] 1-1: ruff-format modified 1 file. Run 'prek run --all-files' to apply formatting locally.

🪛 Ruff (0.14.8)
src/datamodel_code_generator/parser/base.py

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

Remove unused noqa directive

(RUF100)


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

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: 3.13 on Windows
  • GitHub Check: 3.10 on Windows
  • GitHub Check: py312-isort6 on Ubuntu
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.14 on macOS
  • GitHub Check: 3.11 on macOS
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.12 on macOS
  • GitHub Check: 3.14 on Windows
  • GitHub Check: benchmarks
  • GitHub Check: Analyze (python)
🔇 Additional comments (7)
tests/data/expected/main/jsonschema/use_generic_base_class_circular/model.py (1)

1-11: LGTM! Test fixture correctly demonstrates the generic base class feature.

This test fixture appropriately shows the --use-generic-base-class feature in action: the Model class inherits from the shared BaseModel (defined in _internal.py with the model_config) rather than repeating the configuration. The empty Model class is a valid minimal test case for verifying base class inheritance works correctly in circular reference scenarios.

tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/animal.py (2)

7-7: Past concern resolved: shared BaseModel correctly imported.

The import of BaseModel from .vehicle addresses the duplicate definition issue flagged in the previous review. By reusing a single shared base class, the generated code now follows the DRY principle and eliminates type incompatibility across modules.


10-11: LGTM!

The Animal model correctly inherits from the shared BaseModel and demonstrates the --use-generic-base-class feature working as intended. The model_config (extra='forbid') is inherited from the base class without duplication.

src/datamodel_code_generator/parser/base.py (4)

57-58: LGTM! Clean imports for the generic base class feature.

The new constants are properly imported and used throughout the implementation.


673-673: Well-structured feature flag with clean conditional config initialization.

The implementation properly separates generic base class configuration from per-model configuration based on the use_generic_base_class flag, maintaining backward compatibility.

Also applies to: 822-853


2699-2699: LGTM! Correctly positioned in the processing flow.

The __apply_generic_base_class call is properly placed after per-module model processing and before final import cleanup and rendering.


1-1: Verify code formatting with ruff-format.

The project uses ruff-format configured to run against the entire repository with prek run --all-files. Before merging, ensure the code passes formatting checks by running this command locally.

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

♻️ Duplicate comments (3)
src/datamodel_code_generator/parser/base.py (3)

650-650: Remove unused noqa directive.

The noqa directive for PLR0912, PLR0913, PLR0915 is flagged as unused by Ruff, indicating these rules are not currently enabled. Clean up by removing the unnecessary directive.

🔎 Proposed fix
-    def __init__(  # noqa: PLR0012, PLR0913, PLR0915
+    def __init__(

2012-2012: Update or remove noqa directive.

Static analysis indicates the noqa directive for PLR0912, PLR0914, PLR0915 is unused because these rules are not enabled. However, a past review comment indicated PLR0914 (too many local variables) was an actual issue. Either remove the directive if the rules are truly disabled, or verify the correct rule codes and update accordingly.


2084-2086: Avoid accessing private member _additional_imports.

Lines 2084-2085 directly access model._additional_imports, violating encapsulation. A past review suggested adding a public method to the DataModel class (e.g., remove_additional_import) to handle this operation.

🔎 Previously suggested approach

Add a public method to the DataModel class:

# In DataModel class
def remove_additional_import(self, import_obj: Import) -> None:
    """Remove an import from additional imports."""
    self._additional_imports = [i for i in self._additional_imports if i != import_obj]

Then use it here:

if original_import:
    model.remove_additional_import(original_import)
🧹 Nitpick comments (2)
src/datamodel_code_generator/parser/base.py (2)

822-854: Consider extracting repetitive conditional config logic into a helper method.

The pattern of checking use_generic_base_class and conditionally populating either generic_base_class_config or extra_template_data[ALL_MODEL] is repeated five times. While clear, extracting this into a helper method would reduce repetition and make future additions easier.

🔎 Example refactor
def _set_model_config(self, key: str, value: Any) -> None:
    """Set config in generic base class or per-model template data."""
    if self.use_generic_base_class:
        self.generic_base_class_config[key] = value
    else:
        self.extra_template_data[ALL_MODEL][key] = value

# Then use it:
if allow_population_by_field_name:
    self._set_model_config("allow_population_by_field_name", True)

if allow_extra_fields:
    self._set_model_config("allow_extra_fields", True)
# ... etc

2012-2103: Feature implementation is functionally sound but has minor cleanup opportunities.

The __apply_generic_base_class method successfully implements the DRY base class pattern described in PR #2501. The logic correctly:

  • Identifies target models and root modules
  • Creates a shared base class with common configuration
  • Preserves schema-defined inheritance while adding the generic base class
  • Handles cross-module scenarios

However, consider these improvements:

  1. Extract helper methods to reduce complexity (22 local variables)
  2. Address the private member access violation
  3. Clean up unused noqa directives

The core functionality appears correct based on the comprehensive past reviews that addressed inheritance edge cases, cross-module scenarios, and multiple inheritance trees.

📜 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 43e1f4c and 88a8c48.

📒 Files selected for processing (1)
  • src/datamodel_code_generator/parser/base.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-18T13:43:06.494Z
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2681
File: tests/cli_doc/test_cli_doc_coverage.py:82-82
Timestamp: 2025-12-18T13:43:06.494Z
Learning: In datamodel-code-generator project, Ruff preview mode is enabled via `lint.preview = true` in pyproject.toml. This enables preview rules like PLR6301 (no-self-use), so `noqa: PLR6301` directives are necessary and should not be removed even if RUF100 suggests they are unused.

Applied to files:

  • src/datamodel_code_generator/parser/base.py
🧬 Code graph analysis (1)
src/datamodel_code_generator/parser/base.py (6)
src/datamodel_code_generator/model/base.py (7)
  • DataModel (470-756)
  • imports (283-302)
  • imports (638-643)
  • path (735-737)
  • name (654-656)
  • create_base_class_model (714-727)
  • BaseClassDataType (463-464)
src/datamodel_code_generator/imports.py (3)
  • append (74-89)
  • Import (20-38)
  • from_full_path (35-38)
src/datamodel_code_generator/model/enum.py (1)
  • imports (119-121)
src/datamodel_code_generator/types.py (1)
  • imports (450-495)
src/datamodel_code_generator/model/typed_dict.py (1)
  • imports (150-155)
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)
  • create_base_class_model (277-317)
🪛 Ruff (0.14.8)
src/datamodel_code_generator/parser/base.py

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

Remove unused noqa directive

(RUF100)


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

Remove unused noqa directive

(RUF100)


2084-2084: Unused noqa directive (non-enabled: SLF001)

Remove unused noqa directive

(RUF100)


2085-2085: Unused noqa directive (non-enabled: SLF001)

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). (8)
  • GitHub Check: benchmarks
  • GitHub Check: py312-black23 on Ubuntu
  • GitHub Check: 3.14 on Windows
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.12 on macOS
  • GitHub Check: 3.13 on Windows
  • GitHub Check: 3.12 on Windows
🔇 Additional comments (3)
src/datamodel_code_generator/parser/base.py (3)

57-58: LGTM! Imports are properly added for the generic base class feature.

The imports of GENERIC_BASE_CLASS_NAME and GENERIC_BASE_CLASS_PATH are necessary for the new functionality and are correctly sourced from the model.base module.


2086-2093: Verify base class inheritance logic handles all edge cases correctly.

The logic for assigning base classes:

  • If the model has a target_model_parent → skip (inherits transitively)
  • Else if the model has other parent_refs → insert(0, base_class_dt) to prepend
  • Else → base_classes = [base_class_dt] to replace

This appears correct for preserving schema-defined inheritance while adding the generic base class. However, ensure this is tested with:

  1. Models with multiple base classes (some target, some not)
  2. Diamond inheritance patterns
  3. Cross-module inheritance scenarios

Past review comments indicate these were addressed, but verification with comprehensive tests is important given the complexity.


2702-2702: LGTM! Method placement is appropriate.

Calling __apply_generic_base_class at this point in the pipeline ensures all individual model processing is complete before applying the shared base class transformation. This is the correct integration point.

@koxudaxi koxudaxi force-pushed the fix/use-generic-base-class branch from 88a8c48 to 0bf316c Compare December 22, 2025 04:12
This option creates a shared base class (BaseModel/Struct) with common
configuration instead of repeating model_config in every generated model.

Features:
- Supports pydantic v2 BaseModel and msgspec Struct
- Works with module split mode (single BaseModel shared across modules)
- Preserves schema inheritance relationships
- Handles circular imports via _internal.py pattern
@koxudaxi koxudaxi force-pushed the fix/use-generic-base-class branch from 0bf316c to 40d67fe Compare December 22, 2025 05:20
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

♻️ Duplicate comments (3)
src/datamodel_code_generator/parser/base.py (3)

650-650: Remove unused noqa directive.

The noqa comment references rules (PLR0912, PLR0913, PLR0915) that are not triggering warnings per static analysis. Remove the directive to reduce noise.

🔎 Proposed fix
-    def __init__(  # noqa: PLR0912, PLR0913, PLR0915
+    def __init__(

2012-2012: Update noqa directive to suppress the correct rule.

Static analysis indicates the current noqa comment references rules (PLR0912, PLR0914, PLR0915) that are not triggering. If PLR0914 (too many local variables) is the actual concern based on previous review, update accordingly; otherwise, remove the directive.

🔎 Proposed fix
-    def __apply_generic_base_class(  # noqa: PLR0912, PLR0914, PLR0915
+    def __apply_generic_base_class(

2084-2085: Private member access violates encapsulation.

Lines 2084-2085 directly access and mutate model._additional_imports. The static analysis hints indicate the noqa: SLF001 directives are unused, suggesting the linter rule isn't active, but the encapsulation concern remains valid. Consider adding a public method to DataModel for removing additional imports.

🔎 Proposed approach

Add a public method to the DataModel class:

def remove_additional_import(self, import_to_remove: Import) -> None:
    """Remove an import from additional imports."""
    self._additional_imports = [i for i in self._additional_imports if i != import_to_remove]

Then replace lines 2084-2085 with:

if original_import:
    model.remove_additional_import(original_import)
🧹 Nitpick comments (5)
src/datamodel_code_generator/model/base.py (1)

713-727: Template method pattern is appropriate; minor cleanup available.

The base implementation correctly returns None, allowing concrete model types (pydantic_v2.BaseModel, msgspec.Struct) to override with their specific implementations. The docstring clearly explains the contract.

Per static analysis, the FBT001 and FBT002 noqa directives on lines 719-720 are unused since these rules are not enabled. Consider removing them for cleaner code:

🔎 Proposed cleanup
     @classmethod
     def create_base_class_model(
         cls,
         config: dict[str, Any],  # noqa: ARG003
         reference: Reference,  # noqa: ARG003
-        custom_template_dir: Path | None = None,  # noqa: ARG003
-        keyword_only: bool = False,  # noqa: ARG003, FBT001, FBT002
-        treat_dot_as_module: bool = False,  # noqa: ARG003, FBT001, FBT002
+        custom_template_dir: Path | None = None,  # noqa: ARG003
+        keyword_only: bool = False,  # noqa: ARG003
+        treat_dot_as_module: bool = False,  # noqa: ARG003
     ) -> DataModel | None:
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)

284-286: Remove unused noqa directives.

Static analysis indicates these noqa directives are not needed as FBT001/FBT002 rules are not enabled.

Proposed fix
-        keyword_only: bool = False,  # noqa: FBT001, FBT002
-        treat_dot_as_module: bool = False,  # noqa: FBT001, FBT002
+        keyword_only: bool = False,
+        treat_dot_as_module: bool = False,
src/datamodel_code_generator/model/msgspec.py (2)

175-177: Remove unused noqa directives.

Same as the Pydantic v2 implementation, static analysis indicates these directives are unnecessary.

Proposed fix
-        keyword_only: bool = False,  # noqa: FBT001, FBT002
-        treat_dot_as_module: bool = False,  # noqa: FBT001, FBT002
+        keyword_only: bool = False,
+        treat_dot_as_module: bool = False,

116-124: CONFIG_MAPPING silently ignores unsupported options.

Several configuration options (extra_fields=allow, extra_fields=ignore, allow_extra_fields, allow_population_by_field_name, use_attribute_docstrings) map to None and are silently skipped. This is valid since msgspec doesn't support these features, but users may not realize their configuration has no effect.

Consider documenting this limitation in CLI help or emitting a warning when these options are used with msgspec.Struct.

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

2088-2089: Clarify the no-op branch.

The pass statement at line 2089 is a no-op for models that already have a target model parent. Consider adding a brief comment to clarify the intent, or restructure using elif to make the logic flow clearer.

🔎 Proposed fix
                 if has_target_model_parent:
-                    pass
+                    # Model already inherits from a target model that will have the base class;
+                    # no need to add base_class_dt directly to this model.
+                    pass
                 elif parent_refs:  # pragma: no cover
📜 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 88a8c48 and 40d67fe.

📒 Files selected for processing (54)
  • docs/cli-reference/index.md
  • docs/cli-reference/model-customization.md
  • docs/cli-reference/quick-reference.md
  • src/datamodel_code_generator/__init__.py
  • src/datamodel_code_generator/__main__.py
  • src/datamodel_code_generator/arguments.py
  • src/datamodel_code_generator/cli_options.py
  • src/datamodel_code_generator/model/base.py
  • src/datamodel_code_generator/model/enum.py
  • src/datamodel_code_generator/model/msgspec.py
  • src/datamodel_code_generator/model/pydantic_v2/base_model.py
  • src/datamodel_code_generator/model/pydantic_v2/imports.py
  • src/datamodel_code_generator/model/type_alias.py
  • src/datamodel_code_generator/parser/base.py
  • src/datamodel_code_generator/parser/graphql.py
  • src/datamodel_code_generator/parser/jsonschema.py
  • src/datamodel_code_generator/parser/openapi.py
  • tests/data/expected/main/jsonschema/use_generic_base_class.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_allow_extra.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_attr_docstrings.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_circular/__init__.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_circular/_internal.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_circular/friend.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_circular/model.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_circular/person.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_dataclass.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/__init__.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/animal.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/dog.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/mammal.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/model.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_enum_only.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_frozen.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_module_split/__init__.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_module_split/animal.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_module_split/dog.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_module_split/model.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_msgspec.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_msgspec_forbid.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/__init__.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/animal.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/car.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/dog.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/model.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/vehicle.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_populate_by_name.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_with_inheritance.py
  • tests/data/jsonschema/use_generic_base_class_circular.json
  • tests/data/jsonschema/use_generic_base_class_deep_inheritance.json
  • tests/data/jsonschema/use_generic_base_class_enum_only.json
  • tests/data/jsonschema/use_generic_base_class_multi_root.json
  • tests/data/jsonschema/use_generic_base_class_simple.json
  • tests/data/jsonschema/use_generic_base_class_with_inheritance.json
  • tests/main/jsonschema/test_main_jsonschema.py
✅ Files skipped from review due to trivial changes (3)
  • tests/data/expected/main/jsonschema/use_generic_base_class_circular/init.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_module_split/init.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/init.py
🚧 Files skipped from review as they are similar to previous changes (27)
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/init.py
  • tests/data/jsonschema/use_generic_base_class_enum_only.json
  • docs/cli-reference/quick-reference.md
  • src/datamodel_code_generator/main.py
  • tests/data/jsonschema/use_generic_base_class_circular.json
  • tests/data/jsonschema/use_generic_base_class_simple.json
  • tests/data/expected/main/jsonschema/use_generic_base_class_module_split/model.py
  • tests/data/jsonschema/use_generic_base_class_multi_root.json
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/model.py
  • src/datamodel_code_generator/cli_options.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_msgspec.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_enum_only.py
  • src/datamodel_code_generator/parser/graphql.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/model.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_circular/_internal.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/animal.py
  • src/datamodel_code_generator/init.py
  • docs/cli-reference/model-customization.md
  • src/datamodel_code_generator/parser/jsonschema.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_circular/person.py
  • tests/data/jsonschema/use_generic_base_class_deep_inheritance.json
  • tests/data/expected/main/jsonschema/use_generic_base_class_dataclass.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_circular/model.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/car.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_module_split/dog.py
  • tests/data/expected/main/jsonschema/use_generic_base_class_attr_docstrings.py
  • tests/data/expected/main/jsonschema/use_generic_base_class.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-18T13:43:06.494Z
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2681
File: tests/cli_doc/test_cli_doc_coverage.py:82-82
Timestamp: 2025-12-18T13:43:06.494Z
Learning: In datamodel-code-generator project, Ruff preview mode is enabled via `lint.preview = true` in pyproject.toml. This enables preview rules like PLR6301 (no-self-use), so `noqa: PLR6301` directives are necessary and should not be removed even if RUF100 suggests they are unused.

Applied to files:

  • src/datamodel_code_generator/parser/base.py
🧬 Code graph analysis (12)
tests/data/expected/main/jsonschema/use_generic_base_class_module_split/animal.py (5)
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)
  • BaseModel (170-318)
tests/data/expected/main/jsonschema/use_generic_base_class.py (1)
  • BaseModel (11-14)
tests/data/expected/main/jsonschema/use_generic_base_class_with_inheritance.py (2)
  • BaseModel (13-16)
  • Animal (23-24)
src/datamodel_code_generator/reference.py (1)
  • _BaseModel (81-138)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (26-41)
tests/data/expected/main/jsonschema/use_generic_base_class_populate_by_name.py (2)
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)
  • BaseModel (170-318)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (26-41)
tests/data/expected/main/jsonschema/use_generic_base_class_circular/friend.py (1)
tests/data/expected/main/jsonschema/use_generic_base_class_circular/_internal.py (1)
  • Friend (17-19)
src/datamodel_code_generator/model/base.py (3)
src/datamodel_code_generator/model/msgspec.py (1)
  • create_base_class_model (170-207)
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)
  • create_base_class_model (279-318)
src/datamodel_code_generator/reference.py (2)
  • reference (76-78)
  • Reference (141-200)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/dog.py (2)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/animal.py (1)
  • Animal (10-11)
tests/data/expected/main/jsonschema/use_generic_base_class_module_split/dog.py (1)
  • Dog (10-11)
src/datamodel_code_generator/model/pydantic_v2/imports.py (1)
src/datamodel_code_generator/imports.py (2)
  • Import (20-38)
  • from_full_path (35-38)
tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/mammal.py (4)
tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/animal.py (1)
  • Animal (17-18)
tests/data/expected/main/jsonschema/use_generic_base_class_module_split/animal.py (1)
  • Animal (17-18)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/animal.py (1)
  • Animal (10-11)
tests/data/expected/main/jsonschema/use_generic_base_class_with_inheritance.py (1)
  • Animal (23-24)
tests/data/expected/main/jsonschema/use_generic_base_class_frozen.py (4)
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)
  • BaseModel (170-318)
tests/data/expected/main/jsonschema/use_generic_base_class.py (1)
  • BaseModel (11-14)
src/datamodel_code_generator/reference.py (1)
  • _BaseModel (81-138)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (26-41)
src/datamodel_code_generator/model/msgspec.py (3)
src/datamodel_code_generator/model/base.py (6)
  • BaseClassDataType (463-464)
  • imports (283-302)
  • imports (638-643)
  • create_base_class_model (714-727)
  • path (735-737)
  • name (654-656)
src/datamodel_code_generator/imports.py (2)
  • Import (20-38)
  • append (74-89)
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)
  • create_base_class_model (279-318)
tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/dog.py (4)
tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/mammal.py (1)
  • Mammal (10-11)
tests/data/expected/main/jsonschema/use_generic_base_class_module_split/dog.py (1)
  • Dog (10-11)
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/dog.py (1)
  • Dog (10-11)
tests/data/expected/main/jsonschema/use_generic_base_class_with_inheritance.py (1)
  • Dog (27-28)
tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/animal.py (2)
tests/data/expected/main/jsonschema/use_generic_base_class.py (1)
  • BaseModel (11-14)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (26-41)
src/datamodel_code_generator/parser/base.py (4)
src/datamodel_code_generator/model/base.py (5)
  • imports (283-302)
  • imports (638-643)
  • path (735-737)
  • create_base_class_model (714-727)
  • BaseClassDataType (463-464)
src/datamodel_code_generator/imports.py (3)
  • append (74-89)
  • Import (20-38)
  • from_full_path (35-38)
src/datamodel_code_generator/model/msgspec.py (1)
  • create_base_class_model (170-207)
src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)
  • create_base_class_model (279-318)
🪛 Ruff (0.14.8)
src/datamodel_code_generator/model/base.py

719-719: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)


720-720: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/model/pydantic_v2/base_model.py

284-284: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)


285-285: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/model/msgspec.py

175-175: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)


176-176: Unused noqa directive (non-enabled: FBT001, FBT002)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/parser/base.py

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

Remove unused noqa directive

(RUF100)


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

Remove unused noqa directive

(RUF100)


2084-2084: Unused noqa directive (non-enabled: SLF001)

Remove unused noqa directive

(RUF100)


2085-2085: Unused noqa directive (non-enabled: SLF001)

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). (10)
  • GitHub Check: 3.10 on Ubuntu
  • GitHub Check: 3.13 on Windows
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.14 on Windows
  • GitHub Check: 3.13 on Ubuntu
  • GitHub Check: 3.14 on Ubuntu
  • GitHub Check: 3.12 on Windows
  • GitHub Check: Analyze (python)
  • GitHub Check: benchmarks
🔇 Additional comments (35)
tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/animal.py (1)

1-18: LGTM! Correctly implements the DRY base class pattern.

This expected output file correctly demonstrates the core feature: a shared BaseModel class that defines model_config once (lines 11-14), with Animal inheriting from it (line 17). This achieves the PR objective of eliminating repeated model_config blocks across multiple models.

tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/dog.py (1)

1-11: LGTM! Correct deep inheritance structure.

This file correctly extends the inheritance chain to Dog(Mammal) (line 10), demonstrating that the feature works with multi-level inheritance. The absence of model_config here confirms that models properly inherit the shared configuration from the base class hierarchy.

tests/data/expected/main/jsonschema/use_generic_base_class_deep_inheritance/mammal.py (1)

1-11: LGTM! Correctly demonstrates intermediate inheritance.

The Mammal(Animal) class (line 10) correctly forms the middle layer of the inheritance chain, demonstrating that the feature works properly with multi-level hierarchies. The shared model_config from BaseModel propagates through Animal to Mammal without repetition.

tests/data/expected/main/jsonschema/use_generic_base_class_circular/friend.py (1)

1-7: LGTM! Clean re-export pattern for circular references.

This expected output correctly demonstrates the modular structure generated when --use-generic-base-class is used with circular references. The re-export pattern (importing from _internal and declaring __all__) is a standard Python approach for resolving circular imports while maintaining a clean public API.

tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/dog.py (1)

1-11: LGTM! Expected test output correctly demonstrates the DRY base-class feature.

This test file properly demonstrates the --use-generic-base-class feature:

  • Dog inherits from Animal (which inherits from the shared BaseModel with model_config)
  • No repeated model_config block in Dog (the DRY benefit)
  • Correct multi-level inheritance chain for the multi-root structure
tests/data/expected/main/jsonschema/use_generic_base_class_frozen.py (3)

1-8: LGTM! Correct import structure for the DRY base class pattern.

The import aliasing (BaseModel as _BaseModel) correctly enables the definition of a custom BaseModel that wraps the Pydantic base with shared configuration.


11-14: LGTM! Correctly implements the shared base class with frozen config.

The custom BaseModel successfully consolidates the frozen=True configuration in one place, which will be inherited by all models. This achieves the DRY principle that is the core objective of the --use-generic-base-class feature.


17-18: LGTM! Model correctly inherits shared configuration.

The Simple model inherits from the custom BaseModel and correctly omits the model_config definition, demonstrating that the shared configuration is properly inherited. This is exactly the DRY pattern the feature aims to achieve.

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

45-45: LGTM!

Correctly marking Enum as not supporting the generic base class feature. Enums have their own distinct inheritance hierarchy (enum.Enum, enum.StrEnum, enum.IntEnum) and shouldn't participate in the pydantic/msgspec base class pattern.

docs/cli-reference/index.md (1)

14-14: LGTM!

Documentation is correctly updated with the new --use-generic-base-class option. The option count increment and the alphabetically-placed link are accurate.

Also applies to: 170-170

src/datamodel_code_generator/model/pydantic_v2/imports.py (1)

10-10: LGTM!

The new IMPORT_BASE_MODEL constant follows the established pattern and is needed for aliasing pydantic.BaseModel as _BaseModel when generating the generic base class.

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

24-25: LGTM!

Both changes are appropriate:

  1. IS_ALIAS correctly annotated as ClassVar[bool] (was previously just bool for a class-level constant)
  2. SUPPORTS_GENERIC_BASE_CLASS = False correctly marks that type aliases don't participate in the generic base class feature since they aren't class-based models
tests/data/expected/main/jsonschema/use_generic_base_class_multi_root/vehicle.py (1)

1-18: LGTM!

The test fixture correctly demonstrates the DRY pattern introduced by the --use-generic-base-class feature:

  • BaseModel is aliased from _BaseModel to avoid naming collision
  • model_config with extra='forbid' is defined once in the shared BaseModel
  • Vehicle inherits the configuration without repeating it
tests/data/expected/main/jsonschema/use_generic_base_class_populate_by_name.py (1)

2-2: Inconsistent source filename in generated header.

The header comment indicates filename: use_generic_base_class_simple.json, but this expected output file is for the populate_by_name scenario. The source JSON file should likely be named differently (e.g., use_generic_base_class_populate_by_name.json) or the test is reusing the same input file for multiple scenarios.

If this is intentional reuse, consider if the header should reflect the actual test case name for clarity. Otherwise, verify the test input file naming.

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

2-2: Same source filename discrepancy.

Similar to the populate_by_name test, this file references use_generic_base_class_simple.json in the header while being the msgspec_forbid expected output. Please verify test input consistency.


14-19: LGTM on the msgspec generic base class pattern.

The msgspec implementation correctly demonstrates the DRY pattern:

  • Struct aliased from _Struct
  • Base Struct class applies forbid_unknown_fields=True and frozen=True as class parameters
  • Simple inherits configuration cleanly without repetition

This properly mirrors the pydantic approach adapted for msgspec's class-parameter-based configuration style.

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

1-17: LGTM!

The expected output correctly demonstrates the DRY pattern: BaseModel defines model_config once with extra='allow', and Simple inherits from it without repeating the configuration. This aligns with the PR objective of avoiding duplicate model_config blocks.

src/datamodel_code_generator/arguments.py (1)

272-278: LGTM!

The new --use-generic-base-class option follows the established pattern for boolean CLI flags in this file, with appropriate placement in the model customization group and clear help text explaining the DRY benefit.

tests/data/jsonschema/use_generic_base_class_with_inheritance.json (1)

1-28: LGTM!

This test schema appropriately covers the inheritance scenario using allOf, which is essential for verifying that the generic base class feature correctly handles models with existing inheritance relationships.

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

202-202: LGTM!

The use_generic_base_class parameter is correctly added to the __init__ signature and properly forwarded to the parent class constructor at line 302. This follows the established pattern for propagating configuration options through the parser hierarchy.

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

52-53: Constants support the generic base class feature.

These constants are used by the parser module (src/datamodel_code_generator/parser/base.py) to wire the generic base-class flow into the model generation process. The SAST warnings about unused global variables appear to be false positives as the imports happen in other modules.


479-480: LGTM!

The SUPPORTS_GENERIC_BASE_CLASS class variable provides a clean opt-in/opt-out mechanism for model types, and typing IS_ALIAS as ClassVar[bool] correctly reflects its nature as a class-level constant.

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

1-27: LGTM! Inheritance chain is correctly structured.

The expected output demonstrates the proper inheritance hierarchy:

  • BaseModel inherits from _BaseModel (aliased pydantic BaseModel) with shared model_config
  • Animal inherits from the project's BaseModel
  • Dog inherits from Animal (single inheritance, avoiding MRO issues)

This correctly implements the DRY pattern for model_config while preserving schema-defined inheritance relationships.

src/datamodel_code_generator/model/pydantic_v2/base_model.py (1)

278-317: Well-structured base class factory method.

The implementation correctly:

  • Creates a shared base model inheriting from the aliased _BaseModel
  • Propagates configuration through extra_template_data
  • Manages imports by removing duplicates and adding the aliased import
src/datamodel_code_generator/model/msgspec.py (1)

169-207: Consistent implementation with Pydantic v2 pattern.

The create_base_class_model method follows the same structure as the Pydantic v2 implementation while correctly adapting to msgspec's base class kwargs pattern for configuration.

tests/main/jsonschema/test_main_jsonschema.py (3)

3716-3737: Comprehensive CLI documentation and test coverage for --use-generic-base-class.

The primary test case includes proper @pytest.mark.cli_doc decoration for documentation generation, with clear inputs and expected outputs. The test correctly validates the DRY pattern for model_config with the --extra-fields forbid option.


3862-3878: Module split test correctly validates cross-module base class inheritance.

This test ensures the generic base class is properly imported across module boundaries when using --module-split-mode single, which is critical for the feature's usability in larger codebases.


3938-3972: msgspec coverage validates feature works across model types.

These tests ensure the --use-generic-base-class feature works correctly with msgspec.Struct, including the limited configuration mapping (frozen via --enable-faux-immutability and forbid unknown fields via --extra-fields forbid).

src/datamodel_code_generator/parser/base.py (3)

55-65: LGTM!

The new imports for GENERIC_BASE_CLASS_NAME and GENERIC_BASE_CLASS_PATH are correctly added alongside other imports from datamodel_code_generator.model.base.


822-853: LGTM!

The initialization of use_generic_base_class and generic_base_class_config, along with the conditional routing of configuration options, is correctly implemented. The pattern consistently directs settings to either the shared base class configuration or per-model template data based on the flag.


2702-2703: LGTM!

The invocation of __apply_generic_base_class(processed_models) is correctly positioned after all model processing steps and before the final rendering phase. This ensures the generic base class can be properly applied across all processed modules.

tests/data/expected/main/jsonschema/use_generic_base_class_module_split/animal.py (4)

5-8: LGTM!

The imports are correct for Pydantic v2. The aliasing of BaseModel as _BaseModel follows the expected pattern for the generic base class feature, allowing the custom BaseModel to be defined without naming conflicts.


11-14: LGTM!

The BaseModel class correctly implements the DRY pattern by defining model_config once in a shared base class. This achieves the PR objective of avoiding repeated model_config blocks across generated models. The structure matches the expected output from related test files.


17-18: LGTM!

The Animal class correctly inherits from the custom BaseModel, demonstrating the benefit of the generic base class feature—no need to repeat model_config in this model. The type annotation uses modern Python 3.10+ union syntax (str | None), which is appropriate for the target codebase.


1-4: No issue identified. The filename metadata in the header comment correctly references use_generic_base_class_with_inheritance.json, which is the actual input JSON file. The use_generic_base_class_module_split directory is an output variant generated from this same JSON input file with module-split output enabled. The tool's generated header always shows the source input filename regardless of the output structure.

@koxudaxi koxudaxi merged commit ae11c41 into main Dec 22, 2025
37 of 38 checks passed
@koxudaxi koxudaxi deleted the fix/use-generic-base-class branch December 22, 2025 05:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

model_config moved to one top class

2 participants