Skip to content

Improve CLI startup time with lazy imports#2799

Merged
koxudaxi merged 17 commits intomainfrom
perf/lazy-imports
Dec 25, 2025
Merged

Improve CLI startup time with lazy imports#2799
koxudaxi merged 17 commits intomainfrom
perf/lazy-imports

Conversation

@koxudaxi
Copy link
Copy Markdown
Owner

@koxudaxi koxudaxi commented Dec 25, 2025

Summary by CodeRabbit

  • New Features

    • CLI fast-paths for quick version/help/prompt output; opt-in runtime patch application.
  • Improvements

    • Centralized enums/constants and memoized Pydantic version detection for faster startup.
    • Deferred/lazy loading for YAML, templating, autocomplete and debug tooling; clearer error guidance when debug tooling is missing.
    • On-demand, configurable code formatting and fewer None-valued fields emitted in several serialization paths.
    • Performance tweaks and caching to reduce import and rendering overhead.
  • Tests

    • Tests updated to reflect version-detection and debug-tooling import behavior.

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 25, 2025

📝 Walkthrough

Walkthrough

Replaces module-level PYDANTIC_V2 with runtime is_pydantic_v2() checks, centralizes enums into enums.py, lazily loads optional deps (YAML, pysnooper, Jinja2, tomllib/tomli, argcomplete), moves pydantic patching into apply_patch(), adds CLI fast-paths, and adds caching/fast-paths in model rendering. (34 words)

Changes

Cohort / File(s) Summary
Core enums & constants
src/datamodel_code_generator/enums.py
New centralized enums and constants (e.g., InputFileType, DataModelType, StrictTypes, MIN_VERSION, MAX_VERSION, DEFAULT_SHARED_MODULE_NAME, UnionMode) and exported __all__.
Top-level & CLI fast-paths
src/datamodel_code_generator/__init__.py, src/datamodel_code_generator/__main__.py
Replace in-module enum definitions by imports from enums.py; replace PYDANTIC_V2 usage with is_pydantic_v2(); add CLI fast-paths (--version, --help, --generate-prompt) to avoid heavy imports; guard argcomplete activation.
Runtime/version utilities & lazy proxies
src/datamodel_code_generator/util.py, src/datamodel_code_generator/types.py
Add get_pydantic_version(), is_pydantic_v2(), get_safe_loader(); lazy expose BaseModel, SafeLoader and ConfigDict via __getattr__/proxies; lazy StrictTypes via module __getattr__.
Model rendering, caching & Jinja2 lazy-load
src/datamodel_code_generator/model/base.py, src/datamodel_code_generator/model/pydantic/__init__.py, src/datamodel_code_generator/model/pydantic_v2/__init__.py
Defer Jinja2 imports to call sites; add _dedup_key_cache and caching for get_dedup_key; add fast-paths for imports collection and template/env loading; swap PYDANTIC_V2 checks for is_pydantic_v2().
Pydantic v2 base model enum move
src/datamodel_code_generator/model/pydantic_v2/base_model.py
Removed local UnionMode enum and enum import (moved into enums.py).
Import/fast-path optimizations
src/datamodel_code_generator/model/pydantic/base_model.py
Add fast-path in DataModelField.imports to skip expensive checks for simple required fields.
Reference / parsers adjustments
src/datamodel_code_generator/reference.py, src/datamodel_code_generator/parser/jsonschema.py, src/datamodel_code_generator/parser/openapi.py
Replace PYDANTIC_V2 with is_pydantic_v2(); some model_dump calls now use exclude_none=True; adjust v2 model_config branches.
Pydantic patching (lazy)
src/datamodel_code_generator/pydantic_patch.py
New apply_patch() to lazily apply evaluate_forwardref patch (guarded for Python 3.12+); removes unconditional top-level patching.
Formatting API changes
src/datamodel_code_generator/format.py
CodeFormatter.__init__ signature extended with formatter controls (e.g., wrap_string_literal, skip_string_normalization, known_third_party, custom_formatters, encoding, formatters, defer_formatting) and conditional per-formatter config loading for Black/Isort.
Tests & pysnooper handling
tests/main/conftest.py, tests/main/test_exec_validation.py, tests/main/test_main_general.py
Tests updated to use is_pydantic_v2(); pysnooper absence simulated via sys.modules mapping; skip logic adjusted accordingly.
Minor import/source moves
src/datamodel_code_generator/arguments.py, src/datamodel_code_generator/model/pydantic_v2/__init__.py, src/datamodel_code_generator/model/pydantic/base_model.py
Move enums into enums.py; replace direct constant checks with is_pydantic_v2(); defer heavy imports where appropriate.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant CLI as CLI (__main__.py)
  participant Init as Top-level (__init__.py)
  participant Enums as enums.py
  participant Util as util (is_pydantic_v2/get_safe_loader)
  participant Pyd as Pydantic (BaseModel/model_dump)

  CLI->>CLI: parse args (--version/--help/--generate-prompt)
  alt fast-path requested
    CLI-->>CLI: print info & exit (no heavy imports)
    note right of CLI `#D1F2D6`: lightweight startup path
  else normal startup
    CLI->>Init: import package
    Init->>Enums: import enums (fast, no pydantic)
    Init->>Util: call is_pydantic_v2() (lazy)
    Util->>Pyd: lazily import pydantic if needed
    Util-->>Init: provide ConfigDict/BaseModel proxies
    Init->>Pyd: instantiate models / call model_dump (v2 path uses model_dump)
    note right of Pyd `#F2E5D1`: model_dump/model_validate invoked conditionally
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

breaking-change-analyzed

Suggested reviewers

  • ilovelinux

Poem

🐰 I hopped through enums and lazy loads with care,

Flags became functions, imports light as air.
CLI skips heavy steps when version's requested,
Patches wait quietly, only applied when tested.
🥕 Hooray — a nimble startup, spry and well-rested.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Improve CLI startup time with lazy imports' directly and accurately summarizes the main objective: implementing lazy imports to enhance CLI startup performance. The title is concise, specific, and clearly reflects the primary change across the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 87.30% 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 perf/lazy-imports

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0df34d8 and edc0fdb.

📒 Files selected for processing (6)
  • src/datamodel_code_generator/__init__.py
  • src/datamodel_code_generator/model/base.py
  • src/datamodel_code_generator/parser/jsonschema.py
  • src/datamodel_code_generator/reference.py
  • src/datamodel_code_generator/types.py
  • src/datamodel_code_generator/util.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/datamodel_code_generator/parser/jsonschema.py
  • src/datamodel_code_generator/reference.py
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2799
File: src/datamodel_code_generator/model/pydantic/__init__.py:43-43
Timestamp: 2025-12-25T09:22:14.661Z
Learning: In datamodel-code-generator project, defensive `# noqa: PLC0415` directives should be kept on lazy imports (imports inside functions/methods) even when Ruff reports them as unused via RUF100, to prepare for potential future Ruff configuration changes that might enable the import-outside-top-level rule.
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2799
File: src/datamodel_code_generator/util.py:49-66
Timestamp: 2025-12-25T09:22:57.664Z
Learning: In datamodel-code-generator, the is_pydantic_v2() and is_pydantic_v2_11() functions in src/datamodel_code_generator/util.py intentionally use global variable caching (_is_v2, _is_v2_11) on top of lru_cache for performance optimization. This dual-layer caching eliminates function call overhead and cache lookup overhead for frequently-called version checks. The PLW0603 linter warnings should be suppressed with # noqa: PLW0603 as this is a deliberate design choice.
📚 Learning: 2025-12-25T09:22:57.664Z
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2799
File: src/datamodel_code_generator/util.py:49-66
Timestamp: 2025-12-25T09:22:57.664Z
Learning: In datamodel-code-generator, the is_pydantic_v2() and is_pydantic_v2_11() functions in src/datamodel_code_generator/util.py intentionally use global variable caching (_is_v2, _is_v2_11) on top of lru_cache for performance optimization. This dual-layer caching eliminates function call overhead and cache lookup overhead for frequently-called version checks. The PLW0603 linter warnings should be suppressed with # noqa: PLW0603 as this is a deliberate design choice.

Applied to files:

  • src/datamodel_code_generator/util.py
  • src/datamodel_code_generator/__init__.py
  • src/datamodel_code_generator/model/base.py
  • src/datamodel_code_generator/types.py
📚 Learning: 2025-12-25T09:22:14.661Z
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2799
File: src/datamodel_code_generator/model/pydantic/__init__.py:43-43
Timestamp: 2025-12-25T09:22:14.661Z
Learning: In datamodel-code-generator project, defensive `# noqa: PLC0415` directives should be kept on lazy imports (imports inside functions/methods) even when Ruff reports them as unused via RUF100, to prepare for potential future Ruff configuration changes that might enable the import-outside-top-level rule.

Applied to files:

  • src/datamodel_code_generator/util.py
📚 Learning: 2025-12-18T13:43:16.235Z
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:16.235Z
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/util.py
🧬 Code graph analysis (4)
src/datamodel_code_generator/util.py (1)
src/datamodel_code_generator/pydantic_patch.py (1)
  • apply_patch (24-29)
src/datamodel_code_generator/__init__.py (2)
src/datamodel_code_generator/enums.py (10)
  • AllExportsCollisionStrategy (92-102)
  • AllExportsScope (81-89)
  • AllOfMergeMode (142-152)
  • CollapseRootModelsNameStrategy (131-139)
  • FieldTypeCollisionStrategy (105-113)
  • GraphQLScope (155-158)
  • ModuleSplitMode (172-178)
  • NamingStrategy (116-128)
  • ReadOnlyWriteOnlyModelType (161-169)
  • TargetPydanticVersion (181-189)
src/datamodel_code_generator/util.py (6)
  • is_pydantic_v2 (52-57)
  • inner (151-151)
  • inner (154-154)
  • inner (157-157)
  • inner (159-170)
  • inner (182-189)
src/datamodel_code_generator/model/base.py (2)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (29-54)
src/datamodel_code_generator/util.py (4)
  • is_pydantic_v2 (52-57)
  • model_copy (275-279)
  • model_dump (254-258)
  • model_validate (261-265)
src/datamodel_code_generator/types.py (3)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (29-54)
src/datamodel_code_generator/util.py (1)
  • is_pydantic_v2 (52-57)
src/datamodel_code_generator/enums.py (1)
  • StrictTypes (199-206)
🪛 GitHub Check: CodeQL
src/datamodel_code_generator/__init__.py

[notice] 158-158: Unused global variable
The global variable '_pysnooper_default_state_set' is not used.

🪛 Ruff (0.14.10)
src/datamodel_code_generator/util.py

36-36: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


40-40: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


41-41: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


54-54: Unused noqa directive (non-enabled: PLW0603)

Remove unused noqa directive

(RUF100)


80-80: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


82-82: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


197-197: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


215-215: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


218-218: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


232-232: Unused noqa directive (non-enabled: PLW0603)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/__init__.py

72-72: Unused noqa directive (non-enabled: E402)

Remove unused noqa directive

(RUF100)


95-95: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


97-97: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


128-128: Unused noqa directive (non-enabled: PLW0603)

Remove unused noqa directive

(RUF100)


130-130: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


149-149: Unused noqa directive (non-enabled: PLW0603)

Remove unused noqa directive

(RUF100)


151-151: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


918-918: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/model/base.py

446-446: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


483-483: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


720-720: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/types.py

93-93: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


185-185: Unused noqa directive (non-enabled: PLR2004)

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). (4)
  • GitHub Check: benchmarks
  • GitHub Check: py312-black22 on Ubuntu
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.12 on Windows
🔇 Additional comments (27)
src/datamodel_code_generator/model/base.py (9)

39-47: LGTM! Import changes align with lazy loading pattern.

The addition of is_pydantic_v2 and version-compatible utilities (model_copy, model_dump, model_validate) supports the migration away from the PYDANTIC_V2 constant. The TYPE_CHECKING imports for jinja2 types are appropriate for type annotations while keeping runtime imports lazy.

Note: The static analysis tool flags unused noqa: PLC0415 directives on lazy imports (lines 446, 483, 720), but these should be retained as per project learnings to prepare for potential future Ruff configuration changes.


79-90: LGTM! Version check correctly migrated.

The replacement of PYDANTIC_V2 with is_pydantic_v2() is correct and maintains the same branching logic for model configuration.


208-221: LGTM! Caching optimization improves performance.

The caching implementation for self_reference() is well-designed. Using __dict__ directly avoids Pydantic v1's field assignment restrictions while providing efficient memoization for this frequently-called method. The detailed docstring clearly explains the caching strategy.


297-322: LGTM! Fast-path optimization improves performance.

The fast-path check (lines 304-306) efficiently handles the common case where no special typing imports (Union, Optional, Annotated) are needed, avoiding expensive iteration through data type imports. This optimization complements the PR's startup performance goals.


443-461: LGTM! Lazy jinja2 imports improve startup time.

The lazy import pattern correctly defers jinja2 loading until template rendering is actually needed. The @lru_cache decorator ensures the environment is created only once.

Note: The noqa: PLC0415 directive on line 446 is intentional and should be retained as per project learnings, even though static analysis flags it as unused.

Based on learnings, defensive noqa directives for lazy imports should be kept.


480-505: LGTM! Consistent lazy import pattern.

The lazy jinja2 imports follow the same pattern as _get_environment, maintaining consistency. The implementation correctly handles absolute template paths with fallback to built-in directories.

Note: The noqa: PLC0415 directive on line 483 should be retained as per project learnings.

Based on learnings, defensive noqa directives for lazy imports should be kept.


709-726: LGTM! Dedup key caching reduces redundant computations.

The per-instance caching of dedup keys is well-designed, avoiding expensive render() and imports calls for repeated lookups with the same (class_name, use_default) combination. The docstring clearly explains the optimization rationale.

Note: The noqa: PLC0415 directive on line 720 should be retained as per project learnings.

Based on learnings, defensive noqa directives for lazy imports should be kept.


683-683: LGTM! Cache initialization is correct.

The _dedup_key_cache initialization properly sets up the per-instance caching structure used by get_dedup_key().


901-910: LGTM! Version-aware model rebuilding correctly implemented.

The conditional model rebuilding logic correctly branches based on is_pydantic_v2(), though both branches perform identical operations in this case (likely for future-proofing or clarity).

src/datamodel_code_generator/types.py (5)

90-98: LGTM! Lazy StrictTypes exposure maintains backward compatibility.

The __getattr__ implementation provides lazy access to StrictTypes for backward compatibility while deferring the import until actually needed. The error handling for unknown attributes is appropriate.

Note: The noqa: PLC0415 directive on line 93 should be retained as per project learnings.

Based on learnings, defensive noqa directives for lazy imports should be kept.


100-107: LGTM! TYPE_CHECKING import pattern is correct.

The TYPE_CHECKING import of StrictTypes provides proper type annotations while the runtime access is handled through the __getattr__ lazy exposure. This is the recommended pattern for lazy module-level attributes.


180-187: LGTM! Optimization improves performance for common case.

The fast-path optimization for exactly 2 iterables (lines 185-186) avoids the overhead of chain() for the most common case. The docstring clearly explains the optimization rationale.

Note: The noqa: PLR2004 directive on line 185 appears to suppress a "magic number" warning for the literal 2. While the static analysis flags it as unused, it may be defensive similar to the PLC0415 directives.


109-112: LGTM! Version-aware conditional imports are correct.

The conditional import of Pydantic v2-specific modules (GetCoreSchemaHandler, core_schema) based on is_pydantic_v2() is appropriate and prevents import errors on v1 installations.


296-321: LGTM! DataType version-aware configuration is correct.

The conditional model configuration based on is_pydantic_v2() properly separates v1 and v2 setup, including the v1-specific model_rebuild method definition.

src/datamodel_code_generator/util.py (7)

32-46: LGTM! Lazy version detection improves startup performance.

The get_pydantic_version() function correctly defers pydantic import until version information is actually needed. The patch application (lines 36-38) before importing pydantic is critical for Python 3.12+ compatibility. The @lru_cache(maxsize=1) ensures the version is computed only once.

Note: The noqa: PLC0415 directives on lines 36, 40, and 41 should be retained as per project learnings.

Based on learnings, defensive noqa directives for lazy imports should be kept.


49-57: LGTM! Global caching is intentional for performance optimization.

The dual-layer caching (global variable _is_v2 on top of get_pydantic_version()'s lru_cache) is a deliberate performance optimization that eliminates function call overhead and cache lookup overhead for these frequently-called version checks.

As per project learnings: The noqa: PLW0603 directive on line 54 suppresses the global statement warning intentionally, as this caching pattern is a deliberate design choice for hot-path code.

Based on learnings, this global variable caching pattern is intentional for performance.


76-107: LGTM! Subclassing approach is superior to deepcopy.

The refactored get_safe_loader() now uses subclassing (lines 84-95) which is the standard and safer approach compared to the previous deepcopy pattern. The class-level copies of yaml_constructors and yaml_implicit_resolvers properly isolate the custom loader from the base SafeLoader.

Note: The noqa: PLC0415 directives on lines 80 and 82 should be retained as per project learnings.

Based on learnings, defensive noqa directives for lazy imports should be kept.


194-209: LGTM! ConfigDict proxy enables lazy loading.

The proxy pattern allows ConfigDict(**kwargs) syntax while deferring the actual import until first use. The @lru_cache(maxsize=1) ensures the type is loaded only once.

Note: The noqa: PLC0415 directive on line 197 should be retained as per project learnings.

Based on learnings, defensive noqa directives for lazy imports should be kept.


212-224: LGTM! Lazy BaseModel loading with version-aware configuration.

The lazy loading of BaseModel with version-specific configuration is well-implemented. For v2, it creates a subclass with strict=False config, while v1 uses the base class directly.

Note: The noqa: PLC0415 directives on lines 215 and 218 should be retained as per project learnings.

Based on learnings, defensive noqa directives for lazy imports should be kept.


227-240: LGTM! Lazy attribute exposure maintains backward compatibility.

The __getattr__ implementation provides lazy access to BaseModel and SafeLoader, deferring their initialization until first use. The global caching for _BaseModel (line 232) follows the same performance optimization pattern as is_pydantic_v2().

Note: The noqa: PLW0603 directive on line 232 is intentional for performance optimization, consistent with the pattern established for is_pydantic_v2().

Based on learnings, global variable caching with noqa: PLW0603 is intentional for performance.


254-279: LGTM! Utility functions correctly migrated to is_pydantic_v2().

All version-compatible utility functions (model_dump, model_validate, get_fields_set, model_copy) correctly use is_pydantic_v2() to branch between v1 and v2 API methods.

src/datamodel_code_generator/__init__.py (6)

28-47: LGTM! Enum centralization improves organization.

The imports from datamodel_code_generator.enums centralize public enum definitions, improving code organization and maintaining backward compatibility through the public API surface.


71-81: LGTM! Version-aware YamlValue type definition is correct.

The conditional type definition for YamlValue properly handles the difference between Pydantic v1 (which cannot handle TypeAliasType for recursive types) and v2 (which can). The v1 fallback to Any for recursive parts is a necessary workaround.

Note: The noqa: E402 on line 72 suppresses the "module level import not at top of file" warning, which appears intentional given the import dependencies.


93-99: LGTM! Lazy YAML imports improve startup performance.

The lazy imports of yaml and SafeLoader defer YAML library loading until actually needed, supporting the PR's startup performance goals.

Note: The noqa: PLC0415 directives on lines 95 and 97 should be retained as per project learnings.

Based on learnings, defensive noqa directives for lazy imports should be kept.


126-136: LGTM! Lazy pysnooper import with helpful error message.

The lazy import of pysnooper (line 130) defers loading until debug mode is explicitly enabled. The error message (line 135) helpfully guides users to install the debug extras if pysnooper is not available.

Note: The noqa: PLC0415 directive on line 130 and noqa: PLW0603 on line 128 should be retained as per project learnings.

Based on learnings, defensive noqa directives for lazy imports and global variables should be kept.


142-168: LGTM! Lazy pysnooper initialization with proper state tracking.

The decorator correctly implements lazy pysnooper loading and prevents redundant re-initialization of the disabled state when applied to multiple classes. The _pysnooper_default_state_set flag is properly used (checked on line 156, set on line 158).

Note:

  • The noqa: PLC0415 directive on line 151 and noqa: PLW0603 on line 149 should be retained as per project learnings.
  • The CodeQL warning about _pysnooper_default_state_set being unused is a false positive—the variable is clearly used in the logic.

Based on learnings, defensive noqa directives for lazy imports and global variables should be kept.


916-934: LGTM! Lazy yaml.parser import is appropriate.

The lazy import of yaml.parser (lines 918-919) is used for the ParserError exception handling on line 922, deferring the import until the function is called.

Note: The noqa: PLC0415 directive on line 918 should be retained as per project learnings.

Based on learnings, defensive noqa directives for lazy imports should be kept.


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.

Comment thread src/datamodel_code_generator/__init__.py Fixed
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Dec 25, 2025

CodSpeed Performance Report

Merging #2799 will not alter performance

Comparing perf/lazy-imports (edc0fdb) with main (8c7550c)

Summary

✅ 73 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.

@codecov
Copy link
Copy Markdown

codecov Bot commented Dec 25, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.51%. Comparing base (a3ea978) to head (edc0fdb).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2799      +/-   ##
==========================================
+ Coverage   99.46%   99.51%   +0.04%     
==========================================
  Files          88       89       +1     
  Lines       13564    13715     +151     
  Branches     1600     1613      +13     
==========================================
+ Hits        13491    13648     +157     
  Misses         36       36              
+ Partials       37       31       -6     
Flag Coverage Δ
unittests 99.51% <100.00%> (+0.04%) ⬆️

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.

Comment thread src/datamodel_code_generator/__main__.py Fixed
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

Caution

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

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

428-447: Lazy Jinja2 imports are fine; clean up unused noqa and consider deduplicating the rebuild block

  • Moving ChoiceLoader, Environment, FileSystemLoader, and select_autoescape imports into _get_environment and _get_environment_with_absolute_path achieves the lazy-import goal without changing behavior.
  • Ruff’s RUF100 flags the # noqa: PLC0415 comments on these import lines; since PLC0415 isn’t enabled anymore, those suppressions can be dropped.
  • The final if is_pydantic_v2(): ... else: ... block runs the same model_rebuild calls in both branches, so the conditional is redundant and can be simplified to a single branch.
Suggested diff for RUF100 and redundant if/else
-    from jinja2 import ChoiceLoader, Environment, FileSystemLoader, select_autoescape  # noqa: PLC0415
+    from jinja2 import ChoiceLoader, Environment, FileSystemLoader, select_autoescape
@@
-    from jinja2 import ChoiceLoader, Environment, FileSystemLoader, select_autoescape  # noqa: PLC0415
+    from jinja2 import ChoiceLoader, Environment, FileSystemLoader, select_autoescape
@@
-if is_pydantic_v2():
-    _rebuild_namespace = {"Union": Union, "DataModelFieldBase": DataModelFieldBase, "DataType": DataType}
-    DataType.model_rebuild(_types_namespace=_rebuild_namespace)
-    BaseClassDataType.model_rebuild(_types_namespace=_rebuild_namespace)
-    DataModelFieldBase.model_rebuild(_types_namespace={"DataModel": DataModel})
-else:
-    _rebuild_namespace = {"Union": Union, "DataModelFieldBase": DataModelFieldBase, "DataType": DataType}
-    DataType.model_rebuild(_types_namespace=_rebuild_namespace)
-    BaseClassDataType.model_rebuild(_types_namespace=_rebuild_namespace)
-    DataModelFieldBase.model_rebuild(_types_namespace={"DataModel": DataModel})
+_rebuild_namespace = {"Union": Union, "DataModelFieldBase": DataModelFieldBase, "DataType": DataType}
+DataType.model_rebuild(_types_namespace=_rebuild_namespace)
+BaseClassDataType.model_rebuild(_types_namespace=_rebuild_namespace)
+DataModelFieldBase.model_rebuild(_types_namespace={"DataModel": DataModel})

Also applies to: 466-477, 493-497

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

94-94: Clean up unused noqa directives.

Ruff reports that the # noqa: PLC0415 directives on these lines are unused because the PLC0415 rule (import outside top-level) is not enabled in your configuration. These can be safely removed.

🔎 Remove unused noqa directives
-    import yaml  # noqa: PLC0415
+    import yaml
 
-    from datamodel_code_generator.util import SafeLoader  # noqa: PLC0415
+    from datamodel_code_generator.util import SafeLoader

Apply similar changes to lines 129, 150, and 1074.

Also applies to: 96-96, 129-129, 150-150, 1074-1074

src/datamodel_code_generator/__main__.py (1)

7-7: Clean up unused noqa directives.

Ruff reports that the # noqa: PLR2004 directive on line 7 and # noqa: PLC0415 on line 888 are unused. These can be safely removed since the corresponding rules are not enabled.

Also applies to: 888-888

src/datamodel_code_generator/util.py (2)

36-36: Clean up unused noqa directives.

Ruff reports that multiple # noqa: PLC0415 and # noqa: PLW0603 directives are unused because the corresponding rules are not enabled in your configuration. These can be safely removed.

Also applies to: 40-41, 78-78, 81-81, 83-83, 199-199, 221-221, 224-224, 238-238


32-56: Consider documenting the lazy loading strategy.

The lazy loading approach with @lru_cache and on-demand imports is excellent for performance, but it introduces subtle complexity. Consider adding a brief module-level docstring or comment explaining the lazy loading strategy to help future maintainers understand why imports are scattered and why @lru_cache is used.

Example:

"""Utility functions and Pydantic version compatibility helpers.

This module uses lazy loading extensively to minimize CLI startup time:
- Version detection is cached with @lru_cache and only happens on first use
- Pydantic imports are deferred until needed
- Module attributes (BaseModel, SafeLoader) are loaded via __getattr__
"""
📜 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 5aeb0b6 and d23f339.

📒 Files selected for processing (13)
  • src/datamodel_code_generator/__init__.py
  • src/datamodel_code_generator/__main__.py
  • src/datamodel_code_generator/model/base.py
  • src/datamodel_code_generator/model/pydantic/__init__.py
  • src/datamodel_code_generator/model/pydantic_v2/__init__.py
  • src/datamodel_code_generator/parser/jsonschema.py
  • src/datamodel_code_generator/pydantic_patch.py
  • src/datamodel_code_generator/reference.py
  • src/datamodel_code_generator/types.py
  • src/datamodel_code_generator/util.py
  • tests/main/conftest.py
  • tests/main/test_exec_validation.py
  • tests/main/test_main_general.py
🧰 Additional context used
🧬 Code graph analysis (11)
src/datamodel_code_generator/parser/jsonschema.py (1)
src/datamodel_code_generator/util.py (8)
  • is_pydantic_v2 (49-51)
  • model_copy (281-285)
  • model_dump (260-264)
  • model_validate (267-271)
  • model_validator (116-121)
  • model_validator (125-131)
  • model_validator (135-139)
  • model_validator (142-173)
src/datamodel_code_generator/__init__.py (1)
src/datamodel_code_generator/util.py (6)
  • is_pydantic_v2 (49-51)
  • inner (152-152)
  • inner (155-155)
  • inner (158-158)
  • inner (160-171)
  • inner (183-190)
src/datamodel_code_generator/__main__.py (1)
src/datamodel_code_generator/util.py (1)
  • is_pydantic_v2 (49-51)
src/datamodel_code_generator/model/base.py (2)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (27-52)
src/datamodel_code_generator/util.py (4)
  • is_pydantic_v2 (49-51)
  • model_copy (281-285)
  • model_dump (260-264)
  • model_validate (267-271)
tests/main/test_main_general.py (1)
tests/main/conftest.py (1)
  • run_main_with_args (215-241)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
src/datamodel_code_generator/util.py (1)
  • is_pydantic_v2 (49-51)
src/datamodel_code_generator/reference.py (2)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (27-52)
src/datamodel_code_generator/util.py (6)
  • camel_to_snake (254-257)
  • is_pydantic_v2 (49-51)
  • model_validator (116-121)
  • model_validator (125-131)
  • model_validator (135-139)
  • model_validator (142-173)
src/datamodel_code_generator/model/pydantic/__init__.py (1)
src/datamodel_code_generator/util.py (1)
  • is_pydantic_v2 (49-51)
tests/main/test_exec_validation.py (1)
src/datamodel_code_generator/util.py (1)
  • is_pydantic_v2 (49-51)
src/datamodel_code_generator/types.py (2)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (27-52)
src/datamodel_code_generator/util.py (1)
  • is_pydantic_v2 (49-51)
tests/main/conftest.py (2)
src/datamodel_code_generator/util.py (1)
  • is_pydantic_v2 (49-51)
src/datamodel_code_generator/__init__.py (1)
  • DataModelType (236-244)
🪛 GitHub Actions: Lint
src/datamodel_code_generator/__main__.py

[error] 1-1: ruff formatting check failed. 2 files were modified by hooks; process exited with code 1. See diff for file changes. The failing step was 'prek run --all-files'.

🪛 Ruff (0.14.10)
src/datamodel_code_generator/__init__.py

94-94: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


96-96: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


127-127: Unused noqa directive (non-enabled: PLW0603)

Remove unused noqa directive

(RUF100)


129-129: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


148-148: Unused noqa directive (non-enabled: PLW0603)

Remove unused noqa directive

(RUF100)


150-150: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


1074-1074: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/__main__.py

7-7: Unused noqa directive (non-enabled: PLR2004)

Remove unused noqa directive

(RUF100)


888-888: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/model/base.py

431-431: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


468-468: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/model/pydantic_v2/__init__.py

48-48: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/model/pydantic/__init__.py

43-43: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/pydantic_patch.py

27-27: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/util.py

36-36: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


40-40: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


41-41: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


78-78: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


81-81: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


83-83: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


199-199: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


221-221: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


224-224: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


238-238: Unused noqa directive (non-enabled: PLW0603)

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). (20)
  • GitHub Check: py312-black23 on Ubuntu
  • GitHub Check: 3.10 on Windows
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: 3.10 on macOS
  • GitHub Check: 3.13 on Windows
  • GitHub Check: py312-black24 on Ubuntu
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.10 on Ubuntu
  • GitHub Check: py312-black22 on Ubuntu
  • GitHub Check: py312-pydantic1 on Ubuntu
  • GitHub Check: 3.11 on macOS
  • GitHub Check: py312-isort6 on Ubuntu
  • GitHub Check: 3.11 on Ubuntu
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.14 on Windows
  • GitHub Check: 3.12 on Ubuntu
  • GitHub Check: 3.13 on macOS
  • GitHub Check: 3.13 on Ubuntu
  • GitHub Check: Analyze (python)
  • GitHub Check: benchmarks
🔇 Additional comments (27)
src/datamodel_code_generator/pydantic_patch.py (2)

24-29: Guarded patching logic looks correct and idempotent

The Python ≥3.12 guard and local import of pydantic.typing make the patch safe and only applied when needed; repeated calls are harmless.


27-27: Remove unused # noqa: PLC0415 to satisfy Ruff

Ruff reports RUF100 here because PLC0415 is not an enabled rule. You can drop the # noqa: PLC0415 comment without changing behavior.

Suggested diff
-        import pydantic.typing  # noqa: PLC0415
+        import pydantic.typing
⛔ 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:16.235Z
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/conftest.py (1)

22-22: Importing is_pydantic_v2() keeps test skip logic aligned with runtime Pydantic version

Using the shared helper instead of a constant matches the new version-detection strategy across the codebase.

tests/main/test_main_general.py (1)

52-56: Updated pysnooper-missing simulation matches lazy import approach

Switching to mocker.patch.dict("sys.modules", {"pysnooper": None}) is consistent with a design that checks for pysnooper presence via imports or sys.modules rather than a fixed module attribute, and keeps the tests aligned with the new lazy-import behavior.

Also applies to: 58-64

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

48-48: Remove unused # noqa: PLC0415 to fix Ruff warning

Same as in the v1 module, the PLC0415 suppression is not needed and triggers RUF100.

Suggested diff
-        from datamodel_code_generator.util import is_pydantic_v2  # noqa: PLC0415
+        from datamodel_code_generator.util import is_pydantic_v2
⛔ 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:16.235Z
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.

46-52: ConfigDict dict() implementation correctly uses Pydantic v2 model_dump()

The version-agnostic pattern is sound. Pydantic v2 recommends model_dump() over the deprecated dict() method for dict representation. The # noqa: PLC0415 is necessary because the import inside the method is indeed a local import and would trigger the rule without suppression.

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

80-89: Pydantic v1/v2 switching in JsonSchemaObject and ConfigDict usage is correctly implemented

  • Importing is_pydantic_v2 and gating the ConfigDict import on it avoids importing v2-only symbols under Pydantic v1.
  • The JsonSchemaObject helpers split cleanly:
    • v2 path: get_fields() returns model_fields
    • v1 path: get_fields() returns __fields__ and model_rebuild() delegates to update_forward_refs()
  • The model_config vs inner Config split allows EXCLUDE_FIELD_KEYS to build from JsonSchemaObject.get_fields() for both versions, and the constant is properly used in __init__ to filter extras.

This is a reasonable and internally consistent way to centralize Pydantic-version-specific behavior in the parser.

tests/main/test_exec_validation.py (1)

16-18: Runtime Pydantic v2 check for exec-validation tests is correct

Using is_pydantic_v2() in _SKIP_PYDANTIC properly checks the currently installed Pydantic major version at runtime through dynamic import within get_pydantic_version(), ensuring tests are automatically skipped on environments without Pydantic v2.

src/datamodel_code_generator/model/pydantic/__init__.py (1)

39-47: Version-aware dict() implementation is sound

The lazy import of is_pydantic_v2 inside the method and conditional delegation to model_dump(**kwargs) for Pydantic v2 keeps this helper working across versions. The # noqa: PLC0415 is correct and necessary—it suppresses the lazy import style violation, which is acceptable here to avoid hard top-level version dependencies.

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

39-40: Pydantic-compatibility and deep-copy changes follow the recommended compatibility layer

  • Importing ConfigDict, is_pydantic_v2, and the model_* helpers centralizes version-aware behavior and matches Pydantic's recommended approach for dual v1/v2 support.
  • ConstraintsBase and DataModelFieldBase correctly choose between model_config = ConfigDict(...) (v2) and an inner Config class (v1), following Pydantic's migration patterns.
  • The v1-only DataModelFieldBase.model_rebuild() override that delegates to update_forward_refs() keeps forward-ref behavior compatible with Pydantic v1.
  • has_constraints, merge_constraints, and copy_deep() all funnel through helper functions (model_dump, model_copy, model_validate) that correctly call v2 methods when available and fall back to v1 equivalents, avoiding direct .dict()/.copy() calls.
  • The helper functions in util.py implement the recommended shim pattern: calling v2 names (model_dump, model_validate, model_copy) if present, else falling back to v1 names (dict, parse_obj, copy).

This approach aligns with Pydantic's official guidance for supporting both major versions without a full migration.

src/datamodel_code_generator/types.py (3)

55-55: LGTM! Runtime version detection enables lazy loading.

The switch from importing a static PYDANTIC_V2 constant to the runtime function is_pydantic_v2() aligns with the PR objective of improving CLI startup time through lazy imports. This change allows the Pydantic version check to be deferred until actually needed.


97-99: LGTM! Conditional import properly gates v2-only code.

The runtime check correctly guards the import of Pydantic v2-specific modules (GetCoreSchemaHandler and core_schema), preventing import errors when running with Pydantic v1.


289-295: LGTM! Config selection uses runtime version check.

The model configuration now properly uses is_pydantic_v2() to select between Pydantic v2's model_config and v1's Config class at runtime.

src/datamodel_code_generator/__init__.py (3)

94-98: LGTM! YAML lazy loading improves startup time.

The local import of yaml and SafeLoader defers the YAML library loading until load_yaml() is actually called, reducing initial import overhead.


125-135: LGTM! Proper error handling for optional pysnooper dependency.

The lazy import with a clear error message guides users to install the debug extra when needed, while the exception chaining preserves the original error context.


1074-1074: LGTM! yaml.parser lazy import defers dependency loading.

The local import of yaml.parser reduces initial startup overhead by loading it only when needed for input type inference.

src/datamodel_code_generator/__main__.py (3)

7-11: Excellent! Early version check minimizes import overhead.

Handling --version/-V before other imports ensures the fastest possible response for version queries, avoiding the overhead of loading the entire application. This is a best practice for CLI tools.


887-890: LGTM! Conditional argcomplete import reduces startup time.

The environment-based guard ensures argcomplete is only imported when tab completion is active (_ARGCOMPLETE environment variable), avoiding unnecessary imports during normal CLI execution.


122-123: LGTM! Runtime version check for Config model.

The conditional model configuration properly uses is_pydantic_v2() to select the appropriate Pydantic version at runtime.

src/datamodel_code_generator/reference.py (3)

38-38: LGTM! Import updated for runtime version detection.

The addition of is_pydantic_v2 to the import statement supports the transition from static to runtime version checking.


96-115: LGTM! Version-specific dict method selection.

The runtime check properly gates Pydantic v2's model_dump vs v1's super().dict() implementation, ensuring correct behavior across versions.


168-175: LGTM! Config selection uses runtime check.

The model configuration correctly uses is_pydantic_v2() to choose between v2's model_config and v1's Config class.

src/datamodel_code_generator/util.py (5)

32-56: LGTM! Version detection with pydantic patch integration.

The lazy version detection with @lru_cache ensures the version is checked only once, and applying the pydantic patch before importing pydantic is the correct order of operations.


75-109: LGTM! Lazy SafeLoader construction with YAML 1.2 compatibility.

The lazy loader construction with deep copying prevents mutation of the base loader, and the YAML 1.2 boolean pattern with deprecation warnings provides a smooth migration path.


111-111: LGTM! Forward reference enables lazy BaseModel import.

Using a string forward reference "_BaseModel" in the TypeVar avoids importing BaseModel at module load time, supporting the lazy loading strategy.


195-216: LGTM! ConfigDict proxy enables lazy loading with proper interface.

The proxy class correctly delegates both __call__ for instantiation and __class_getitem__ for subscripting to the lazily-loaded ConfigDict, maintaining the full interface.


236-246: LGTM! getattr provides clean lazy loading interface.

The __getattr__ pattern properly exposes BaseModel and SafeLoader on-demand while raising AttributeError for unknown attributes, maintaining expected module behavior.

Comment thread src/datamodel_code_generator/__init__.py Outdated
Comment thread src/datamodel_code_generator/model/pydantic/__init__.py
Comment thread tests/main/conftest.py
Comment thread src/datamodel_code_generator/__main__.py Fixed
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/__init__.py (1)

142-158: The _pysnooper_initialized flag name could be clearer.

As noted in a previous review, this flag tracks whether the default disabled state has been set, not whether pysnooper is truly initialized. Consider renaming to _pysnooper_default_state_set for clarity in a follow-up.

The lazy import pattern itself is correct for the startup optimization goal.

🧹 Nitpick comments (1)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)

48-54: Consider removing the unused noqa directive.

The lazy import of is_pydantic_v2 inside the method supports the startup optimization goal. However, static analysis indicates the noqa: PLC0415 directive on line 50 is unused (the rule isn't enabled).

🔎 Suggested fix
     def dict(self, **kwargs: Any) -> dict[str, Any]:  # type: ignore[override]
         """Version-compatible dict method for templates."""
-        from datamodel_code_generator.util import is_pydantic_v2  # noqa: PLC0415
+        from datamodel_code_generator.util import is_pydantic_v2

         if is_pydantic_v2():
             return self.model_dump(**kwargs)
         return super().dict(**kwargs)
📜 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 d23f339 and 92b3b9f.

📒 Files selected for processing (7)
  • src/datamodel_code_generator/__init__.py
  • src/datamodel_code_generator/__main__.py
  • src/datamodel_code_generator/arguments.py
  • src/datamodel_code_generator/enums.py
  • src/datamodel_code_generator/model/pydantic_v2/__init__.py
  • src/datamodel_code_generator/model/pydantic_v2/base_model.py
  • src/datamodel_code_generator/types.py
💤 Files with no reviewable changes (1)
  • src/datamodel_code_generator/model/pydantic_v2/base_model.py
🧰 Additional context used
🧬 Code graph analysis (4)
src/datamodel_code_generator/__init__.py (3)
src/datamodel_code_generator/enums.py (3)
  • AllExportsCollisionStrategy (92-102)
  • AllExportsScope (81-89)
  • InputFileType (35-45)
src/datamodel_code_generator/util.py (1)
  • is_pydantic_v2 (49-51)
src/datamodel_code_generator/model/type_alias.py (1)
  • TypeAlias (37-42)
src/datamodel_code_generator/arguments.py (1)
src/datamodel_code_generator/enums.py (3)
  • StrictTypes (199-206)
  • TargetPydanticVersion (181-189)
  • UnionMode (192-196)
src/datamodel_code_generator/__main__.py (1)
src/datamodel_code_generator/util.py (1)
  • is_pydantic_v2 (49-51)
src/datamodel_code_generator/types.py (4)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (29-54)
src/datamodel_code_generator/util.py (1)
  • is_pydantic_v2 (49-51)
src/datamodel_code_generator/model/base.py (2)
  • name (769-771)
  • DataModelFieldBase (124-425)
src/datamodel_code_generator/enums.py (1)
  • StrictTypes (199-206)
🪛 Ruff (0.14.10)
src/datamodel_code_generator/__init__.py

72-72: Unused noqa directive (non-enabled: E402)

Remove unused noqa directive

(RUF100)


95-95: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


97-97: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


128-128: Unused noqa directive (non-enabled: PLW0603)

Remove unused noqa directive

(RUF100)


130-130: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


149-149: Unused noqa directive (non-enabled: PLW0603)

Remove unused noqa directive

(RUF100)


151-151: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


918-918: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/model/pydantic_v2/__init__.py

50-50: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/__main__.py

7-7: Unused noqa directive (non-enabled: PLR2004)

Remove unused noqa directive

(RUF100)


10-10: Unused noqa directive (non-enabled: T201)

Remove unused noqa directive

(RUF100)


13-13: Unused noqa directive (non-enabled: PLR2004)

Remove unused noqa directive

(RUF100)


894-894: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/types.py

93-93: Unused noqa directive (non-enabled: PLC0415)

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). (19)
  • GitHub Check: py312-isort5 on Ubuntu
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: 3.13 on Ubuntu
  • GitHub Check: py312-pydantic1 on Ubuntu
  • GitHub Check: py312-isort6 on Ubuntu
  • GitHub Check: 3.11 on Windows
  • GitHub Check: py312-black24 on Ubuntu
  • GitHub Check: 3.12 on macOS
  • GitHub Check: 3.11 on macOS
  • GitHub Check: py312-black22 on Ubuntu
  • GitHub Check: 3.14 on Windows
  • GitHub Check: 3.10 on Ubuntu
  • GitHub Check: 3.12 on Ubuntu
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.13 on macOS
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.13 on Windows
  • GitHub Check: Analyze (python)
  • GitHub Check: benchmarks
🔇 Additional comments (16)
src/datamodel_code_generator/arguments.py (1)

17-35: LGTM - Import reorganization to centralized enums module.

The imports of StrictTypes and UnionMode now come from the centralized datamodel_code_generator.enums module, which aligns with the PR's goal of improving CLI startup time by separating enums from heavy pydantic imports.

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

13-15: LGTM - Import reorganization for centralized enums.

The UnionMode import is now sourced from the centralized enums module, while BaseModel and DataModelField remain from the local .base_model module.

src/datamodel_code_generator/__main__.py (3)

5-17: Effective early exit optimization for CLI startup.

The early exit handling for --version and --help before importing heavy dependencies is an effective optimization. This avoids loading pydantic and other dependencies when users just want version info or help text.

Note: The GitHub security scan flagged the print statement at module level, but this is intentional behavior for the early exit path.


128-129: Verify class-level conditional works correctly at import time.

The is_pydantic_v2() check at class body level is evaluated once when the module is imported. This is correct for defining different class structures based on Pydantic version, but ensure the utility function is stable and doesn't cause import-time side effects.

The same pattern is used at line 316 for validator definitions.


893-896: LGTM - Environment-guarded argcomplete loading.

The argcomplete import and autocomplete call are now guarded by checking for _ARGCOMPLETE environment variable, which is set by argcomplete during shell completion. This avoids unnecessary imports during normal CLI execution.

src/datamodel_code_generator/types.py (4)

55-55: LGTM - Import updated to use runtime check function.

The import now uses is_pydantic_v2 function instead of the static PYDANTIC_V2 constant, enabling runtime version detection.


89-98: Good backwards compatibility mechanism via __getattr__.

The module-level __getattr__ provides lazy access to StrictTypes for code that imports it from this module, maintaining backwards compatibility while the enum has been moved to the centralized enums module. This avoids breaking existing imports like from datamodel_code_generator.types import StrictTypes.


100-111: LGTM - Conditional imports based on Pydantic version.

The StrictTypes import under TYPE_CHECKING supports type hints without runtime import, and the conditional import of GetCoreSchemaHandler and core_schema only occurs when Pydantic v2 is installed.


291-315: LGTM - DataType class configuration conditional.

The is_pydantic_v2() check correctly configures model_config for Pydantic v2 or provides a fallback Config class for Pydantic v1. This maintains compatibility with both versions.

src/datamodel_code_generator/enums.py (3)

1-12: Well-structured centralized enums module.

This new module effectively separates all enum definitions from pydantic-heavy modules, which is the core of the startup time optimization. The minimal imports (only enum, typing, and typing_extensions) ensure this module loads quickly.


192-206: Note: Naming convention differs for UnionMode and StrictTypes.

These two enums use lowercase member names (smart, left_to_right, str, bytes, etc.) while other enums in this file use PascalCase (Schemas, Module, Error). This appears intentional to match the Pydantic v2 API values and CLI argument values respectively.


209-230: LGTM - Comprehensive and well-organized __all__ export.

The export list is alphabetically sorted and includes all public entities (constants, TypedDict, and Enums).

src/datamodel_code_generator/__init__.py (4)

26-47: LGTM - Centralized enum imports.

All enum classes are now imported from datamodel_code_generator.enums, which is the core of the startup optimization. This avoids loading pydantic when only enum definitions are needed.


71-80: LGTM - Runtime Pydantic version check for YamlValue type.

The conditional type definition handles the difference between Pydantic v1 and v2's handling of TypeAliasType. Pydantic v1 requires the fallback to Any for recursive type parts.


93-99: LGTM - Lazy imports in load_yaml.

Moving the yaml and SafeLoader imports inside the function reduces top-level import time. This is called on-demand rather than at module import.


916-918: LGTM - Lazy import for yaml.parser in infer_input_type.

The yaml.parser import is deferred until infer_input_type is actually called, supporting the startup optimization.

Comment thread src/datamodel_code_generator/__main__.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: 1

🧹 Nitpick comments (3)
src/datamodel_code_generator/format.py (1)

177-177: Clean up unused noqa directive.

The static analysis tool indicates that the suppressed codes (PLR0912, PLR0913, PLR0915, PLR0917) are not enabled in your Ruff configuration, making the directive unnecessary.

🔎 Proposed fix
-    def __init__(  # noqa: PLR0912, PLR0913, PLR0915, PLR0917
+    def __init__(
src/datamodel_code_generator/types.py (1)

93-93: Consider removing unused noqa directives.

The static analysis indicates these noqa directives are for rules that aren't enabled in your Ruff configuration:

  • Line 93: noqa: PLC0415 (import-outside-toplevel)
  • Line 190: noqa: PLR2004 (magic-value-comparison)

While the directives don't cause harm, removing them would clean up the code slightly.

Proposed cleanup
 def __getattr__(name: str) -> Any:
     """Provide lazy access to StrictTypes for backwards compatibility."""
     if name == "StrictTypes":
-        from datamodel_code_generator.enums import StrictTypes  # noqa: PLC0415
+        from datamodel_code_generator.enums import StrictTypes
 
         return StrictTypes
     if n == 1:
         return tuple(iterables[0])
-    if n == 2:  # noqa: PLR2004
+    if n == 2:
         return (*iterables[0], *iterables[1])

Also applies to: 190-190

src/datamodel_code_generator/__main__.py (1)

7-7: Clean up unused noqa directives.

Static analysis indicates that the noqa directives on these lines suppress rules that are not enabled in your ruff configuration:

  • Lines 7, 13: PLR2004 (magic value comparison)
  • Lines 10, 28: T201 (print statement)
  • Line 906: PLC0415 (import outside top-level)

Removing these unused directives would improve code clarity.

🔎 Proposed cleanup
-if len(sys.argv) == 2 and sys.argv[1] in {"--version", "-V"}:  # noqa: PLR2004
+if len(sys.argv) == 2 and sys.argv[1] in {"--version", "-V"}:
     from importlib.metadata import version

-    print(f"datamodel-codegen {version('datamodel-code-generator')}")  # noqa: T201
+    print(f"datamodel-codegen {version('datamodel-code-generator')}")
     sys.exit(0)

-if len(sys.argv) == 2 and sys.argv[1] in {"--help", "-h"}:  # noqa: PLR2004
+if len(sys.argv) == 2 and sys.argv[1] in {"--help", "-h"}:
     from datamodel_code_generator.arguments import arg_parser

     arg_parser.print_help()
     sys.exit(0)

-        print(prompt_output)  # noqa: T201
+        print(prompt_output)
         sys.exit(0)

-        import argcomplete  # noqa: PLC0415
+        import argcomplete

Also applies to: 10-10, 13-13, 28-28, 906-906

📜 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 92b3b9f and 9950cb2.

📒 Files selected for processing (3)
  • src/datamodel_code_generator/__main__.py
  • src/datamodel_code_generator/format.py
  • src/datamodel_code_generator/types.py
🧰 Additional context used
🧬 Code graph analysis (3)
src/datamodel_code_generator/__main__.py (2)
src/datamodel_code_generator/prompt.py (1)
  • generate_prompt (76-128)
src/datamodel_code_generator/util.py (1)
  • is_pydantic_v2 (49-51)
src/datamodel_code_generator/types.py (4)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (29-54)
src/datamodel_code_generator/util.py (1)
  • is_pydantic_v2 (49-51)
src/datamodel_code_generator/model/base.py (1)
  • name (769-771)
src/datamodel_code_generator/enums.py (1)
  • StrictTypes (199-206)
src/datamodel_code_generator/format.py (1)
src/datamodel_code_generator/util.py (1)
  • load_toml (26-29)
🪛 Ruff (0.14.10)
src/datamodel_code_generator/__main__.py

7-7: Unused noqa directive (non-enabled: PLR2004)

Remove unused noqa directive

(RUF100)


10-10: Unused noqa directive (non-enabled: T201)

Remove unused noqa directive

(RUF100)


13-13: Unused noqa directive (non-enabled: PLR2004)

Remove unused noqa directive

(RUF100)


28-28: Unused noqa directive (non-enabled: T201)

Remove unused noqa directive

(RUF100)


906-906: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/types.py

93-93: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


190-190: Unused noqa directive (non-enabled: PLR2004)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/format.py

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

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). (20)
  • GitHub Check: py312-isort5 on Ubuntu
  • GitHub Check: 3.11 on Windows
  • GitHub Check: py312-isort6 on Ubuntu
  • GitHub Check: py312-pydantic1 on Ubuntu
  • GitHub Check: Analyze (python)
  • GitHub Check: py312-black24 on Ubuntu
  • GitHub Check: py312-black22 on Ubuntu
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.11 on Ubuntu
  • GitHub Check: 3.14 on macOS
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.14 on Ubuntu
  • GitHub Check: 3.12 on macOS
  • GitHub Check: 3.12 on Ubuntu
  • GitHub Check: 3.13 on Windows
  • GitHub Check: 3.13 on macOS
  • GitHub Check: 3.14 on Windows
  • GitHub Check: 3.13 on Ubuntu
  • GitHub Check: benchmarks
🔇 Additional comments (11)
src/datamodel_code_generator/format.py (4)

203-206: LGTM: Early instance variable assignment enables lazy loading.

Storing these configuration values upfront allows the conditional formatter initialization logic to reference them cleanly.


208-209: LGTM: Conditional formatter enablement supports startup optimization.

The use_black and use_isort flags cleanly gate the initialization of formatter configurations, aligning with the PR's goal of reducing startup time by avoiding unnecessary imports and configuration loading.


211-254: LGTM: Conditional Black initialization improves startup performance.

The lazy loading of Black configuration (including deferred pyproject.toml parsing) directly supports the PR objective. The initialization is properly gated by the use_black flag, and format_code ensures apply_black is only invoked when Formatter.BLACK is in the active formatters list.

Note: Line 254's type: ignore[assignment] suggests self.black_mode may be typed as non-optional. While the runtime safety is ensured by the formatter checks, consider whether the type annotation should explicitly allow None for clarity.


256-268: LGTM: Conditional Isort initialization mirrors Black's lazy loading pattern.

The symmetric approach for Isort configuration loading is clean and consistent. The apply_isort method already handles the None case for self.isort_config (line 389), ensuring backward compatibility with isort 4.x while supporting the lazy loading optimization.

src/datamodel_code_generator/types.py (5)

55-55: LGTM: Import updated to use runtime version check.

The change from a static PYDANTIC_V2 constant to the is_pydantic_v2() function aligns with the PR's goal of improving startup time through lazy imports.


89-98: Well-implemented lazy import mechanism.

The module-level __getattr__ follows PEP 562 and correctly provides lazy access to StrictTypes, deferring its import until actually needed. This directly supports the PR's startup performance goals.


100-106: Correct use of TYPE_CHECKING for type hints.

The TYPE_CHECKING import ensures type checkers can resolve StrictTypes references without triggering a runtime import, properly complementing the lazy __getattr__ mechanism.


109-111: Runtime check correctly replaces static constant.

The runtime is_pydantic_v2() check correctly replaces the previous static PYDANTIC_V2 constant. While this is evaluated during class definition at import time, it enables the lazy import pattern used elsewhere in the PR.


180-193: Good micro-optimization for common cases.

The chain_as_tuple optimization avoids the overhead of itertools.chain() for the common cases of 0-2 iterables by using direct tuple construction and unpacking. This is a sensible performance improvement.

src/datamodel_code_generator/__main__.py (2)

87-87: LGTM! Runtime pydantic version detection supports lazy imports.

The replacement of the static PYDANTIC_V2 constant with the runtime is_pydantic_v2() function enables lazy evaluation and avoids eager imports at module load time, which aligns well with the PR's objective to improve CLI startup performance.

Also applies to: 140-140, 328-328


905-908: LGTM! Environment-guarded argcomplete import improves startup time.

The conditional import of argcomplete based on the _ARGCOMPLETE environment variable ensures the module is only loaded during shell completion, avoiding unnecessary overhead during normal CLI execution. This is an effective lazy-loading pattern that directly supports the PR's performance goals.

Comment thread src/datamodel_code_generator/__main__.py Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

437-438: LGTM: Lazy imports improve startup time.

Moving jinja2 imports inside cached functions defers the import until template rendering is needed. Since these functions use @lru_cache, the import overhead is only paid once.

Remove unused noqa directives

The static analysis correctly identifies that PLC0415 is not enabled, so the noqa comments are unnecessary:

-    from jinja2 import ChoiceLoader, Environment, FileSystemLoader, select_autoescape  # noqa: PLC0415
+    from jinja2 import ChoiceLoader, Environment, FileSystemLoader, select_autoescape

Apply the same change at lines 474-475 and 711.

Also applies to: 474-475


892-901: Consider unifying identical conditional branches.

Both the if is_pydantic_v2(): and else: branches execute identical code. Unless this is anticipating future divergence, the conditional could be simplified:

Simplify by removing redundant conditional
-if is_pydantic_v2():
-    _rebuild_namespace = {"Union": Union, "DataModelFieldBase": DataModelFieldBase, "DataType": DataType}
-    DataType.model_rebuild(_types_namespace=_rebuild_namespace)
-    BaseClassDataType.model_rebuild(_types_namespace=_rebuild_namespace)
-    DataModelFieldBase.model_rebuild(_types_namespace={"DataModel": DataModel})
-else:
-    _rebuild_namespace = {"Union": Union, "DataModelFieldBase": DataModelFieldBase, "DataType": DataType}
-    DataType.model_rebuild(_types_namespace=_rebuild_namespace)
-    BaseClassDataType.model_rebuild(_types_namespace=_rebuild_namespace)
-    DataModelFieldBase.model_rebuild(_types_namespace={"DataModel": DataModel})
+_rebuild_namespace = {"Union": Union, "DataModelFieldBase": DataModelFieldBase, "DataType": DataType}
+DataType.model_rebuild(_types_namespace=_rebuild_namespace)
+BaseClassDataType.model_rebuild(_types_namespace=_rebuild_namespace)
+DataModelFieldBase.model_rebuild(_types_namespace={"DataModel": DataModel})

However, if this conditional is preparing for version-specific namespace requirements, keep it as-is.

📜 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 9950cb2 and 33cb337.

📒 Files selected for processing (4)
  • src/datamodel_code_generator/model/base.py
  • src/datamodel_code_generator/model/pydantic/base_model.py
  • src/datamodel_code_generator/parser/jsonschema.py
  • src/datamodel_code_generator/parser/openapi.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/datamodel_code_generator/parser/jsonschema.py
🧰 Additional context used
🧬 Code graph analysis (3)
src/datamodel_code_generator/parser/openapi.py (1)
src/datamodel_code_generator/util.py (1)
  • model_dump (260-264)
src/datamodel_code_generator/model/pydantic/base_model.py (7)
src/datamodel_code_generator/model/base.py (3)
  • nullable (863-865)
  • imports (288-313)
  • imports (771-776)
src/datamodel_code_generator/types.py (2)
  • nullable (293-295)
  • imports (481-526)
src/datamodel_code_generator/model/dataclass.py (1)
  • imports (139-144)
src/datamodel_code_generator/model/pydantic_v2/types.py (1)
  • imports (62-67)
src/datamodel_code_generator/model/enum.py (1)
  • imports (119-121)
src/datamodel_code_generator/model/typed_dict.py (1)
  • imports (150-155)
src/datamodel_code_generator/model/type_alias.py (1)
  • imports (28-34)
src/datamodel_code_generator/model/base.py (2)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (29-54)
src/datamodel_code_generator/util.py (4)
  • is_pydantic_v2 (49-51)
  • model_copy (281-285)
  • model_dump (260-264)
  • model_validate (267-271)
🪛 GitHub Actions: Lint
src/datamodel_code_generator/model/pydantic/base_model.py

[error] 267-273: ruff-format: formatting changes were made by the hook. The pre-commit hook reformatted the file and CI failed due to the formatting change. Re-run the command to apply fixes: 'prek run --all-files --show-diff-on-failure --skip readme'.

🪛 Ruff (0.14.10)
src/datamodel_code_generator/model/base.py

437-437: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


474-474: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


711-711: Unused noqa directive (non-enabled: PLC0415)

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). (20)
  • GitHub Check: py312-black23 on Ubuntu
  • GitHub Check: py312-isort5 on Ubuntu
  • GitHub Check: py312-pydantic1 on Ubuntu
  • GitHub Check: py312-black24 on Ubuntu
  • GitHub Check: py312-black22 on Ubuntu
  • GitHub Check: py312-isort6 on Ubuntu
  • GitHub Check: 3.11 on Ubuntu
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: 3.12 on macOS
  • GitHub Check: 3.14 on Windows
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.10 on Ubuntu
  • GitHub Check: 3.10 on macOS
  • GitHub Check: 3.14 on Ubuntu
  • GitHub Check: 3.13 on Windows
  • GitHub Check: 3.13 on Ubuntu
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.14 on macOS
  • GitHub Check: Analyze (python)
  • GitHub Check: benchmarks
🔇 Additional comments (7)
src/datamodel_code_generator/model/base.py (5)

287-313: LGTM: Fast-path optimization improves performance.

The early return when no special typing imports are needed (Union, Optional, Annotated) avoids the overhead of import filtering and iterator operations. The logic correctly identifies the common case where fields only need their data type's imports.


674-674: LGTM: Caching prevents redundant expensive operations.

The per-instance cache for get_dedup_key is well-designed:

  • Cache key (class_name, use_default) correctly captures all inputs
  • Caches the result of expensive render() and imports calls
  • No invalidation needed since models are effectively immutable after creation
  • Updated docstring clearly documents the caching behavior

Also applies to: 700-716


79-89: LGTM: Runtime version checks enable lazy pydantic imports.

Replacing static PYDANTIC_V2 with runtime is_pydantic_v2() checks allows pydantic imports to be deferred. The conditional logic maintains backward compatibility with both v1 and v2 configurations.

Also applies to: 127-137


169-179: LGTM: Provides consistent model_rebuild API across pydantic versions.

The compatibility shim for v1's update_forward_refs as model_rebuild is well-implemented:

  • Only defined when not using v2 (avoids overriding native method)
  • Correctly translates _types_namespace to v1's localns
  • Maintains API consistency across the codebase

46-47: LGTM: TYPE_CHECKING guard reduces runtime imports.

Moving jinja2 type imports under TYPE_CHECKING is the correct pattern. These types are only needed for static analysis and are not required at runtime, contributing to faster startup.

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

755-755: The pattern model_dump(object_schema, exclude_none=True) is already established throughout the codebase and presents no risk. This identical approach is used in jsonschema.py at lines 1084, 2617, 2753, and 2820. Constraint fields are all optional with None defaults; downstream code accesses them via attribute checks or dictionary assignment after serialization, neither of which depends on explicit None values in the output dictionary. No breaking changes are expected.

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

269-277: The fast-path conditions are correct and properly cover all scenarios where Field() is not needed.

The logic is sound: when a field is required, non-nullable, has no alias, no constraints, and no extras, the __str__ method returns an empty string, making self.field falsy and thus avoiding the unnecessary Field() import. The concerns about nullable being False vs None are unfounded—both evaluate as True in not self.nullable, and Field("...") is only generated when nullable=True AND required=True (line 240-241). For required fields with the fast-path conditions, _computed_default_factory is always None (set explicitly at line 220-221), so it cannot introduce unexpected Field() generation.

Comment thread src/datamodel_code_generator/model/pydantic/base_model.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 (2)
src/datamodel_code_generator/util.py (2)

228-257: Consider simplifying BaseModel caching and documenting for type checkers.

The __getattr__ pattern for lazy module-level attributes works correctly but has some considerations:

  1. The manual caching of _BaseModel (lines 243, 248, 250-252) could be simplified by relying on the @lru_cache on _get_base_model_class().
  2. Type checkers may not recognize BaseModel and SafeLoader as module exports without explicit __all__ or a .pyi stub file.
🔎 Optional simplification
-_BaseModel: type | None = None
-
-
 def __getattr__(name: str) -> Any:
     """Provide lazy access to BaseModel and SafeLoader."""
-    global _BaseModel  # noqa: PLW0603
     if name == "BaseModel":
-        if _BaseModel is None:
-            _BaseModel = _get_base_model_class()
-        return _BaseModel
+        return _get_base_model_class()
     if name == "SafeLoader":
         return get_safe_loader()
     msg = f"module {__name__!r} has no attribute {name!r}"
     raise AttributeError(msg)

The lru_cache on _get_base_model_class() already handles caching, making the manual global unnecessary.


36-41: Optional: Clean up unused noqa directives.

Static analysis indicates multiple noqa directives that appear unnecessary because the corresponding rules (PLC0415, PLW0603) may not be enabled in your ruff configuration. Consider removing them to reduce noise:

  • Lines 36, 40, 41, 88, 91, 93, 209, 231, 234: # noqa: PLC0415
  • Line 248: # noqa: PLW0603 (will be removed if you apply the global statement fix)

Also applies to: 88-93, 209-209, 231-231, 234-234, 248-248

📜 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 33cb337 and 330dfa9.

📒 Files selected for processing (1)
  • src/datamodel_code_generator/util.py
🧰 Additional context used
🧬 Code graph analysis (1)
src/datamodel_code_generator/util.py (2)
src/datamodel_code_generator/pydantic_patch.py (1)
  • apply_patch (24-29)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (29-54)
🪛 GitHub Actions: Lint
src/datamodel_code_generator/util.py

[error] 55-55: ruff: PLW0603 Using the global statement to update _is_v2 is discouraged


[error] 63-63: ruff: PLW0603 Using the global statement to update _is_v2_11 is discouraged

🪛 Ruff (0.14.10)
src/datamodel_code_generator/util.py

36-36: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


40-40: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


41-41: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


88-88: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


91-91: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


93-93: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


209-209: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


231-231: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


234-234: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


248-248: Unused noqa directive (non-enabled: PLW0603)

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). (19)
  • GitHub Check: Analyze (python)
  • GitHub Check: py312-pydantic1 on Ubuntu
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.13 on Windows
  • GitHub Check: 3.12 on Ubuntu
  • GitHub Check: py312-black24 on Ubuntu
  • GitHub Check: py312-black22 on Ubuntu
  • GitHub Check: py312-isort5 on Ubuntu
  • GitHub Check: 3.10 on Ubuntu
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: 3.14 on Windows
  • GitHub Check: 3.11 on Windows
  • GitHub Check: py312-isort6 on Ubuntu
  • GitHub Check: 3.11 on Ubuntu
  • GitHub Check: 3.13 on Ubuntu
  • GitHub Check: 3.10 on macOS
  • GitHub Check: py312-black23 on Ubuntu
  • GitHub Check: 3.13 on macOS
  • GitHub Check: benchmarks
🔇 Additional comments (4)
src/datamodel_code_generator/util.py (4)

121-121: Verify that type checkers handle the string-based TypeVar bound correctly.

Using a string forward reference "_BaseModel" for the TypeVar bound is unconventional. While this enables lazy loading, type checkers typically expect the bound to be resolvable at module load time. This may cause type inference issues in code using this TypeVar.

Consider testing with mypy and pyright to confirm that type checking works correctly for functions using this Model TypeVar. If issues arise, you may need to use TYPE_CHECKING blocks or adjust the approach.


152-202: LGTM: Version-aware validator decorators.

The replacement of static PYDANTIC_V2 checks with is_pydantic_v2() calls is correct and enables lazy version detection. The logic properly handles v1/v2 API differences, including the classmethod wrapping for v2 before mode validators.


270-295: LGTM: Version-compatible utility functions.

All utility functions correctly implement version-aware branching using is_pydantic_v2(). The replacements properly map to the correct Pydantic v1 and v2 APIs.


215-226: No action required: ConfigDict[...] syntax is not used in the codebase.

The __class_getitem__ method on line 222 would theoretically fail with dict (Pydantic v1) at runtime, but this code path is never invoked. The actual usage pattern only calls ConfigDict(...) via __call__, which works correctly. The pyright: ignore[reportIndexIssue] comment indicates this limitation is acknowledged. If subscript notation is needed in the future, the proxy would need to handle dict subscripting separately.

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

Caution

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

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

902-911: Redundant conditional: both branches are identical.

The if is_pydantic_v2() / else branches execute exactly the same code. Since model_rebuild already handles v1/v2 differences internally (via the custom wrapper defined at lines 171-179), this conditional can be removed.

🔎 Proposed simplification
-if is_pydantic_v2():
-    _rebuild_namespace = {"Union": Union, "DataModelFieldBase": DataModelFieldBase, "DataType": DataType}
-    DataType.model_rebuild(_types_namespace=_rebuild_namespace)
-    BaseClassDataType.model_rebuild(_types_namespace=_rebuild_namespace)
-    DataModelFieldBase.model_rebuild(_types_namespace={"DataModel": DataModel})
-else:
-    _rebuild_namespace = {"Union": Union, "DataModelFieldBase": DataModelFieldBase, "DataType": DataType}
-    DataType.model_rebuild(_types_namespace=_rebuild_namespace)
-    BaseClassDataType.model_rebuild(_types_namespace=_rebuild_namespace)
-    DataModelFieldBase.model_rebuild(_types_namespace={"DataModel": DataModel})
+_rebuild_namespace = {"Union": Union, "DataModelFieldBase": DataModelFieldBase, "DataType": DataType}
+DataType.model_rebuild(_types_namespace=_rebuild_namespace)
+BaseClassDataType.model_rebuild(_types_namespace=_rebuild_namespace)
+DataModelFieldBase.model_rebuild(_types_namespace={"DataModel": DataModel})
🧹 Nitpick comments (2)
src/datamodel_code_generator/model/base.py (2)

447-448: Consider removing unused noqa directives.

Static analysis indicates # noqa: PLC0415 is unused in the current linter configuration. If pylint isn't used in CI, these can be removed for cleaner code.

🔎 Proposed fix
-    from jinja2 import ChoiceLoader, Environment, FileSystemLoader, select_autoescape  # noqa: PLC0415
+    from jinja2 import ChoiceLoader, Environment, FileSystemLoader, select_autoescape

710-726: LGTM!

Good caching optimization for get_dedup_key. The cache key tuple correctly captures both parameters that affect the result.

Same minor note: the # noqa: PLC0415 on line 721 is flagged as unused by static analysis.

📜 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 330dfa9 and 37f664e.

📒 Files selected for processing (1)
  • src/datamodel_code_generator/model/base.py
🧰 Additional context used
🪛 Ruff (0.14.10)
src/datamodel_code_generator/model/base.py

447-447: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


484-484: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


721-721: Unused noqa directive (non-enabled: PLC0415)

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). (20)
  • GitHub Check: py312-pydantic1 on Ubuntu
  • GitHub Check: 3.11 on macOS
  • GitHub Check: py312-black22 on Ubuntu
  • GitHub Check: 3.12 on Windows
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: py312-isort6 on Ubuntu
  • GitHub Check: 3.10 on macOS
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.10 on Ubuntu
  • GitHub Check: 3.12 on macOS
  • GitHub Check: 3.13 on macOS
  • GitHub Check: py312-black24 on Ubuntu
  • GitHub Check: 3.14 on macOS
  • GitHub Check: 3.14 on Windows
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.13 on Windows
  • GitHub Check: 3.11 on Ubuntu
  • GitHub Check: 3.13 on Ubuntu
  • GitHub Check: benchmarks
  • GitHub Check: Analyze (python)
🔇 Additional comments (5)
src/datamodel_code_generator/model/base.py (5)

39-48: LGTM!

Clean import organization: is_pydantic_v2 is imported for runtime checks, while jinja2 types are deferred under TYPE_CHECKING to support lazy loading. This aligns with the PR's goal of improving CLI startup time.


79-89: LGTM!

Runtime version check at class definition time correctly configures pydantic v1/v2-specific model configuration.


208-222: LGTM!

Good caching optimization with appropriate documentation. The docstring correctly notes that "parent is stable at render time", which justifies the cache-once design without invalidation.


303-307: Verify fast path consistency with slow path.

The fast path returns data_type.all_imports unfiltered, while the slow path filters out IMPORT_UNION/IMPORT_OPTIONAL when not needed in the type hint. If data_type.all_imports contains these imports but the final type hint doesn't use them, the fast path would include unnecessary imports that the slow path would exclude.

This is likely an acceptable tradeoff for performance (extra imports don't cause errors), but worth confirming this behavioral difference is intentional.


684-684: LGTM!

Cache initialization with correct type hint matches the usage pattern in get_dedup_key.

Comment thread src/datamodel_code_generator/__init__.py
Comment thread src/datamodel_code_generator/__main__.py
@koxudaxi koxudaxi force-pushed the perf/lazy-imports branch 2 times, most recently from 6c54277 to ae13c77 Compare December 25, 2025 09:30
- Fix Pydantic v1 compatibility by using __dict__ for self_reference cache
- Add noqa comments for PLW0603 in util.py
- Fix type error: wrap all_imports iterator in tuple()
- Rename _pysnooper_initialized to _pysnooper_default_state_set for clarity
- Add PydanticV2Dataclass to skip condition in conftest.py
- Add # pragma: no cover for fast path CLI code (--version, --help,
  --generate-prompt) that runs at module import time
- Add # pragma: no cover for stdout output and ARGCOMPLETE paths
- Add # pragma: no branch for pydantic version caching checks

🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Add # pragma: no cover for chain_as_tuple n==0 and n==1 cases
- Add # pragma: no cover for deprecated YAML bool warning branch

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@koxudaxi koxudaxi force-pushed the perf/lazy-imports branch 2 times, most recently from 2fd794c to bdfd2b7 Compare December 25, 2025 09:48
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

Caution

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

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

1-5: Update docstring to reflect is_pydantic_v2() instead of PYDANTIC_V2

The module docstring still refers to “Pydantic version detection (PYDANTIC_V2)”, but the code now exposes is_pydantic_v2() and get_pydantic_version() instead of a constant. Updating the docstring would prevent confusion for readers.

<suggested_change>
Replace PYDANTIC_V2 in the docstring with is_pydantic_v2() / get_pydantic_version().
</suggested_change>

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

60-74: YAML bool deprecation warning and customized SafeLoader behavior are coherent

The _construct_yaml_bool_with_warning helper correctly warns on deprecated capitalized YAML booleans and only treats lowercase true/false as canonical, and get_safe_loader() constructs an isolated loader class that adjusts timestamp and bool constructors plus implicit resolvers. The use of @lru_cache(maxsize=1) ensures this setup cost is paid only once.

Given earlier discussion about the SafeLoader customization, it’s fine to keep this implementation for now; if you revisit it in a dedicated refactor, you can switch to a subclass-based pattern without changing the public API.

Also applies to: 76-110

src/datamodel_code_generator/__init__.py (1)

126-137: pysnooper debug flag _pysnooper_default_state_set is used correctly

The updated enable_debug_message() and snooper_to_methods() both respect _pysnooper_default_state_set: the former enables tracing and marks the flag, the latter lazily sets DISABLED = True once across all decorated classes. This matches the intent to keep tracing off by default and only enable it via the --debug option.

Since CodeQL previously flagged this as unused, it’s good to keep in mind that this is a known false positive; behavior here looks correct as implemented.

Also applies to: 142-159

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

208-221: Self-reference caching via __dict__ is safe and avoids Pydantic assignment quirks

The new self_reference() implementation caches the boolean result in __dict__["_self_reference_cache"], which avoids Pydantic v1’s restrictions on field assignment while making repeated calls cheap. Given that parent and data_type are stable post-construction, this caching is appropriate and should not change semantics.

If you ever add mutation of parent or data_type after construction, consider invalidating _self_reference_cache in those paths.


683-684: Per-instance dedup key caching is correct and should significantly reduce render costs

Initializing _dedup_key_cache in DataModel.__init__ and caching (render_output, imports) tuples per (class_name, use_default) key avoids repeated render() and imports computation in hot paths like model deduplication. The fallback class name "M" when use_default and class_name is None matches the existing default-name behavior.

If you later add knobs that affect rendered output (e.g., extra template data toggles), consider including them in the cache key or explicitly documenting that get_dedup_key assumes those inputs are stable for a given instance.

Also applies to: 709-725

src/datamodel_code_generator/types.py (1)

180-188: chain_as_tuple 2-iterable fast path is safe and matches existing semantics

The special case for len(iterables) == 2 using (*iterables[0], *iterables[1]) eagerly consumes both iterables in order, just like tuple(chain(*iterables)), and avoids the small overhead of constructing an intermediate chain object in the common two-iterator case.

If you later find three-way chains are also hot, you could extend this pattern to len == 3 while keeping the general fallback for larger counts.

src/datamodel_code_generator/__main__.py (1)

769-897: run_generate_from_config now correctly handles stdout-only generation

The new block that prints result when output is None ensures CLI calls that rely on stdout (no --output) still surface generated code, including the case where generate() returns a dict of multiple modules (each printed with a trailing newline). This keeps behavior intuitive without affecting the file-output code paths.

If multi-module-to-stdout output becomes more common, consider prefixing each chunk with a comment containing its module path to make the combined stream easier to interpret.

📜 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 6c54277 and bdfd2b7.

📒 Files selected for processing (7)
  • src/datamodel_code_generator/__init__.py
  • src/datamodel_code_generator/__main__.py
  • src/datamodel_code_generator/model/base.py
  • src/datamodel_code_generator/model/pydantic/base_model.py
  • src/datamodel_code_generator/types.py
  • src/datamodel_code_generator/util.py
  • tests/main/conftest.py
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2799
File: src/datamodel_code_generator/model/pydantic/__init__.py:43-43
Timestamp: 2025-12-25T09:22:14.661Z
Learning: In datamodel-code-generator project, defensive `# noqa: PLC0415` directives should be kept on lazy imports (imports inside functions/methods) even when Ruff reports them as unused via RUF100, to prepare for potential future Ruff configuration changes that might enable the import-outside-top-level rule.
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2799
File: src/datamodel_code_generator/util.py:49-66
Timestamp: 2025-12-25T09:22:57.664Z
Learning: In datamodel-code-generator, the is_pydantic_v2() and is_pydantic_v2_11() functions in src/datamodel_code_generator/util.py intentionally use global variable caching (_is_v2, _is_v2_11) on top of lru_cache for performance optimization. This dual-layer caching eliminates function call overhead and cache lookup overhead for frequently-called version checks. The PLW0603 linter warnings should be suppressed with # noqa: PLW0603 as this is a deliberate design choice.
📚 Learning: 2025-12-25T09:22:57.664Z
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2799
File: src/datamodel_code_generator/util.py:49-66
Timestamp: 2025-12-25T09:22:57.664Z
Learning: In datamodel-code-generator, the is_pydantic_v2() and is_pydantic_v2_11() functions in src/datamodel_code_generator/util.py intentionally use global variable caching (_is_v2, _is_v2_11) on top of lru_cache for performance optimization. This dual-layer caching eliminates function call overhead and cache lookup overhead for frequently-called version checks. The PLW0603 linter warnings should be suppressed with # noqa: PLW0603 as this is a deliberate design choice.

Applied to files:

  • tests/main/conftest.py
  • src/datamodel_code_generator/__main__.py
  • src/datamodel_code_generator/types.py
  • src/datamodel_code_generator/util.py
  • src/datamodel_code_generator/model/base.py
  • src/datamodel_code_generator/__init__.py
📚 Learning: 2025-12-25T09:22:14.661Z
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2799
File: src/datamodel_code_generator/model/pydantic/__init__.py:43-43
Timestamp: 2025-12-25T09:22:14.661Z
Learning: In datamodel-code-generator project, defensive `# noqa: PLC0415` directives should be kept on lazy imports (imports inside functions/methods) even when Ruff reports them as unused via RUF100, to prepare for potential future Ruff configuration changes that might enable the import-outside-top-level rule.

Applied to files:

  • src/datamodel_code_generator/__main__.py
  • src/datamodel_code_generator/util.py
  • src/datamodel_code_generator/model/pydantic/base_model.py
📚 Learning: 2025-12-18T13:43:16.235Z
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:16.235Z
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/util.py
  • src/datamodel_code_generator/model/pydantic/base_model.py
🧬 Code graph analysis (6)
tests/main/conftest.py (2)
src/datamodel_code_generator/util.py (1)
  • is_pydantic_v2 (52-57)
src/datamodel_code_generator/enums.py (1)
  • DataModelType (48-56)
src/datamodel_code_generator/__main__.py (2)
src/datamodel_code_generator/prompt.py (1)
  • generate_prompt (76-128)
src/datamodel_code_generator/util.py (1)
  • is_pydantic_v2 (52-57)
src/datamodel_code_generator/types.py (3)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (29-54)
src/datamodel_code_generator/util.py (1)
  • is_pydantic_v2 (52-57)
src/datamodel_code_generator/enums.py (1)
  • StrictTypes (199-206)
src/datamodel_code_generator/util.py (4)
src/datamodel_code_generator/reference.py (1)
  • _BaseModel (81-138)
src/datamodel_code_generator/pydantic_patch.py (1)
  • apply_patch (24-29)
src/datamodel_code_generator/model/base.py (2)
  • method (380-382)
  • name (796-798)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (29-54)
src/datamodel_code_generator/model/base.py (6)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (29-54)
src/datamodel_code_generator/util.py (2)
  • is_pydantic_v2 (52-57)
  • model_dump (258-262)
src/datamodel_code_generator/parser/base.py (2)
  • parent (602-604)
  • data_type (1079-1081)
src/datamodel_code_generator/types.py (3)
  • all_data_types (432-438)
  • all_imports (469-473)
  • imports (476-521)
src/datamodel_code_generator/model/msgspec.py (1)
  • needs_annotated_import (433-443)
src/datamodel_code_generator/model/pydantic/base_model.py (1)
  • imports (267-275)
src/datamodel_code_generator/model/pydantic/base_model.py (6)
src/datamodel_code_generator/types.py (2)
  • nullable (288-290)
  • imports (476-521)
src/datamodel_code_generator/model/dataclass.py (1)
  • imports (139-144)
src/datamodel_code_generator/model/pydantic_v2/types.py (1)
  • imports (62-67)
src/datamodel_code_generator/model/enum.py (1)
  • imports (119-121)
src/datamodel_code_generator/model/typed_dict.py (1)
  • imports (150-155)
src/datamodel_code_generator/model/type_alias.py (1)
  • imports (28-34)
🪛 GitHub Check: CodeQL
src/datamodel_code_generator/__main__.py

[notice] 11-11: Use of a print statement at module level
Print statement may execute during import.


[notice] 31-31: Use of a print statement at module level
Print statement may execute during import.

src/datamodel_code_generator/__init__.py

[notice] 158-158: Unused global variable
The global variable '_pysnooper_default_state_set' is not used.

🪛 Ruff (0.14.10)
src/datamodel_code_generator/__main__.py

8-8: Unused noqa directive (non-enabled: PLR2004)

Remove unused noqa directive

(RUF100)


11-11: Unused noqa directive (non-enabled: T201)

Remove unused noqa directive

(RUF100)


15-15: Unused noqa directive (non-enabled: PLR2004)

Remove unused noqa directive

(RUF100)


31-31: Unused noqa directive (non-enabled: T201)

Remove unused noqa directive

(RUF100)


909-909: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/types.py

93-93: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


185-185: Unused noqa directive (non-enabled: PLR2004)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/util.py

36-36: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


40-40: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


41-41: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


54-54: Unused noqa directive (non-enabled: PLW0603)

Remove unused noqa directive

(RUF100)


79-79: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


82-82: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


84-84: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


200-200: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


219-219: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


222-222: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


236-236: Unused noqa directive (non-enabled: PLW0603)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/model/base.py

446-446: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


483-483: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


720-720: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/__init__.py

72-72: Unused noqa directive (non-enabled: E402)

Remove unused noqa directive

(RUF100)


95-95: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


97-97: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


128-128: Unused noqa directive (non-enabled: PLW0603)

Remove unused noqa directive

(RUF100)


130-130: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


149-149: Unused noqa directive (non-enabled: PLW0603)

Remove unused noqa directive

(RUF100)


151-151: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


918-918: Unused noqa directive (non-enabled: PLC0415)

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). (20)
  • GitHub Check: 3.12 on Ubuntu
  • GitHub Check: py312-black23 on Ubuntu
  • GitHub Check: py312-black22 on Ubuntu
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: 3.14 on macOS
  • GitHub Check: py312-isort6 on Ubuntu
  • GitHub Check: 3.10 on Windows
  • GitHub Check: py312-black24 on Ubuntu
  • GitHub Check: py312-isort5 on Ubuntu
  • GitHub Check: 3.10 on Ubuntu
  • GitHub Check: 3.11 on Windows
  • GitHub Check: py312-pydantic1 on Ubuntu
  • GitHub Check: 3.11 on Ubuntu
  • GitHub Check: 3.10 on macOS
  • GitHub Check: 3.13 on macOS
  • GitHub Check: 3.13 on Ubuntu
  • GitHub Check: 3.13 on Windows
  • GitHub Check: 3.14 on Windows
  • GitHub Check: benchmarks
  • GitHub Check: Analyze (python)
🔇 Additional comments (19)
src/datamodel_code_generator/model/pydantic/base_model.py (1)

267-275: DataModelField.imports fast path is safe and avoids unnecessary Field computation

For required, non-nullable fields with no alias, constraints, or extras, __str__ returns "" so self.field is falsy and IMPORT_FIELD was never added. The new early return to super().imports preserves behavior while skipping the expensive self.field/__str__ path for this common case.

tests/main/conftest.py (1)

22-22: Exec-skip logic now correctly covers both Pydantic v1 and v2 model types

Using is_pydantic_v2() plus the set check for {PydanticV2BaseModel, PydanticV2Dataclass} gives the right behavior: exec validation is skipped when the generated model type and installed Pydantic major version are incompatible. This also fixes the earlier omission of PydanticV2Dataclass in the skip condition.

You may want to re-run tests/main/test_exec_validation.py under environments with and without Pydantic v2 to confirm the skip behavior matches expectations.

Also applies to: 448-452

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

39-40: Pydantic v1/v2 branching and config setup look consistent and centralized

Using is_pydantic_v2() plus ConfigDict for v2 and inner Config classes for v1 in both ConstraintsBase and DataModelFieldBase, and wiring model_rebuild only for v1 inside if not TYPE_CHECKING, keeps behavior aligned across versions while routing through the new utility helpers (model_dump, model_validate). The final model_rebuild block for DataType, BaseClassDataType, and DataModelFieldBase is symmetric for v1/v2 and looks correct.

It’s worth running a small smoke test under both Pydantic v1 and v2 to ensure forward refs and constraint handling behave identically (e.g., the existing integration tests that exercise both versions).

Also applies to: 74-90, 124-139, 168-181, 901-910


297-323: Imports fast path correctly skips typing imports when unnecessary

The imports property’s fast path (not has_union and not has_optional and not needs_annotated) short-circuits to tuple(self.data_type.all_imports), and the slow path still conditionally re-adds Union, Optional, and Annotated as needed. This preserves previous behavior while avoiding extra iteration and filtering for the common simple-field case.

Keep an eye on fields that use Annotated in subclasses (e.g. msgspec) to ensure use_annotated and needs_annotated_import remain in sync with how templates consume these imports.


443-462: Lazy Jinja2 imports with cached environments/templates are well-structured

Moving jinja2 imports into _get_environment and _get_environment_with_absolute_path and caching Environment/Template instances with @lru_cache gives the intended lazy-loading behavior while keeping template lookup order (custom dir first, then built-ins) intact. The TemplateBase.template override correctly routes absolute paths through _get_template_with_absolute_path and relative ones through _get_template_with_custom_dir.

Given Jinja2’s sensitivity to loader configuration, it’s worth re-running any tests that exercise custom template directories and absolute custom templates to confirm includes still resolve as expected.

Also applies to: 480-505, 508-566, 771-778

src/datamodel_code_generator/types.py (2)

55-56: Lazy StrictTypes exposure via __getattr__ preserves backwards compatibility

Defining __getattr__ to resolve StrictTypes from datamodel_code_generator.enums on first access ensures existing from datamodel_code_generator.types import StrictTypes imports continue to work while centralizing enum definitions. Pairing this with a TYPE_CHECKING-only import keeps type checkers happy without adding runtime cost.

If there are external users importing StrictTypes from this module, it’s worth running a quick import test (including from ... import StrictTypes and attribute access) to confirm behavior under both Python 3.10 and 3.11+.

Also applies to: 89-99, 100-108


55-56: DataType’s v1/v2 configuration and helpers look correct

Using is_pydantic_v2() with ConfigDict for v2 and a Config class (plus v1-only model_rebuild) for v1 keeps DataType’s behavior aligned with Pydantic expectations. The rest of the class—imports, type_hint, and base_type_hint—remains unchanged in semantics, so the version split is purely in config and forward-ref handling.

It’s a good idea to run the existing schema-to-type tests (including ones that exercise constrained types and unions) under both Pydantic v1 and v2 to confirm DataType still produces the same type hints and imports.

Also applies to: 96-106, 293-322, 523-551, 564-653, 669-776

src/datamodel_code_generator/__main__.py (5)

5-33: CLI fast paths for --version, --help, and --generate-prompt are narrowly scoped

The early checks on sys.argv only trigger for the specific single-flag --version/-V and --help/-h cases and for explicit --generate-prompt usage, so programmatic calls to main(args) are unaffected. This matches the performance goal of avoiding heavy imports for these quick operations.

Given past static-analysis concerns about module-level print/sys.exit, it’s worth confirming (e.g., via a tiny script) that importing this module from another program with unrelated sys.argv values does not trigger the fast paths.


87-93: Config’s v1/v2 handling and validators remain coherent with the new utilities

Switching Config’s v2 path to use ConfigDict and model_validate, and keeping the v1 path on inner Config plus dict-based validators, lines up with is_pydantic_v2() and the new compatibility helpers from util. The split between v2 instance-based validators and v1 classmethod-style validators appears consistent and keeps behavior intact.

Running the CLI with a mix of options that exercise these validators (e.g., --original-field-name-delimiter, --custom-file-header*, --keyword-only) under both Pydantic v1 and v2 would be a good sanity check.

Also applies to: 140-177, 331-385, 388-443, 564-577


906-912: Argcomplete integration is correctly isolated and lazy

Checking for _ARGCOMPLETE in os.environ before importing argcomplete and calling argcomplete.autocomplete(arg_parser) keeps tab-completion setup opt-in and avoids adding startup cost for normal invocations.

If you ship shell completion scripts, it’d be good to verify they still work with this guarded integration (especially on shells where _ARGCOMPLETE is set differently).


916-936: Main control flow remains clear and compatible with new features

The handling of --version, --generate_pyproject_config, --generate_prompt, pyproject loading, and the --check/diff logic all remain unchanged in semantics, with the only meaningful addition being the reuse of run_generate_from_config and the new stdout printing. Error messages for bad combinations like --check without --output or --watch with invalid inputs are still precise.

Also applies to: 969-979, 987-1001, 1110-1119


916-924: No regression in URL / RAW_DATA_TYPES handling with new input-type autodetection

The updated infer_input_type still routes text through load_yaml, and the except yaml.parser.ParserError fallback to InputFileType.CSV preserves the previous heuristic while avoiding a top-level YAML import. Combined with the reuse of already-read text for single-file inputs, this keeps autodetection behavior intact and efficient.

If you have fixtures with ambiguous inputs (YAML-like CSV or vice versa), re-run those to confirm the inferred type and downstream parsing still work as before.

Also applies to: 648-654, 993-1001, 1110-1119

src/datamodel_code_generator/util.py (4)

1-5: Centralized Pydantic version detection with caching and patching is well-designed

get_pydantic_version() applies the pydantic_patch before importing pydantic, parses the version once under @lru_cache, and returns (version, is_v2, is_v2_11). is_pydantic_v2() then adds a cheap global cache on top, which is appropriate for very hot paths. The PLW0603 suppression is justified given this explicit design.

If you maintain support for a wide range of Pydantic versions, a tiny harness that prints get_pydantic_version() and is_pydantic_v2() across those versions can help ensure the boundary checks (>= 2.0b3, >= 2.11) remain correct.

Also applies to: 32-47, 49-58


112-149: Version-aware model_validator and field_validator wrappers correctly route to v1/v2 APIs

Both decorators now branch on is_pydantic_v2() at decoration time: using Pydantic v2’s model_validator/field_validator in v2 and falling back to root_validator/validator in v1, with the mode == "before" mapping to pre=True correctly. This keeps your call sites uniform while handling both major versions.

As these wrappers abstract Pydantic’s public APIs, it’s worth re-running a representative subset of models using both v1 and v2 validators to ensure signatures and error messages are still as expected.

Also applies to: 177-193


196-214: Lazy ConfigDict and BaseModel exposure via proxies and __getattr__ is clean

_get_config_dict() and _ConfigDictProxy provide a simple way to call ConfigDict(...) where available and fall back to dict for v1-only paths, and _get_base_model_class() plus module-level __getattr__ expose a version-compatible BaseModel that sets strict=False on v2. The use of a cached _BaseModel instance avoids repeated imports and class generation.

Consider a quick import test (from datamodel_code_generator.util import BaseModel, ConfigDict) under both Pydantic v1 and v2 to verify that the resolved types behave as intended when subclassed.

Also applies to: 216-245


247-256: Compatibility helpers for dump/validate/fields/copy align with v1/v2 semantics

The model_dump, model_validate, get_fields_set, and model_copy helpers consistently branch on is_pydantic_v2() and delegate to the appropriate Pydantic methods (model_dump vs dict, model_validate vs parse_obj, model_fields_set vs __fields_set__, model_copy vs copy). This centralization should make it much easier to keep templates and models version-agnostic.

You might want to add or extend a small unit test module that exercises these helpers directly across both Pydantic versions for at least one simple BaseModel subclass.

Also applies to: 258-283

src/datamodel_code_generator/__init__.py (3)

26-47: Enums and type aliases are cleanly centralized and exported

Importing all the enums and constants (DataModelType, ReuseScope, TargetPydanticVersion, etc.) from datamodel_code_generator.enums simplifies this module and keeps the public API driven by a single enums source. The __all__ list still exposes the expected symbols, so external consumers shouldn’t see any breaking changes.

Also applies to: 59-68, 71-81, 942-963


26-27: YamlValue typing and lazy YAML loading are version-aware and safe

Using TypeAliasType for YamlValue under TYPE_CHECKING and at runtime only when is_pydantic_v2() is true, and falling back to a simpler dict[str, Any] | list[Any] | YamlScalar alias for v1, avoids Pydantic v1’s issues with recursive aliases while preserving richer typing for v2. Moving the YAML and SafeLoader imports into load_yaml (and yaml.parser into infer_input_type) keeps startup light and aligns with the new lazy-import strategy.

Re-run the YAML-related tests (including those that rely on type inference via infer_input_type) under both Pydantic v1 and v2 to confirm there are no regressions in parsing or typing.

Also applies to: 59-68, 71-81, 93-115


93-100: YAML error handling in infer_input_type aligns with the new loader

infer_input_type now imports yaml.parser locally and treats a yaml.parser.ParserError from load_yaml as the CSV signal, leaving other errors to surface normally. Combined with load_yaml’s use of the customized SafeLoader, this keeps the heuristic “YAML-parse failure => maybe CSV” intact without a top-level YAML dependency.

If there are edge-case fixtures for CSV-vs-YAML detection, it would be useful to verify that they still infer as expected with the new loader and error handling.

Also applies to: 215-263, 916-935

@koxudaxi koxudaxi force-pushed the perf/lazy-imports branch 2 times, most recently from af6a3c7 to 0df34d8 Compare December 25, 2025 10:34
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/model/base.py (2)

605-684: Per‑instance _dedup_key_cache is a reasonable optimization; consider documenting mutation assumptions.

Caching the (render(...), imports) tuple per (class_name, use_default) key avoids recomputing expensive template rendering and import collection when deduplication checks are repeated for the same model. This assumes the model (fields, imports, decorators) is effectively immutable after first use, which matches how models are constructed today; if future code mutates models post‑render, we might need cache invalidation. A short comment noting that expectation near _dedup_key_cache would make this clearer.

Also applies to: 709-726


901-910: Bottom model_rebuild branch is duplicated and could be simplified.

Both branches of the final if is_pydantic_v2(): ... else: execute identical model_rebuild calls with the same _rebuild_namespace. Since the v1/v2 behavior has already been handled inside the classes, you can collapse this into a single branch to reduce noise.

📜 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 bdfd2b7 and 0df34d8.

📒 Files selected for processing (6)
  • src/datamodel_code_generator/__init__.py
  • src/datamodel_code_generator/model/base.py
  • src/datamodel_code_generator/parser/jsonschema.py
  • src/datamodel_code_generator/reference.py
  • src/datamodel_code_generator/types.py
  • src/datamodel_code_generator/util.py
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2799
File: src/datamodel_code_generator/model/pydantic/__init__.py:43-43
Timestamp: 2025-12-25T09:22:14.661Z
Learning: In datamodel-code-generator project, defensive `# noqa: PLC0415` directives should be kept on lazy imports (imports inside functions/methods) even when Ruff reports them as unused via RUF100, to prepare for potential future Ruff configuration changes that might enable the import-outside-top-level rule.
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2799
File: src/datamodel_code_generator/util.py:49-66
Timestamp: 2025-12-25T09:22:57.664Z
Learning: In datamodel-code-generator, the is_pydantic_v2() and is_pydantic_v2_11() functions in src/datamodel_code_generator/util.py intentionally use global variable caching (_is_v2, _is_v2_11) on top of lru_cache for performance optimization. This dual-layer caching eliminates function call overhead and cache lookup overhead for frequently-called version checks. The PLW0603 linter warnings should be suppressed with # noqa: PLW0603 as this is a deliberate design choice.
📚 Learning: 2025-12-25T09:22:57.664Z
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2799
File: src/datamodel_code_generator/util.py:49-66
Timestamp: 2025-12-25T09:22:57.664Z
Learning: In datamodel-code-generator, the is_pydantic_v2() and is_pydantic_v2_11() functions in src/datamodel_code_generator/util.py intentionally use global variable caching (_is_v2, _is_v2_11) on top of lru_cache for performance optimization. This dual-layer caching eliminates function call overhead and cache lookup overhead for frequently-called version checks. The PLW0603 linter warnings should be suppressed with # noqa: PLW0603 as this is a deliberate design choice.

Applied to files:

  • src/datamodel_code_generator/__init__.py
  • src/datamodel_code_generator/parser/jsonschema.py
  • src/datamodel_code_generator/util.py
  • src/datamodel_code_generator/reference.py
  • src/datamodel_code_generator/types.py
  • src/datamodel_code_generator/model/base.py
📚 Learning: 2025-12-25T09:22:14.661Z
Learnt from: koxudaxi
Repo: koxudaxi/datamodel-code-generator PR: 2799
File: src/datamodel_code_generator/model/pydantic/__init__.py:43-43
Timestamp: 2025-12-25T09:22:14.661Z
Learning: In datamodel-code-generator project, defensive `# noqa: PLC0415` directives should be kept on lazy imports (imports inside functions/methods) even when Ruff reports them as unused via RUF100, to prepare for potential future Ruff configuration changes that might enable the import-outside-top-level rule.

Applied to files:

  • src/datamodel_code_generator/util.py
📚 Learning: 2025-12-18T13:43:16.235Z
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:16.235Z
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/util.py
🧬 Code graph analysis (4)
src/datamodel_code_generator/parser/jsonschema.py (1)
src/datamodel_code_generator/util.py (2)
  • is_pydantic_v2 (52-57)
  • model_dump (256-260)
src/datamodel_code_generator/util.py (1)
src/datamodel_code_generator/pydantic_patch.py (1)
  • apply_patch (24-29)
src/datamodel_code_generator/reference.py (2)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (29-54)
src/datamodel_code_generator/util.py (6)
  • camel_to_snake (250-253)
  • is_pydantic_v2 (52-57)
  • model_validator (117-122)
  • model_validator (126-132)
  • model_validator (136-140)
  • model_validator (143-174)
src/datamodel_code_generator/types.py (3)
src/datamodel_code_generator/model/pydantic_v2/__init__.py (1)
  • ConfigDict (29-54)
src/datamodel_code_generator/util.py (1)
  • is_pydantic_v2 (52-57)
src/datamodel_code_generator/enums.py (1)
  • StrictTypes (199-206)
🪛 Ruff (0.14.10)
src/datamodel_code_generator/__init__.py

72-72: Unused noqa directive (non-enabled: E402)

Remove unused noqa directive

(RUF100)


95-95: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


97-97: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


128-128: Unused noqa directive (non-enabled: PLW0603)

Remove unused noqa directive

(RUF100)


130-130: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


149-149: Unused noqa directive (non-enabled: PLW0603)

Remove unused noqa directive

(RUF100)


151-151: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


918-918: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/util.py

36-36: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


40-40: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


41-41: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


54-54: Unused noqa directive (non-enabled: PLW0603)

Remove unused noqa directive

(RUF100)


79-79: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


82-82: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


84-84: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


199-199: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


217-217: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


220-220: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


234-234: Unused noqa directive (non-enabled: PLW0603)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/types.py

93-93: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


185-185: Unused noqa directive (non-enabled: PLR2004)

Remove unused noqa directive

(RUF100)

src/datamodel_code_generator/model/base.py

446-446: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


483-483: Unused noqa directive (non-enabled: PLC0415)

Remove unused noqa directive

(RUF100)


720-720: Unused noqa directive (non-enabled: PLC0415)

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). (20)
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: 3.13 on Ubuntu
  • GitHub Check: py312-isort6 on Ubuntu
  • GitHub Check: benchmarks
  • GitHub Check: py312-pydantic1 on Ubuntu
  • GitHub Check: 3.14 on Windows
  • GitHub Check: 3.11 on Ubuntu
  • GitHub Check: 3.10 on macOS
  • GitHub Check: 3.13 on macOS
  • GitHub Check: py312-isort5 on Ubuntu
  • GitHub Check: py312-black22 on Ubuntu
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.11 on macOS
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.12 on Ubuntu
  • GitHub Check: py312-black24 on Ubuntu
  • GitHub Check: py312-black23 on Ubuntu
  • GitHub Check: 3.10 on Ubuntu
  • GitHub Check: Analyze (python)
🔇 Additional comments (23)
src/datamodel_code_generator/parser/jsonschema.py (2)

80-89: Pydantic v2 gating and model_config look correct and consistent with util helpers.

Using is_pydantic_v2() for both the ConfigDict import and the JsonSchemaObject get_fields/model_rebuild/model_config split keeps the v1/v2 behavior centralized and aligned with the new util helpers. The runtime-only definitions under if not TYPE_CHECKING also avoid pyright noise while still supporting JsonSchemaObject.model_rebuild() via the inherited BaseModel method on v2. Based on learnings, the extra calls to is_pydantic_v2() are cheap due to the global cached flag on top of get_pydantic_version(). Please rerun schema tests under both Pydantic v1 and v2 to confirm no behavioral drift.

Also applies to: 91-93, 204-227, 369-383


1084-1100: Switch to model_dump for constraints/merging is version-safe and the exclude_none usage is appropriate.

Replacing direct .dict() calls with the shared model_dump() helper (plus exclude_unset=True / by_alias=True where previously used) keeps all these code paths compatible with both Pydantic v1 and v2. Using exclude_none=True specifically for constraint dictionaries (get_object_field, array/root/mixed-root constraints, and primitive/allOf merges) avoids emitting spurious None entries while preserving falsy-but-meaningful values like False and 0. This looks semantically equivalent or stricter than before, but please ensure the JSON Schema constraint tests (especially around hostname pattern, allOf/anyOf merges, and root model constraints) are exercised on both Pydantic versions.

Also applies to: 1131-1132, 1239-1244, 1253-1267, 1281-1287, 1363-1367, 1563-1573, 1716-1722, 1736-1761, 2617-2636, 2754-2756, 2821-2823

src/datamodel_code_generator/reference.py (2)

37-39: Version-aware _BaseModel dict override and config are sound.

The split dict implementation based on is_pydantic_v2() preserves the existing exclusion semantics (always unioning _exclude_fields into exclude) while delegating to model_dump on v2 and BaseModel.dict on v1. Using ConfigDict vs an inner Config class to set arbitrary_types_allowed and keep_untouched/ignored_types matches the expected Pydantic v2/v1 patterns and keeps _BaseModel usable across both. Since this depends on Pydantic internals, please confirm serialization of Reference/resolver-related models still matches snapshots on both v1 and v2.

Also applies to: 81-90, 95-139


156-167: Reference model_config migration to ConfigDict maintains prior behavior.

The new model_config = ConfigDict(...) for v2 mirrors the v1 Config attributes (arbitrary_types_allowed, keep_untouched via ignored_types, and instance revalidation policy) and keeps cached_property untouched as before. This should be transparent to calling code while aligning with Pydantic v2’s config style. Please verify that constructing and serializing Reference instances behaves the same under both Pydantic versions (especially around cached_property usage and revalidation).

Also applies to: 168-184

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

39-40: ConstraintsBase v1/v2 config split is correct and uses shared helpers.

Using ConfigDict when is_pydantic_v2() is true and a v1 Config class otherwise mirrors the pattern in other modules and keeps arbitrary_types_allowed/keep_untouched behavior the same. has_constraints now goes through model_dump, which is the right central hook for v1/v2 compatibility.

Also applies to: 74-90


124-139: DataModelFieldBase Pydantic config and v1-only model_rebuild are aligned with the new BaseModel abstraction.

The model_config = ConfigDict(...) for v2 and legacy Config class for v1 follow the same pattern as other models. Defining a custom model_rebuild() only on v1 (using update_forward_refs) while letting v2 inherit the default behavior is appropriate, and the bottom-of-file rebuild block correctly passes a types namespace for both versions.

Also applies to: 168-180, 901-910


181-187: self_reference caching is safe and avoids repeated graph walks.

Caching the boolean result in __dict__['_self_reference_cache'] sidesteps Pydantic v1’s attribute restrictions and keeps subsequent self_reference() calls O(1). The computation itself still correctly checks for a parent reference and compares against all referenced data types. This looks good for both correctness and performance.

Also applies to: 208-221


296-323: Fast path in DataModelFieldBase.imports preserves semantics while reducing work.

Computing has_union, has_optional, and needs_annotated from the already-built type_hint and short‑circuiting to tuple(self.data_type.all_imports) when none apply avoids the extra filtering/chain_as_tuple overhead on the common path. When any of those flags are set, the logic still mirrors the previous behavior (dropping unused UNION/OPTIONAL imports and explicitly adding the needed ones, plus Annotated when required).


443-462: Lazy Jinja2 environment/template loading is correctly encapsulated and keeps PLC0415 noqa directives for future‑proofing.

The new _get_environment* and _get_template* helpers lazily import Jinja2, support custom template directories and absolute paths, and are safely cached by lru_cache. Using # noqa: PLC0415 on the lazy imports matches the project’s convention (and should be kept despite RUF100) to guard against future “import at top level” rules. Please verify custom template override tests (including includes) still pass.

Also applies to: 480-505, 761-778

src/datamodel_code_generator/types.py (3)

55-56: Lazy StrictTypes exposure via __getattr__ maintains backward compatibility.

Redirecting StrictTypes accesses through __getattr__ to datamodel_code_generator.enums.StrictTypes preserves from datamodel_code_generator.types import StrictTypes for callers while letting you centralize the enum in enums.py. Keeping the lazy import guarded with # noqa: PLC0415 is consistent with the project’s convention for function‑scope imports despite RUF100.

Also applies to: 90-97, 100-107


180-188: chain_as_tuple two‑iterable fast path is safe and avoids itertools.chain overhead.

The new special case for len(iterables) == 2 simply expands both iterables into a single tuple, which is equivalent to the previous tuple(chain(*iterables)) behavior (including for generators) but slightly cheaper in the common 2‑way case. No functional concerns here.


109-112: DataType Pydantic config and init changes align with v1/v2 support and lazy imports.

Conditionally importing GetCoreSchemaHandler/core_schema only when is_pydantic_v2() is true keeps v1 environments from requiring pydantic_core at runtime. The model_config split (ConfigDict with extra="forbid"/revalidate_instances="never" vs v1 Config with extra="forbid"/copy_on_model_validation) matches expectations, and the v1‑only model_rebuild override mirrors the pattern used in other models. Wrapping super().__init__ in if not TYPE_CHECKING keeps type checkers happy without affecting runtime. Please re-run type-hint and generation tests under both Pydantic versions to confirm.

Also applies to: 293-321, 523-552

src/datamodel_code_generator/util.py (5)

32-47: Version detection and global caching in is_pydantic_v2() match the agreed design.

get_pydantic_version() centralizes the apply_patch() call and version parsing behind an lru_cache(maxsize=1), and is_pydantic_v2() then adds a global _is_v2 flag to eliminate repeated function/call‑site overhead on hot paths. This aligns with the prior decision to keep the PLW0603‑suppressed global caching for performance and should be kept as‑is despite lint nudges. Based on learnings, this is the intended pattern.

Also applies to: 49-58


60-74: YAML bool deprecation warning and lazy SafeLoader are functionally sound.

The new _construct_yaml_bool_with_warning() cleanly warns on legacy uppercase YAML bools while still accepting them, and get_safe_loader() now lazily builds a customized loader (with timestamp and bool resolvers adjusted) under an lru_cache(maxsize=1). Exposing this via __getattr__ as SafeLoader keeps the external API simple and avoids touching PyYAML until actually needed. Please ensure YAML parsing tests (especially around bools and timestamps) cover both old and new forms.

Also applies to: 76-110, 232-241


196-212: ConfigDict proxy cleanly defers the Pydantic v2 import.

_ConfigDictProxy plus _get_config_dict() let callers write ConfigDict(...) without importing Pydantic at module import time, while still constructing a real pydantic.ConfigDict under the hood when used. This is a neat way to share the v2 config object across modules while keeping the import lazy; just avoid using isinstance(..., ConfigDict) anywhere, since it’s a proxy, not the actual type.


214-227: Lazy BaseModel selection and module __getattr__ are well-structured.

_get_base_model_class() chooses between a v2 subclass with model_config = ConfigDict(strict=False) and the raw Pydantic BaseModel, and __getattr__ uses it to expose BaseModel lazily while also delegating SafeLoader to get_safe_loader(). The use of global _BaseModel with # noqa: PLW0603 is consistent with the project’s performance/lint strategy. Please confirm that all internal imports of BaseModel now go through datamodel_code_generator.util so this indirection is consistently applied.

Also applies to: 229-241


116-175: Pydantic v1/v2 decorators and helper wrappers are correctly unified on is_pydantic_v2().

model_validator and field_validator now dispatch to pydantic.model_validator/field_validator on v2 and to root_validator/validator on v1, which is exactly what you need to keep shared decorators in templates/models. Likewise, model_dump, model_validate, get_fields_set, and model_copy wrap the v2 model_* APIs and the v1 dict/parse_obj/__fields_set__/copy APIs behind a consistent interface. This should significantly simplify caller code; re-running tests that exercise validators, defaults, and field sets on both Pydantic versions will be important.

Also applies to: 177-193, 256-281

src/datamodel_code_generator/__init__.py (6)

26-47: LGTM: Centralized enum imports improve modularity.

The TypeAliasType import and centralized enum imports from enums.py align well with the lazy loading objectives. This reduces duplication and improves maintainability.


95-98: LGTM: Lazy imports for YAML dependencies.

The lazy imports of yaml and SafeLoader defer loading until load_yaml() is called, improving startup time. The # noqa: PLC0415 directives should be retained despite RUF100 warnings—they're defensive annotations in case the import-outside-top-level rule is enabled in future Ruff configurations.

Based on learnings, these defensive directives are intentional.


126-136: LGTM: Lazy loading with improved error handling.

The lazy import of pysnooper and the chained exception with installation guidance are well-implemented. The # noqa: PLW0603 and # noqa: PLC0415 directives are intentional defensive annotations per project learnings and should be retained.


142-159: LGTM: Lazy pysnooper initialization with state management.

The lazy import pattern with early return (lines 150-153) and the _pysnooper_default_state_set flag correctly prevent redundant initialization when the decorator is applied to multiple classes. The # noqa directives are intentional defensive annotations per project learnings.


918-919: LGTM: Lazy import for YAML parser.

The lazy import of yaml.parser defers loading until infer_input_type() is called. The # noqa: PLC0415 directive should be retained as a defensive annotation per project learnings.


74-80: The module-level is_pydantic_v2() call is acceptable—pydantic is a required dependency.

Pydantic (v1.5+) is declared as a required dependency in pyproject.toml, not optional. The module-level version check at line 76 is appropriate for a required dependency, and the dual-layer caching (global variable + lru_cache) in is_pydantic_v2() is an intentional performance optimization to eliminate both function call and cache lookup overhead.

Likely an incorrect or invalid review comment.

@koxudaxi koxudaxi merged commit 767714f into main Dec 25, 2025
38 checks passed
@koxudaxi koxudaxi deleted the perf/lazy-imports branch December 25, 2025 12:15
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.

2 participants