Skip to content

Commit 67ea24b

Browse files
feat: Add --module-split-mode option to generate one file per model (#1170) (#2685)
* feat: Add module split mode for generating separate files per model class * docs: update CLI reference documentation 🤖 Generated by GitHub Actions * refactor: Update __change_from_import method signature for clarity * refactor: Improve readability of conditional statement in base.py * refactor: Move camel_to_snake to util.py to fix cyclic import * refactor: Update imports in order.py and test_main_general.py for clarity and consistency * docs: update CLI reference documentation 🤖 Generated by GitHub Actions --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 0574548 commit 67ea24b

16 files changed

Lines changed: 300 additions & 30 deletions

File tree

docs/cli-reference/general-options.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
| [`--http-ignore-tls`](#http-ignore-tls) | Disable TLS certificate verification for HTTPS requests. |
1515
| [`--http-query-parameters`](#http-query-parameters) | Add query parameters to HTTP requests for remote schemas. |
1616
| [`--ignore-pyproject`](#ignore-pyproject) | Ignore pyproject.toml configuration file. |
17+
| [`--module-split-mode`](#module-split-mode) | Split generated models into separate files, one per model cl... |
1718
| [`--shared-module-name`](#shared-module-name) | Customize the name of the shared module for deduplicated mod... |
1819
| [`--watch`](#watch) | Watch mode cannot be used with --check mode. |
1920
| [`--watch-delay`](#watch-delay) | Watch mode starts file watcher and handles clean exit. |
@@ -1672,6 +1673,116 @@ testing without project configuration.
16721673

16731674
---
16741675

1676+
## `--module-split-mode` {#module-split-mode}
1677+
1678+
Split generated models into separate files, one per model class.
1679+
1680+
The `--module-split-mode=single` flag generates each model class in its own file,
1681+
named after the class in snake_case. Use with `--all-exports-scope=recursive` to
1682+
create an __init__.py that re-exports all models for convenient imports.
1683+
1684+
**Related:** [`--all-exports-scope`](general-options.md#all-exports-scope), [`--use-exact-imports`](template-customization.md#use-exact-imports)
1685+
1686+
!!! tip "Usage"
1687+
1688+
```bash
1689+
datamodel-codegen --input schema.json --module-split-mode single --all-exports-scope recursive --use-exact-imports # (1)!
1690+
```
1691+
1692+
1. :material-arrow-left: `--module-split-mode` - the option documented here
1693+
1694+
??? example "Input Schema"
1695+
1696+
```json
1697+
{
1698+
"$schema": "http://json-schema.org/draft-07/schema#",
1699+
"definitions": {
1700+
"User": {
1701+
"type": "object",
1702+
"properties": {
1703+
"id": {"type": "integer"},
1704+
"name": {"type": "string"}
1705+
}
1706+
},
1707+
"Order": {
1708+
"type": "object",
1709+
"properties": {
1710+
"id": {"type": "integer"},
1711+
"user": {"$ref": "#/definitions/User"}
1712+
}
1713+
}
1714+
}
1715+
}
1716+
```
1717+
1718+
??? example "Output"
1719+
1720+
```python
1721+
# __init__.py
1722+
# generated by datamodel-codegen:
1723+
# filename: input.json
1724+
1725+
from __future__ import annotations
1726+
1727+
from .model import Model
1728+
from .order import Order
1729+
from .user import User
1730+
1731+
__all__ = [
1732+
"Model",
1733+
"Order",
1734+
"User",
1735+
]
1736+
1737+
# model.py
1738+
# generated by datamodel-codegen:
1739+
# filename: input.json
1740+
1741+
from __future__ import annotations
1742+
1743+
from typing import Any
1744+
1745+
from pydantic import BaseModel
1746+
1747+
1748+
class Model(BaseModel):
1749+
__root__: Any
1750+
1751+
# order.py
1752+
# generated by datamodel-codegen:
1753+
# filename: input.json
1754+
1755+
from __future__ import annotations
1756+
1757+
from typing import Optional
1758+
1759+
from pydantic import BaseModel
1760+
1761+
from .user import User
1762+
1763+
1764+
class Order(BaseModel):
1765+
id: Optional[int] = None
1766+
user: Optional[User] = None
1767+
1768+
# user.py
1769+
# generated by datamodel-codegen:
1770+
# filename: input.json
1771+
1772+
from __future__ import annotations
1773+
1774+
from typing import Optional
1775+
1776+
from pydantic import BaseModel
1777+
1778+
1779+
class User(BaseModel):
1780+
id: Optional[int] = None
1781+
name: Optional[str] = None
1782+
```
1783+
1784+
---
1785+
16751786
## `--shared-module-name` {#shared-module-name}
16761787

16771788
Customize the name of the shared module for deduplicated models.

docs/cli-reference/index.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ This documentation is auto-generated from test cases.
1414
| 🏗️ [Model Customization](model-customization.md) | 26 | Model generation behavior |
1515
| 🎨 [Template Customization](template-customization.md) | 16 | Output formatting and custom rendering |
1616
| 📘 [OpenAPI-only Options](openapi-only-options.md) | 5 | OpenAPI-specific features |
17-
| ⚙️ [General Options](general-options.md) | 13 | Utilities and meta options |
17+
| ⚙️ [General Options](general-options.md) | 14 | Utilities and meta options |
1818
| 📝 [Utility Options](utility-options.md) | 5 | Help, version, debug options |
1919

2020
## All Options
2121

22-
**Jump to:** [A](#a) · [B](#b) · [C](#c) · [D](#d) · [E](#e) · [F](#f) · [G](#g) · [H](#h) · [I](#i) · [K](#k) · [N](#n) · [O](#o) · [P](#p) · [R](#r) · [S](#s) · [T](#t) · [U](#u) · [V](#v) · [W](#w)
22+
**Jump to:** [A](#a) · [B](#b) · [C](#c) · [D](#d) · [E](#e) · [F](#f) · [G](#g) · [H](#h) · [I](#i) · [K](#k) · [M](#m) · [N](#n) · [O](#o) · [P](#p) · [R](#r) · [S](#s) · [T](#t) · [U](#u) · [V](#v) · [W](#w)
2323

2424

2525
### A {#a}
@@ -102,6 +102,10 @@ This documentation is auto-generated from test cases.
102102
- [`--keep-model-order`](model-customization.md#keep-model-order)
103103
- [`--keyword-only`](model-customization.md#keyword-only)
104104

105+
### M {#m}
106+
107+
- [`--module-split-mode`](general-options.md#module-split-mode)
108+
105109
### N {#n}
106110

107111
- [`--no-alias`](field-customization.md#no-alias)

docs/cli-reference/quick-reference.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ datamodel-codegen [OPTIONS]
144144
| [`--http-ignore-tls`](general-options.md#http-ignore-tls) | Disable TLS certificate verification for HTTPS requests. |
145145
| [`--http-query-parameters`](general-options.md#http-query-parameters) | Add query parameters to HTTP requests for remote schemas. |
146146
| [`--ignore-pyproject`](general-options.md#ignore-pyproject) | Ignore pyproject.toml configuration file. |
147+
| [`--module-split-mode`](general-options.md#module-split-mode) | Split generated models into separate files, one per model class. |
147148
| [`--shared-module-name`](general-options.md#shared-module-name) | Customize the name of the shared module for deduplicated models. |
148149
| [`--watch`](general-options.md#watch) | Watch mode cannot be used with --check mode. |
149150
| [`--watch-delay`](general-options.md#watch-delay) | Watch mode starts file watcher and handles clean exit. |
@@ -214,6 +215,7 @@ All options sorted alphabetically:
214215
- [`--input-file-type`](base-options.md#input-file-type) - Specify the input file type for code generation.
215216
- [`--keep-model-order`](model-customization.md#keep-model-order) - Keep model definition order as specified in schema.
216217
- [`--keyword-only`](model-customization.md#keyword-only) - Generate dataclasses with keyword-only fields (Python 3.10+)...
218+
- [`--module-split-mode`](general-options.md#module-split-mode) - Split generated models into separate files, one per model cl...
217219
- [`--no-alias`](field-customization.md#no-alias) - Disable Field alias generation for non-Python-safe property ...
218220
- [`--no-color`](utility-options.md#no-color) - Disable colorized output
219221
- [`--no-use-specialized-enum`](typing-customization.md#no-use-specialized-enum) - Disable specialized Enum classes for Python 3.11+ code gener...

src/datamodel_code_generator/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,15 @@ class ReadOnlyWriteOnlyModelType(Enum):
300300
All = "all"
301301

302302

303+
class ModuleSplitMode(Enum):
304+
"""Mode for splitting generated models into separate files.
305+
306+
Single: Generate one file per model class.
307+
"""
308+
309+
Single = "single"
310+
311+
303312
class Error(Exception):
304313
"""Base exception for datamodel-code-generator errors."""
305314

@@ -470,6 +479,7 @@ def generate( # noqa: PLR0912, PLR0913, PLR0914, PLR0915
470479
read_only_write_only_model_type: ReadOnlyWriteOnlyModelType | None = None,
471480
all_exports_scope: AllExportsScope | None = None,
472481
all_exports_collision_strategy: AllExportsCollisionStrategy | None = None,
482+
module_split_mode: ModuleSplitMode | None = None,
473483
) -> None:
474484
"""Generate Python data models from schema definitions or structured data.
475485
@@ -718,6 +728,7 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]:
718728
disable_future_imports=disable_future_imports,
719729
all_exports_scope=all_exports_scope,
720730
all_exports_collision_strategy=all_exports_collision_strategy,
731+
module_split_mode=module_split_mode,
721732
)
722733
if not input_filename: # pragma: no cover
723734
if isinstance(input_, str):
@@ -857,6 +868,7 @@ def infer_input_type(text: str) -> InputFileType:
857868
"InputFileType",
858869
"InvalidClassNameError",
859870
"LiteralType",
871+
"ModuleSplitMode",
860872
"PythonVersion",
861873
"ReadOnlyWriteOnlyModelType",
862874
"generate",

src/datamodel_code_generator/__main__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
Error,
3232
InputFileType,
3333
InvalidClassNameError,
34+
ModuleSplitMode,
3435
OpenAPIScope,
3536
ReadOnlyWriteOnlyModelType,
3637
ReuseScope,
@@ -464,6 +465,7 @@ def validate_all_exports_collision_strategy(cls, values: dict[str, Any]) -> dict
464465
read_only_write_only_model_type: Optional[ReadOnlyWriteOnlyModelType] = None # noqa: UP045
465466
all_exports_scope: Optional[AllExportsScope] = None # noqa: UP045
466467
all_exports_collision_strategy: Optional[AllExportsCollisionStrategy] = None # noqa: UP045
468+
module_split_mode: Optional[ModuleSplitMode] = None # noqa: UP045
467469
watch: bool = False
468470
watch_delay: float = 0.5
469471

@@ -765,6 +767,7 @@ def run_generate_from_config( # noqa: PLR0913, PLR0917
765767
read_only_write_only_model_type=config.read_only_write_only_model_type,
766768
all_exports_scope=config.all_exports_scope,
767769
all_exports_collision_strategy=config.all_exports_collision_strategy,
770+
module_split_mode=config.module_split_mode,
768771
)
769772

770773

src/datamodel_code_generator/arguments.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
DataclassArguments,
2323
DataModelType,
2424
InputFileType,
25+
ModuleSplitMode,
2526
OpenAPIScope,
2627
ReadOnlyWriteOnlyModelType,
2728
ReuseScope,
@@ -323,6 +324,12 @@ def start_section(self, heading: str | None) -> None:
323324
choices=[s.value for s in AllExportsCollisionStrategy],
324325
default=None,
325326
)
327+
model_options.add_argument(
328+
"--module-split-mode",
329+
help="Split generated models into separate files. 'single': generate one file per model class.",
330+
choices=[m.value for m in ModuleSplitMode],
331+
default=None,
332+
)
326333

327334
# ======================================================================================
328335
# Typing options for generated models

src/datamodel_code_generator/cli_options.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ class CLIOptionMeta:
206206
"--all-exports-collision-strategy": CLIOptionMeta(
207207
name="--all-exports-collision-strategy", category=OptionCategory.GENERAL
208208
),
209+
"--module-split-mode": CLIOptionMeta(name="--module-split-mode", category=OptionCategory.GENERAL),
209210
"--disable-warnings": CLIOptionMeta(name="--disable-warnings", category=OptionCategory.GENERAL),
210211
"--watch": CLIOptionMeta(name="--watch", category=OptionCategory.GENERAL),
211212
"--watch-delay": CLIOptionMeta(name="--watch-delay", category=OptionCategory.GENERAL),

0 commit comments

Comments
 (0)