Return str or dict when output=None in generate()#2787
Conversation
📝 WalkthroughWalkthroughThis PR makes generate() return generated code when output=None: a single module returns a string, multiple modules return a Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~35 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
cdc64fd to
7a6c76b
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/datamodel_code_generator/__init__.py (1)
965-992: Consider consolidating duplicate future import extraction logic.The future import extraction logic (lines 965-992) duplicates the logic in
_build_module_content(lines 459-485). While the file writing path usesprint()statements versus string concatenation, this could potentially be refactored to reduce duplication.This is a minor observation; the current implementation is correct and functional.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
docs/using_as_module.mdsrc/datamodel_code_generator/__init__.pysrc/datamodel_code_generator/__main__.pytests/main/test_main_general.pytests/test_main_kr.py
🧰 Additional context used
🧬 Code graph analysis (3)
tests/test_main_kr.py (3)
tests/main/openapi/test_main_openapi.py (1)
test_main_modular_no_file(463-470)tests/main/conftest.py (1)
run_main_with_args(215-241)src/datamodel_code_generator/__main__.py (1)
Exit(94-100)
src/datamodel_code_generator/__init__.py (1)
src/datamodel_code_generator/format.py (1)
Formatter(162-168)
src/datamodel_code_generator/__main__.py (1)
src/datamodel_code_generator/__init__.py (1)
generate(489-1016)
⏰ 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.10 on macOS
- GitHub Check: py312-black24 on Ubuntu
- GitHub Check: 3.10 on Windows
- GitHub Check: py312-isort5 on Ubuntu
- GitHub Check: 3.11 on macOS
- GitHub Check: benchmarks
- GitHub Check: py312-pydantic1 on Ubuntu
- GitHub Check: 3.11 on Ubuntu
- GitHub Check: py312-isort6 on Ubuntu
- GitHub Check: 3.13 on Windows
- GitHub Check: 3.12 on macOS
- GitHub Check: py312-isort7 on Ubuntu
- GitHub Check: Analyze (python)
- GitHub Check: 3.13 on macOS
- GitHub Check: 3.13 on Ubuntu
- GitHub Check: 3.11 on Windows
- GitHub Check: 3.12 on Ubuntu
- GitHub Check: 3.14 on Windows
- GitHub Check: 3.14 on Ubuntu
- GitHub Check: 3.12 on Windows
🔇 Additional comments (13)
tests/test_main_kr.py (1)
104-109: LGTM!The test correctly validates the new behavior where modular output without a file path now succeeds and prints generated content to stdout. The assertions for
"class Chocolate"and"class Source"verify that the expected models are present in the output.src/datamodel_code_generator/__main__.py (1)
849-855: LGTM!The stdout output logic correctly handles both single-module (string) and multi-module (mapping) return types from
generate(). Adding a trailing newline ensures proper stdout formatting.docs/using_as_module.md (2)
15-62: LGTM!The documentation clearly explains the new return value behavior with practical examples. The type hint
str | GeneratedModulesand the handling pattern for both cases are well-documented.
190-198: LGTM!The return value summary table is a helpful addition that clearly documents the behavior matrix for different
outputparameter scenarios.src/datamodel_code_generator/__init__.py (4)
85-90: LGTM!The
GeneratedModulestype alias is well-documented and provides a clear contract for the multi-module return type.
448-486: LGTM!The
_build_module_contenthelper correctly handles future import extraction and placement when a custom file header is provided. The logic properly preserves the docstring position while inserting__future__imports in the correct location.
606-616: LGTM!The updated return type and docstring accurately describe the three possible return scenarios.
921-935: LGTM!The new return logic correctly handles both single-module (string) and multi-module (GeneratedModules dict) cases when
outputis None.tests/main/test_main_general.py (5)
10-21: LGTM!The new imports for
inline_snapshotandGeneratedModulesare appropriate for the added tests.
1442-1464: LGTM!This test validates the core new functionality:
generate()returns a string whenoutput=Nonefor single-file schemas. The snapshot testing approach ensures the output format is verified.
1518-1554: LGTM!These tests correctly verify that:
generate()returnsNonewhen an output path is provided- The file content matches what would be returned with
output=NoneThe
.strip()comparison on line 1554 appropriately handles potential trailing whitespace differences.
1557-1642: LGTM!This test validates the multi-module return behavior using a directory input with cross-file references. The snapshot correctly captures the
GeneratedModulesdict structure with module path tuples as keys.
1650-1717: LGTM!These tests comprehensively cover custom file header scenarios:
- Basic custom header
- Custom header with code after docstring (testing
__future__import placement)- Custom header with
disable_future_imports=True
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/datamodel_code_generator/__init__.py (2)
448-487: Code duplication between_build_module_contentand file-writing logic.The future imports extraction logic at lines 463-477 is nearly identical to lines 969-985 in the file-writing path. Consider extracting shared logic to reduce duplication, though this is not critical for this PR.
🔎 Potential refactor to reduce duplication
You could extract the future imports handling into a separate helper:
def _extract_future_imports(body: str) -> tuple[str, str]: """Extract future imports from body, returning (extracted, body_without_future).""" lines = body.split("\n") future_indices = [i for i, line in enumerate(lines) if line.strip().startswith("from __future__")] if not future_indices: return "", body extracted = "\n".join(lines[i] for i in future_indices) remaining = [line for i, line in enumerate(lines) if i not in future_indices] return extracted, "\n".join(remaining).lstrip("\n")This could be used in both
_build_module_contentand the file-writing path.
958-1000: Consider using a context manager for file handling.The file is opened at line 960 but closed manually at line 1000. Using a context manager would be safer against exceptions during write operations.
🔎 Proposed refactor using context manager
for path, (body, future_imports, filename) in modules.items(): if not path.parent.exists(): path.parent.mkdir(parents=True) - file = path.open("wt", encoding=encoding) + with path.open("wt", encoding=encoding) as file: - safe_filename = filename.replace("\n", " ").replace("\r", " ") if filename else "" - effective_header = custom_file_header or header.format(safe_filename) + safe_filename = filename.replace("\n", " ").replace("\r", " ") if filename else "" + effective_header = custom_file_header or header.format(safe_filename) - if custom_file_header and body: - # ... (indent rest of the block) + if custom_file_header and body: + # ... (rest of the block) - file.close()
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
docs/using_as_module.mdsrc/datamodel_code_generator/__init__.pysrc/datamodel_code_generator/__main__.pytests/main/test_main_general.pytests/test_main_kr.py
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/test_main_kr.py
🧰 Additional context used
🧬 Code graph analysis (2)
tests/main/test_main_general.py (1)
src/datamodel_code_generator/__init__.py (4)
AllExportsScope(259-267)DataModelType(226-234)generate(489-1016)InputFileType(204-214)
src/datamodel_code_generator/__main__.py (1)
src/datamodel_code_generator/__init__.py (1)
generate(489-1016)
⏰ 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 Ubuntu
- GitHub Check: py312-isort5 on Ubuntu
- GitHub Check: py312-black22 on Ubuntu
- GitHub Check: py312-pydantic1 on Ubuntu
- GitHub Check: 3.11 on Windows
- GitHub Check: 3.10 on macOS
- GitHub Check: py312-isort7 on Ubuntu
- GitHub Check: 3.11 on macOS
- GitHub Check: py312-black24 on Ubuntu
- GitHub Check: 3.11 on Ubuntu
- GitHub Check: 3.13 on Windows
- GitHub Check: 3.12 on Ubuntu
- GitHub Check: 3.14 on Windows
- GitHub Check: 3.10 on Windows
- GitHub Check: 3.12 on macOS
- GitHub Check: 3.13 on Ubuntu
- GitHub Check: 3.12 on Windows
- GitHub Check: 3.13 on macOS
- GitHub Check: benchmarks
🔇 Additional comments (10)
src/datamodel_code_generator/__main__.py (1)
734-855: LGTM! Clean implementation of stdout printing for generated content.The changes correctly:
- Capture the return value from
generate()- Print string results directly when
output is None- Iterate over
GeneratedModulesdict values for multi-module outputdocs/using_as_module.md (2)
15-62: Documentation looks comprehensive and well-structured.The examples clearly demonstrate:
- Getting generated code as a string (single module)
- Handling
GeneratedModulesdict for multi-module schemas- The
isinstance(result, dict)check correctly distinguishes between return types
190-199: Return Value Summary table is accurate and helpful.The table correctly documents the behavior, including that a file path with multiple modules raises an error.
src/datamodel_code_generator/__init__.py (3)
85-91: Well-documented type alias.The
GeneratedModulesTypeAlias with its docstring clearly explains the purpose and structure of the return type for multi-module generation.
921-935: Clean implementation of in-memory return for both single and multi-module cases.The logic correctly:
- Returns a
strfor single-file output- Returns a
GeneratedModulesdict with sorted keys for deterministic ordering- Applies headers consistently to all modules
1052-1052: LGTM!GeneratedModulescorrectly exported.Adding
GeneratedModulesto__all__makes it part of the public API, enabling users to type-hint their code when working with multi-module generation.tests/main/test_main_general.py (4)
1439-1461: Good test coverage for basic string return.The test correctly validates:
- Return type is
strwhenoutput=None- Generated content structure with header, imports, and model
1554-1639: Comprehensive test for multi-module generation.The test properly validates the
GeneratedModulesreturn type with tuple keys mapping to generated code strings.Note: The snapshot shows
test_generate_returns_dict_for0as the filename in__init__.py(lines 1597-1598), which appears truncated. This is likely because the directory name (tmp_path) is used as input_filename fallback, which is expected behavior for directory inputs.
1647-1714: Excellent edge case coverage for custom file headers.The three tests thoroughly validate:
- Basic custom header with future imports placement
- Custom header with docstring and code - future imports inserted after docstring
- Custom header when
disable_future_imports=True- no future import handling needed
1642-1644: Simple but effective export verification.The test confirms
GeneratedModulesis importable from the public API. The assertion is minimal but sufficient since the import at line 16 would fail if the export was missing.
CodSpeed Performance ReportMerging #2787 will not alter performanceComparing Summary
Footnotes
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2787 +/- ##
========================================
Coverage 99.47% 99.48%
========================================
Files 88 88
Lines 13213 13348 +135
Branches 1556 1565 +9
========================================
+ Hits 13144 13279 +135
Misses 36 36
Partials 33 33
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Breaking Change AnalysisResult: Breaking changes detected Reasoning: This PR contains several breaking changes: 1) The Content for Release NotesAPI/CLI Changes
Default Behavior Changes
# Before: No output
datamodel-codegen --input schema.json
# After: Prints generated code to stdout
datamodel-codegen --input schema.jsonError Handling Changes
This analysis was performed by Claude Code Action |
* Add --collapse-root-models-name-strategy option * docs: update CLI reference documentation and prompt data 🤖 Generated by GitHub Actions * Add pragma no cover for defensive edge cases * Achieve 100% diff coverage for collapse-root-models-name-strategy * Use cast instead of type ignore comment * Remove line comments from collapse-root-models implementation * Add complex e2e tests for collapse-root-models-name-strategy * Update reference metadata when renaming in parent strategy * Refactor collapse-root-models tests to use parameterization for v1/v2 * Add schema path context to error messages (#2786) * Return str or dict when output=None in generate() (#2787) * Add --http-timeout CLI option (#2788) * Add --http-timeout CLI option for configurable HTTP request timeout * docs: update CLI reference documentation and prompt data 🤖 Generated by GitHub Actions --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * Pass schema extensions to templates (#2790) * Pass schema extensions to templates * Move model_base import to top of file * Add schema extensions documentation Document how x-* schema extensions are passed to custom templates via the extensions variable, with examples for database model configuration and other use cases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) * Add propertyNames and x-propertyNames support (#2789) * Add propertyNames and x-propertyNames support * Fix Pydantic v1 compatibility for x-propertyNames Use the model_validate utility function from util module instead of calling JsonSchemaObject.model_validate() directly, which only exists in Pydantic v2. 🤖 Generated with [Claude Code](https://claude.com/claude-code) * Add test for x-propertyNames non-dict branch coverage Test that x-propertyNames with non-dict value (e.g., boolean) is correctly ignored, achieving 100% diff coverage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) * Add support for additional_imports in extra-template-data JSON (#2793) * Update zensical to 0.0.15 (#2794) * Add --use-field-description-example option (#2792) * Add --use-field-description-example option * docs: update CLI reference documentation and prompt data 🤖 Generated by GitHub Actions * Add tests for complete branch coverage of docstring property --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * Fix formatting in test file --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Fixes: #423
Summary by CodeRabbit
New Features
generate()can return generated code as a string (single module) or a mapping of modules to code (multi-module)Documentation
Behavior Changes
Tests
✏️ Tip: You can customize this high-level summary in your review settings.