Skip to content

Avoid TYPE_CHECKING imports for Ruff modular output#3043

Merged
koxudaxi merged 15 commits intomainfrom
fix/issue-3038-type-checking-imports
Mar 10, 2026
Merged

Avoid TYPE_CHECKING imports for Ruff modular output#3043
koxudaxi merged 15 commits intomainfrom
fix/issue-3038-type-checking-imports

Conversation

@koxudaxi
Copy link
Copy Markdown
Owner

@koxudaxi koxudaxi commented Mar 10, 2026

Summary

  • add a --use-type-checking-imports / --no-use-type-checking-imports switch for Ruff-based output
  • keep runtime imports in modular output by disabling Ruff TC auto-fixes when the option is turned off
  • add regression coverage for CLI generation, formatter command construction, and runtime validation

Investigation

Testing

  • tox run -e py314-parallel
  • tox run -e cli-docs -- --check
  • tox run -e type
  • tox run -e readme -- --check
  • tox run -e config-types -- --check

Fixes #3038

Summary by CodeRabbit

  • New Features

    • Added CLI options --use-type-checking-imports and --no-use-type-checking-imports to control whether generated model imports are moved into TYPE_CHECKING blocks or kept at runtime. By default, runtime imports are preserved for multi-module Pydantic output when using Ruff.
  • Documentation

    • CLI reference, quick reference, and template customization docs updated to document the new options (some template sections were duplicated).
  • Tests

    • New tests cover formatter/Ruff invocations and runtime import behavior (some test entries were duplicated).

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 10, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds CLI flags and config to control whether generated imports are placed inside TYPE_CHECKING, propagates that option through config, parser, generator, and CodeFormatter, updates Ruff command construction to conditionally preserve runtime imports, and updates docs, tests, and expected outputs accordingly.

Changes

Cohort / File(s) Summary
Documentation
docs/cli-reference/index.md, docs/cli-reference/quick-reference.md, docs/cli-reference/template-customization.md
Add --use-type-checking-imports / --no-use-type-checking-imports docs and examples; minor alignment/duplication issues in template-customization.md.
CLI surface & wiring
src/datamodel_code_generator/arguments.py, src/datamodel_code_generator/cli_options.py, src/datamodel_code_generator/__main__.py
Introduce boolean CLI flags, register metadata, add to boolean options, and pass through generate entrypoints.
Configuration & types
src/datamodel_code_generator/config.py, src/datamodel_code_generator/_types/generate_config_dict.py, src/datamodel_code_generator/_types/parser_config_dicts.py
Add `use_type_checking_imports: bool
Formatting / Ruff integration
src/datamodel_code_generator/format.py, src/datamodel_code_generator/__init__.py
Add resolve_use_type_checking_imports(...), add use_type_checking_imports parameter to CodeFormatter, store flag, introduce _ruff_check_command helper, and ensure ruff_path and conditional --unfixable behavior are used for Ruff invocations.
Parser wiring & codegen flow
src/datamodel_code_generator/parser/base.py, src/datamodel_code_generator/prompt_data.py
Add parser state use_type_checking_imports, compute effective value via resolver in formatter build path, propagate to CodeFormatter; add CLI option descriptions.
Model flags
src/datamodel_code_generator/model/base.py, src/datamodel_code_generator/model/pydantic_base.py, src/datamodel_code_generator/model/pydantic_v2/dataclass.py
Add PRESERVE_RUNTIME_IMPORTS_FOR_MULTI_MODULE_RUFF class flags to indicate runtime-import preservation behaviour for multi-module + Ruff cases.
Tests & expected outputs
tests/data/expected/.../*.py, tests/main/test_main_general.py, tests/main/test_public_api_signature_baseline.py, tests/test_format.py
Add/modify expected internal modules for both runtime and TYPE_CHECKING variants; add tests asserting Ruff path reuse, --unfixable usage, and behavior for --no-use-type-checking-imports. Note: several duplicated test and example blocks appeared in diffs.
Public surface export
src/datamodel_code_generator/__init__.py
Export resolve_use_type_checking_imports and adapt public generation flow to pass new CodeFormatter parameter.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as CLI
  participant Parser as Parser
  participant Formatter as CodeFormatter/Generator
  participant Ruff as Ruff (subprocess)
  participant FS as Filesystem

  CLI->>Parser: provide args (use_type_checking_imports)
  Parser->>Formatter: build formatter/config (resolve_use_type_checking_imports)
  Formatter->>FS: write generated files
  Formatter->>Ruff: run check/format (use _ruff_check_command, ruff_path, optional --unfixable)
  Ruff-->>Formatter: return results
  Formatter->>FS: finalize output (runtime imports preserved or TYPE_CHECKING used)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • #2832: Baseline public API signature updates — directly related to added public API parameter and baseline test adjustments.
  • #2779: Formatting subsystem changes — overlaps with CodeFormatter/Ruff command construction and formatting flow edits.
  • #2844: Config/TypedDict surface extensions — related to added config fields and TypedDict updates.

Suggested labels

breaking-change-analyzed

Poem

🐰 I hopped through code, nudged imports into the light,
No more hidden TYPE_CHECKING in the night.
Ruff sorted the paths, I twitched my whiskers so spry,
Now models run clean — no rebuilds to apply! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive The PR contains extensive changes across configuration, formatting, testing, and documentation that appear related to the feature, but includes duplicated test code (duplicate test functions and documentation blocks) that may indicate incomplete refactoring or merge conflicts. Review and remove duplicate test functions in tests/main/test_main_general.py and verify no duplicate documentation blocks exist in template-customization.md as indicated by the AI-generated summaries.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Avoid TYPE_CHECKING imports for Ruff modular output' clearly and concisely describes the primary change: preventing generated imports from being placed in TYPE_CHECKING blocks for Ruff-formatted modular output.
Linked Issues check ✅ Passed The PR successfully implements the core requirement from issue #3038: providing a flag to disable TYPE_CHECKING import wrapping. The implementation adds --use-type-checking-imports/--no-use-type-checking-imports CLI options to control the behavior, allowing runtime access to imports without requiring model_rebuild() calls.
Docstring Coverage ✅ Passed Docstring coverage is 92.86% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/issue-3038-type-checking-imports

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 10, 2026

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

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Mar 10, 2026

Merging this PR will not alter performance

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

✅ 11 untouched benchmarks
⏩ 98 skipped benchmarks1


Comparing fix/issue-3038-type-checking-imports (ffd6833) with main (77af774)

Open in CodSpeed

Footnotes

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

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 10, 2026

Codecov Report

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

Additional details and impacted files
@@            Coverage Diff             @@
##              main     #3043    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           85        86     +1     
  Lines        17883     18045   +162     
  Branches      2091      2098     +7     
==========================================
+ Hits         17883     18045   +162     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

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

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

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

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)
docs/cli-reference/template-customization.md (1)

2345-2351: State the default and paired flag here.

Because this section is written under the negative form, it is hard to tell that TYPE_CHECKING imports stay enabled by default and that --use-type-checking-imports is the explicit opt-back-in form. A one-line note would make the CLI behavior much clearer.

📝 Suggested wording
 The `--no-use-type-checking-imports` flag prevents Ruff from moving generated model imports
 into `TYPE_CHECKING` blocks. This is useful for modular Pydantic output where referenced
 models need to be importable at runtime without calling `model_rebuild()` manually.
+By default, TYPE_CHECKING imports remain enabled; `--use-type-checking-imports` is the
+explicit form of that default behavior.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/cli-reference/template-customization.md` around lines 2345 - 2351, Add a
one-line clarification stating the default behavior and the paired flag: explain
that by default generated model imports are wrapped in TYPE_CHECKING blocks
(i.e., type-checking imports are enabled), and that
--no-use-type-checking-imports disables that behavior while
--use-type-checking-imports explicitly re-enables it; reference the flags
--no-use-type-checking-imports, --use-type-checking-imports and the
TYPE_CHECKING behavior in the same paragraph so readers immediately see the
default and the opt-out/opt-back-in option.
tests/test_format.py (1)

197-218: Cover the batch-formatting path too.

generate() applies Ruff to modular output via CodeFormatter.format_directory() in src/datamodel_code_generator/__init__.py:902-919, so this only locks down the stdin branch. A companion test for format_directory(..., use_type_checking_imports=False) would protect the actual issue-3038 path.

🧪 Suggested companion test
+def test_format_directory_ruff_check_without_type_checking_imports(
+    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+    monkeypatch.chdir(tmp_path)
+    formatter = CodeFormatter(
+        PythonVersionMin,
+        formatters=[Formatter.RUFF_CHECK],
+        use_type_checking_imports=False,
+    )
+    output_dir = tmp_path / "output"
+    output_dir.mkdir()
+
+    with mock.patch("subprocess.run") as mock_run:
+        formatter.format_directory(output_dir)
+
+    mock_run.assert_called_once_with(
+        ("ruff", "check", "--fix", "--unsafe-fixes", "--unfixable", "TC001,TC002,TC003", str(output_dir)),
+        capture_output=True,
+        check=False,
+        cwd=str(tmp_path),
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_format.py` around lines 197 - 218, Add a companion test that
exercises the batch-formatting path by invoking CodeFormatter.format_directory
(the path exercised by generate()) with use_type_checking_imports=False; in the
new test (similar to
test_format_code_ruff_check_formatter_without_type_checking_imports) chdir to
tmp_path, create a sample Python file under the directory, patch subprocess.run
(mock.patch("subprocess.run")) to return a stdout, call
formatter.format_directory(tmp_path) and assert formatted output or that
subprocess.run was invoked with arguments matching the Ruff invocation for file
input (file path(s) and cwd set to str(tmp_path)) to cover the modular/batch
branch and protect the issue-3038 path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/cli-reference/quick-reference.md`:
- Line 155: Add the positive CLI flag `--use-type-checking-imports` alongside
the existing `--no-use-type-checking-imports` entry: update the Template
Customization table row to include a parallel entry for
`--use-type-checking-imports` (linking to
template-customization.md#use-type-checking-imports or the same anchor pattern
as the negative flag) and add the `--use-type-checking-imports` entry into the
alphabetical index on the same page (quick-reference.md) so both
BooleanOptionalAction flags are listed consistently; ensure the flag label and
description mirror the negative-flag wording but expressed positively (e.g.,
"Keep generated model imports available at runtime when using Ruff fixes.") and
match formatting of other flag entries.

In `@src/datamodel_code_generator/arguments.py`:
- Around line 421-427: The flag "--use-type-checking-imports" is registered on
model_options but belongs to the template/formatter group; move the add_argument
call so it registers on template_options instead of model_options. Locate the
existing model_options.add_argument(...) invocation for
"--use-type-checking-imports" and change it to
template_options.add_argument(...), preserving the help text,
action=BooleanOptionalAction and default=None so CLI behavior and docs remain
the same.

In `@src/datamodel_code_generator/format.py`:
- Around line 415-420: The _ruff_check_command currently assumes a "ruff" string
and callers like apply_ruff_lint and format_directory don't pass a resolved
path; change _ruff_check_command to resolve the executable internally by calling
self._find_ruff_path() as the default ruff path (use that value instead of the
literal "ruff"), then build and return the command tuple as before; update the
_ruff_check_command signature/implementation to call self._find_ruff_path() when
no explicit path provided so all callers (apply_ruff_lint, format_directory)
automatically get the correct Ruff executable.

---

Nitpick comments:
In `@docs/cli-reference/template-customization.md`:
- Around line 2345-2351: Add a one-line clarification stating the default
behavior and the paired flag: explain that by default generated model imports
are wrapped in TYPE_CHECKING blocks (i.e., type-checking imports are enabled),
and that --no-use-type-checking-imports disables that behavior while
--use-type-checking-imports explicitly re-enables it; reference the flags
--no-use-type-checking-imports, --use-type-checking-imports and the
TYPE_CHECKING behavior in the same paragraph so readers immediately see the
default and the opt-out/opt-back-in option.

In `@tests/test_format.py`:
- Around line 197-218: Add a companion test that exercises the batch-formatting
path by invoking CodeFormatter.format_directory (the path exercised by
generate()) with use_type_checking_imports=False; in the new test (similar to
test_format_code_ruff_check_formatter_without_type_checking_imports) chdir to
tmp_path, create a sample Python file under the directory, patch subprocess.run
(mock.patch("subprocess.run")) to return a stdout, call
formatter.format_directory(tmp_path) and assert formatted output or that
subprocess.run was invoked with arguments matching the Ruff invocation for file
input (file path(s) and cwd set to str(tmp_path)) to cover the modular/batch
branch and protect the issue-3038 path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5dbeb895-e565-4bd6-aeb1-c4cc97eb26e7

📥 Commits

Reviewing files that changed from the base of the PR and between 8c4bfd1 and 33a2f00.

⛔ Files ignored due to path filters (1)
  • docs/llms-full.txt is excluded by none and included by none
📒 Files selected for processing (18)
  • docs/cli-reference/index.md
  • docs/cli-reference/quick-reference.md
  • docs/cli-reference/template-customization.md
  • src/datamodel_code_generator/__init__.py
  • src/datamodel_code_generator/__main__.py
  • src/datamodel_code_generator/_types/generate_config_dict.py
  • src/datamodel_code_generator/_types/parser_config_dicts.py
  • src/datamodel_code_generator/arguments.py
  • src/datamodel_code_generator/cli_options.py
  • src/datamodel_code_generator/config.py
  • src/datamodel_code_generator/format.py
  • src/datamodel_code_generator/parser/base.py
  • src/datamodel_code_generator/prompt_data.py
  • tests/data/expected/main/input_model/config_class.py
  • tests/data/expected/main/openapi/no_use_type_checking_imports_internal.py
  • tests/main/test_main_general.py
  • tests/main/test_public_api_signature_baseline.py
  • tests/test_format.py

Comment thread docs/cli-reference/quick-reference.md
Comment thread src/datamodel_code_generator/arguments.py Outdated
Comment thread src/datamodel_code_generator/format.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.

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

415-420: ⚠️ Potential issue | 🟠 Major

Resolve Ruff executable lookup inside _ruff_check_command().

apply_ruff_lint() and format_directory() call this helper without ruff_path, so they still fall back to the literal "ruff" while apply_ruff_check_and_format() uses _find_ruff_path(). In environments where Ruff is only installed next to sys.executable, Formatter.RUFF_CHECK and batch format_directory() will fail even though the combined check+format path works.

♻️ Proposed fix
-    def _ruff_check_command(self, *paths: str, ruff_path: str = "ruff") -> tuple[str, ...]:
+    def _ruff_check_command(self, *paths: str, ruff_path: str | None = None) -> tuple[str, ...]:
         """Build the Ruff check command for the current formatter settings."""
+        if ruff_path is None:
+            ruff_path = self._find_ruff_path()
         command: tuple[str, ...] = (ruff_path, "check", "--fix", "--unsafe-fixes")
         if not self.use_type_checking_imports:
             command += ("--unfixable", "TC001,TC002,TC003")
         return (*command, *paths)

Verify that both callers still omit ruff_path while the helper defaults to the literal command. Expected result: the helper signature still shows ruff_path: str = "ruff", and the apply_ruff_lint() / format_directory() call sites do not pass ruff_path=.

#!/bin/bash
set -euo pipefail

sed -n '374,451p' src/datamodel_code_generator/format.py

python - <<'PY'
from pathlib import Path

text = Path("src/datamodel_code_generator/format.py").read_text(encoding="utf-8")
print("helper defaults to literal 'ruff':", 'def _ruff_check_command(self, *paths: str, ruff_path: str = "ruff")' in text)
print("apply_ruff_lint passes ruff_path:", 'self._ruff_check_command("-", ruff_path=' in text)
print("format_directory passes ruff_path:", 'self._ruff_check_command(str(directory), ruff_path=' in text)
PY
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/datamodel_code_generator/format.py` around lines 415 - 420, The helper
_ruff_check_command currently hardcodes the literal "ruff" when callers omit
ruff_path; change its implementation so that if ruff_path is the default "ruff"
(or None) it calls self._find_ruff_path() and uses that resolved path instead,
then build and return the command as before; keep the signature
_ruff_check_command(self, *paths: str, ruff_path: str = "ruff") so
apply_ruff_lint and format_directory can continue to omit ruff_path while
apply_ruff_check_and_format can still pass an explicit value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/datamodel_code_generator/format.py`:
- Around line 415-420: The helper _ruff_check_command currently hardcodes the
literal "ruff" when callers omit ruff_path; change its implementation so that if
ruff_path is the default "ruff" (or None) it calls self._find_ruff_path() and
uses that resolved path instead, then build and return the command as before;
keep the signature _ruff_check_command(self, *paths: str, ruff_path: str =
"ruff") so apply_ruff_lint and format_directory can continue to omit ruff_path
while apply_ruff_check_and_format can still pass an explicit value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0f6d9a3a-348f-40d2-89e3-e00066a475af

📥 Commits

Reviewing files that changed from the base of the PR and between 33a2f00 and 7bc82e1.

📒 Files selected for processing (2)
  • src/datamodel_code_generator/format.py
  • tests/test_format.py

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/main/test_main_general.py (1)

1688-1698: Pin the output model type in these regressions.

These checks are specifically about modular Pydantic runtime imports, but they currently rely on the project default still being a Pydantic model. Adding --output-model-type pydantic_v2.BaseModel would keep them from breaking on an unrelated default change.

Also applies to: 1719-1729, 1741-1751

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

In `@tests/main/test_main_general.py` around lines 1688 - 1698, The tests like
test_type_checking_imports_default_to_runtime_imports_for_modular_pydantic_ruff
(and the similar blocks at the other two locations) assume a Pydantic output
model but depend on the project default; update the run_main_and_assert calls
(the extra_args list) to include "--output-model-type", "pydantic_v2.BaseModel"
so the invocation explicitly pins the output model type and prevents unrelated
default changes from breaking these regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/datamodel_code_generator/format.py`:
- Around line 206-218: The helper resolve_use_type_checking_imports currently
only considers defer_formatting but misses the in-memory multi-module generation
path; update resolve_use_type_checking_imports to accept a new boolean flag
(e.g., in_memory_multi_module) and change the final return so that when
in_memory_multi_module is true and is_pydantic_output and has_ruff it forces
runtime imports (i.e., return False / not TYPE_CHECKING), and then update
callers (generate(...) and Parser.parse()) to pass in_memory_multi_module=True
when output is None or when emitting multiple modules in-memory so Ruff cannot
move model imports into TYPE_CHECKING.

In `@tests/test_format.py`:
- Around line 172-186: Replace the hardcoded "/tmp/venv/bin/ruff" sentinel in
this test with a non-temp neutral constant and reuse it across the test file;
specifically, update the mock return_value for formatter._find_ruff_path and the
expected subprocess.run call to use a shared FAKE_RUFF_PATH (or similar) instead
of "/tmp/venv/bin/ruff" so the test no longer triggers Ruff S108 and avoids
repeating the literal, and update any other tests in this file that use the same
literal to reference the new constant.

---

Nitpick comments:
In `@tests/main/test_main_general.py`:
- Around line 1688-1698: The tests like
test_type_checking_imports_default_to_runtime_imports_for_modular_pydantic_ruff
(and the similar blocks at the other two locations) assume a Pydantic output
model but depend on the project default; update the run_main_and_assert calls
(the extra_args list) to include "--output-model-type", "pydantic_v2.BaseModel"
so the invocation explicitly pins the output model type and prevents unrelated
default changes from breaking these regressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: de4f5150-2f1d-498a-ab15-36733e31a307

📥 Commits

Reviewing files that changed from the base of the PR and between 7bc82e1 and 6605f62.

📒 Files selected for processing (12)
  • src/datamodel_code_generator/__init__.py
  • src/datamodel_code_generator/__main__.py
  • src/datamodel_code_generator/_types/generate_config_dict.py
  • src/datamodel_code_generator/_types/parser_config_dicts.py
  • src/datamodel_code_generator/arguments.py
  • src/datamodel_code_generator/config.py
  • src/datamodel_code_generator/format.py
  • src/datamodel_code_generator/parser/base.py
  • tests/data/expected/main/input_model/config_class.py
  • tests/main/test_main_general.py
  • tests/main/test_public_api_signature_baseline.py
  • tests/test_format.py
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/datamodel_code_generator/config.py
  • src/datamodel_code_generator/main.py
  • src/datamodel_code_generator/arguments.py
  • src/datamodel_code_generator/_types/parser_config_dicts.py
  • tests/data/expected/main/input_model/config_class.py
  • tests/main/test_public_api_signature_baseline.py
  • src/datamodel_code_generator/_types/generate_config_dict.py
  • src/datamodel_code_generator/init.py

Comment thread src/datamodel_code_generator/format.py Outdated
Comment thread tests/test_format.py
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@docs/cli-reference/template-customization.md`:
- Around line 2348-2352: The paragraph is ambiguous about the default in the
multi-module Ruff + Pydantic case; update the text to explicitly state that the
new default behavior preserves runtime imports (i.e., Ruff will not move
generated model imports into TYPE_CHECKING blocks) for modular Pydantic output,
explain the runtime vs. type-checking trade-off, and call out that using
--use-type-checking-imports re-enables TYPE_CHECKING-only imports which restores
the old behavior but requires manual calls to model_rebuild() (or similar) for
cross-module references to work at runtime; mention the flags
--no-use-type-checking-imports and --use-type-checking-imports, TYPE_CHECKING,
Ruff, Pydantic, and model_rebuild() so readers can locate the relevant concepts.

In `@src/datamodel_code_generator/format.py`:
- Around line 217-218: The condition computing has_ruff and the return
expression currently treats both Formatter.RUFF_CHECK and Formatter.RUFF_FORMAT
the same; narrow this to only consider Formatter.RUFF_CHECK so format-only
setups don't trigger the workaround—update the has_ruff expression to test
Formatter.RUFF_CHECK (not RUFF_FORMAT) and keep the return using
is_multi_module_output and preserve_runtime_imports_for_multi_module_ruff
unchanged; look for the has_ruff variable and Formatter.RUFF_CHECK /
Formatter.RUFF_FORMAT in this function and change the boolean check accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: eaea1a38-db6d-457a-8ccc-2c29b57474aa

📥 Commits

Reviewing files that changed from the base of the PR and between 6605f62 and af9bc85.

⛔ Files ignored due to path filters (1)
  • docs/llms-full.txt is excluded by none and included by none
📒 Files selected for processing (14)
  • docs/cli-reference/index.md
  • docs/cli-reference/quick-reference.md
  • docs/cli-reference/template-customization.md
  • src/datamodel_code_generator/__init__.py
  • src/datamodel_code_generator/arguments.py
  • src/datamodel_code_generator/format.py
  • src/datamodel_code_generator/model/base.py
  • src/datamodel_code_generator/model/pydantic_base.py
  • src/datamodel_code_generator/model/pydantic_v2/dataclass.py
  • src/datamodel_code_generator/parser/base.py
  • src/datamodel_code_generator/prompt_data.py
  • tests/data/expected/main/openapi/use_type_checking_imports_internal.py
  • tests/main/test_main_general.py
  • tests/test_format.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/main/test_main_general.py
  • docs/cli-reference/quick-reference.md
  • src/datamodel_code_generator/prompt_data.py

Comment thread docs/cli-reference/template-customization.md Outdated
Comment thread src/datamodel_code_generator/format.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.

🧹 Nitpick comments (1)
tests/main/test_main_general.py (1)

1712-1723: Derive the package name from output_dir instead of hardcoding model.

These runtime checks only need the generated package at output_dir, but hardcoding "model" couples them to the fixture implementation and duplicates the same cleanup logic in both tests.

♻️ Suggested cleanup
+    package_name = output_dir.name
     sys.path.insert(0, str(output_dir.parent))
     importlib.invalidate_caches()
     try:
-        from model._internal import Result
+        Result = importlib.import_module(f"{package_name}._internal").Result

         result = Result.model_validate({"event": {"id": "abc"}})
         assert result.event is not None
         assert result.event.__class__.__name__ == "Event"
     finally:
         sys.path.pop(0)
-        for name in [module for module in sys.modules if module == "model" or module.startswith("model.")]:
+        prefix = f"{package_name}."
+        for name in [module for module in sys.modules if module == package_name or module.startswith(prefix)]:
             del sys.modules[name]

Also applies to: 1782-1793

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

In `@tests/main/test_main_general.py` around lines 1712 - 1723, The test currently
hardcodes the package name "model" when inserting into sys.path, importing
Result and cleaning sys.modules; change it to derive the package name from
output_dir (e.g., pkg_name = output_dir.name or path-based derivation) and use
that variable for the import and cleanup logic so the test targets the generated
package instead of a hardcoded string; update the import statement that
references Result.model_validate and the sys.modules cleanup list to use the
derived pkg_name and also apply the same change to the similar block around
lines 1782-1793.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/main/test_main_general.py`:
- Around line 1712-1723: The test currently hardcodes the package name "model"
when inserting into sys.path, importing Result and cleaning sys.modules; change
it to derive the package name from output_dir (e.g., pkg_name = output_dir.name
or path-based derivation) and use that variable for the import and cleanup logic
so the test targets the generated package instead of a hardcoded string; update
the import statement that references Result.model_validate and the sys.modules
cleanup list to use the derived pkg_name and also apply the same change to the
similar block around lines 1782-1793.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e799250c-44ba-4cb2-a2f1-d00b864e0f34

📥 Commits

Reviewing files that changed from the base of the PR and between af9bc85 and 6623adc.

⛔ Files ignored due to path filters (1)
  • docs/llms-full.txt is excluded by none and included by none
📒 Files selected for processing (5)
  • docs/cli-reference/template-customization.md
  • src/datamodel_code_generator/format.py
  • src/datamodel_code_generator/parser/base.py
  • tests/main/test_main_general.py
  • tests/test_format.py

@koxudaxi koxudaxi merged commit 569894a into main Mar 10, 2026
40 checks passed
@koxudaxi koxudaxi deleted the fix/issue-3038-type-checking-imports branch March 10, 2026 18:40
@github-actions
Copy link
Copy Markdown
Contributor

Breaking Change Analysis

Result: No breaking changes detected

Reasoning: This PR adds new CLI options (--use-type-checking-imports / --no-use-type-checking-imports) and configuration fields, all of which are optional and backward compatible. The default behavior change for multi-module Pydantic output with Ruff formatting is a bug fix that corrects runtime validation failures (issue #3038), not a breaking change. Previously, Ruff would move model imports into TYPE_CHECKING blocks, causing runtime failures because Pydantic couldn't find referenced models for validation. The new default keeps imports at runtime, producing correct working code. Users who want the old behavior can explicitly opt in with --use-type-checking-imports.


This analysis was performed by Claude Code Action

@github-actions
Copy link
Copy Markdown
Contributor

🎉 Released in 0.55.0

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add flag to avoid imports inside TYPE_CHECKING blocks for pydantic

1 participant