Skip to content

Commit 9445b94

Browse files
authored
Add --all-exports-scope and --all-exports-collision-strategy options for recursive exports (#2588)
* Add support for all exports scope and collision strategy in __init__.py generation * Add support for recursive all exports scope in __init__.py generation * Add support for recursive all exports scope with collision error handling * Add support for all exports scope with no child exports handling
1 parent 40adf78 commit 9445b94

65 files changed

Lines changed: 1270 additions & 50 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,15 @@ Field customization:
443443
docstring
444444

445445
Model customization:
446+
--all-exports-collision-strategy {error,minimal-prefix,full-prefix}
447+
Strategy for name collisions when using --all-exports-
448+
scope=recursive. ''error'': raise an error (default). ''minimal-
449+
prefix'': add module prefix only to colliding names. ''full-prefix'':
450+
add full module path prefix to colliding names.
451+
--all-exports-scope {children,recursive}
452+
Generate __all__ in __init__.py with re-exports. ''children'': export
453+
from direct child modules only. ''recursive'': export from all
454+
descendant modules.
446455
--allow-extra-fields Deprecated: Allow passing extra fields. This flag is deprecated. Use
447456
`--extra-fields=allow` instead.
448457
--allow-population-by-field-name
@@ -490,8 +499,6 @@ Model customization:
490499
target python version
491500
--treat-dot-as-module
492501
treat dotted module names as modules
493-
--use-all-exports Generate __all__ = [...] in __init__.py to export all defined models
494-
and types
495502
--use-exact-imports import exact types instead of modules, for example: "from .foo
496503
import Bar" instead of "from . import foo" with "foo.Bar"
497504
--use-pendulum use pendulum instead of datetime

docs/index.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,15 @@ Field customization:
435435
docstring
436436

437437
Model customization:
438+
--all-exports-collision-strategy {error,minimal-prefix,full-prefix}
439+
Strategy for name collisions when using --all-exports-
440+
scope=recursive. ''error'': raise an error (default). ''minimal-
441+
prefix'': add module prefix only to colliding names. ''full-prefix'':
442+
add full module path prefix to colliding names.
443+
--all-exports-scope {children,recursive}
444+
Generate __all__ in __init__.py with re-exports. ''children'': export
445+
from direct child modules only. ''recursive'': export from all
446+
descendant modules.
438447
--allow-extra-fields Deprecated: Allow passing extra fields. This flag is deprecated. Use
439448
`--extra-fields=allow` instead.
440449
--allow-population-by-field-name
@@ -482,8 +491,6 @@ Model customization:
482491
target python version
483492
--treat-dot-as-module
484493
treat dotted module names as modules
485-
--use-all-exports Generate __all__ = [...] in __init__.py to export all defined models
486-
and types
487494
--use-exact-imports import exact types instead of modules, for example: "from .foo
488495
import Bar" instead of "from . import foo" with "foo.Bar"
489496
--use-pendulum use pendulum instead of datetime

src/datamodel_code_generator/__init__.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,30 @@ class OpenAPIScope(Enum):
246246
Webhooks = "webhooks"
247247

248248

249+
class AllExportsScope(Enum):
250+
"""Scope for __all__ exports in __init__.py.
251+
252+
children: Export models from direct child modules only.
253+
recursive: Export models from all descendant modules recursively.
254+
"""
255+
256+
Children = "children"
257+
Recursive = "recursive"
258+
259+
260+
class AllExportsCollisionStrategy(Enum):
261+
"""Strategy for handling name collisions in recursive exports.
262+
263+
error: Raise an error when name collision is detected.
264+
minimal_prefix: Add module prefix only to colliding names.
265+
full_prefix: Add full module path prefix to all colliding names.
266+
"""
267+
268+
Error = "error"
269+
MinimalPrefix = "minimal-prefix"
270+
FullPrefix = "full-prefix"
271+
272+
249273
class GraphQLScope(Enum):
250274
"""Scopes for GraphQL model generation."""
251275

@@ -375,7 +399,8 @@ def generate( # noqa: PLR0912, PLR0913, PLR0914, PLR0915
375399
dataclass_arguments: DataclassArguments | None = None,
376400
disable_future_imports: bool = False,
377401
type_mappings: list[str] | None = None,
378-
use_all_exports: bool = False,
402+
all_exports_scope: AllExportsScope | None = None,
403+
all_exports_collision_strategy: AllExportsCollisionStrategy | None = None,
379404
) -> None:
380405
"""Generate Python data models from schema definitions or structured data.
381406
@@ -613,7 +638,11 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]:
613638
)
614639

615640
with chdir(output):
616-
results = parser.parse(disable_future_imports=disable_future_imports, use_all_exports=use_all_exports)
641+
results = parser.parse(
642+
disable_future_imports=disable_future_imports,
643+
all_exports_scope=all_exports_scope,
644+
all_exports_collision_strategy=all_exports_collision_strategy,
645+
)
617646
if not input_filename: # pragma: no cover
618647
if isinstance(input_, str):
619648
input_filename = "<stdin>"
@@ -704,6 +733,8 @@ def infer_input_type(text: str) -> InputFileType:
704733
__all__ = [
705734
"MAX_VERSION",
706735
"MIN_VERSION",
736+
"AllExportsCollisionStrategy",
737+
"AllExportsScope",
707738
"DatetimeClassType",
708739
"DefaultPutDict",
709740
"Error",

src/datamodel_code_generator/__main__.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222

2323
from datamodel_code_generator import (
2424
DEFAULT_SHARED_MODULE_NAME,
25+
AllExportsCollisionStrategy,
26+
AllExportsScope,
2527
DataclassArguments,
2628
DataModelType,
2729
Error,
@@ -241,6 +243,10 @@ def validate_custom_formatters(cls, values: dict[str, Any]) -> dict[str, Any]:
241243
f"`--keyword-only` requires `--target-python-version` {PythonVersion.PY_310.value} or higher."
242244
)
243245

246+
__validate_all_exports_collision_strategy_err: ClassVar[str] = (
247+
"`--all-exports-collision-strategy` can only be used with `--all-exports-scope=recursive`."
248+
)
249+
244250
if PYDANTIC_V2:
245251

246252
@model_validator() # pyright: ignore[reportArgumentType]
@@ -289,6 +295,13 @@ def validate_root(self: Self) -> Self: # pyright: ignore[reportRedeclaration]
289295
self.field_constraints = True
290296
return self
291297

298+
@model_validator() # pyright: ignore[reportArgumentType]
299+
def validate_all_exports_collision_strategy(self: Self) -> Self: # pyright: ignore[reportRedeclaration]
300+
"""Validate all_exports_collision_strategy requires recursive scope."""
301+
if self.all_exports_collision_strategy is not None and self.all_exports_scope != AllExportsScope.Recursive:
302+
raise Error(self.__validate_all_exports_collision_strategy_err)
303+
return self
304+
292305
else:
293306

294307
@model_validator() # pyright: ignore[reportArgumentType]
@@ -337,6 +350,16 @@ def validate_root(cls, values: dict[str, Any]) -> dict[str, Any]: # noqa: N805
337350
values["field_constraints"] = True
338351
return values
339352

353+
@model_validator() # pyright: ignore[reportArgumentType]
354+
def validate_all_exports_collision_strategy(cls, values: dict[str, Any]) -> dict[str, Any]: # noqa: N805
355+
"""Validate all_exports_collision_strategy requires recursive scope."""
356+
if (
357+
values.get("all_exports_collision_strategy") is not None
358+
and values.get("all_exports_scope") != AllExportsScope.Recursive
359+
):
360+
raise Error(cls.__validate_all_exports_collision_strategy_err)
361+
return values
362+
340363
input: Optional[Union[Path, str]] = None # noqa: UP007, UP045
341364
input_file_type: InputFileType = InputFileType.Auto
342365
output_model_type: DataModelType = DataModelType.PydanticBaseModel
@@ -425,7 +448,8 @@ def validate_root(cls, values: dict[str, Any]) -> dict[str, Any]: # noqa: N805
425448
parent_scoped_naming: bool = False
426449
disable_future_imports: bool = False
427450
type_mappings: Optional[list[str]] = None # noqa: UP045
428-
use_all_exports: bool = False
451+
all_exports_scope: Optional[AllExportsScope] = None # noqa: UP045
452+
all_exports_collision_strategy: Optional[AllExportsCollisionStrategy] = None # noqa: UP045
429453

430454
def merge_args(self, args: Namespace) -> None:
431455
"""Merge command-line arguments into config."""
@@ -827,7 +851,8 @@ def main(args: Sequence[str] | None = None) -> Exit: # noqa: PLR0911, PLR0912,
827851
dataclass_arguments=config.dataclass_arguments,
828852
disable_future_imports=config.disable_future_imports,
829853
type_mappings=config.type_mappings,
830-
use_all_exports=config.use_all_exports,
854+
all_exports_scope=config.all_exports_scope,
855+
all_exports_collision_strategy=config.all_exports_collision_strategy,
831856
)
832857
except InvalidClassNameError as e:
833858
print(f"{e} You have to set `--class-name` option", file=sys.stderr) # noqa: T201

src/datamodel_code_generator/arguments.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
from datamodel_code_generator import (
1818
DEFAULT_SHARED_MODULE_NAME,
19+
AllExportsCollisionStrategy,
20+
AllExportsScope,
1921
DataclassArguments,
2022
DataModelType,
2123
InputFileType,
@@ -294,9 +296,20 @@ def start_section(self, heading: str | None) -> None:
294296
default=None,
295297
)
296298
model_options.add_argument(
297-
"--use-all-exports",
298-
help="Generate __all__ = [...] in __init__.py to export all defined models and types",
299-
action="store_true",
299+
"--all-exports-scope",
300+
help="Generate __all__ in __init__.py with re-exports. "
301+
"'children': export from direct child modules only. "
302+
"'recursive': export from all descendant modules.",
303+
choices=[s.value for s in AllExportsScope],
304+
default=None,
305+
)
306+
model_options.add_argument(
307+
"--all-exports-collision-strategy",
308+
help="Strategy for name collisions when using --all-exports-scope=recursive. "
309+
"'error': raise an error (default). "
310+
"'minimal-prefix': add module prefix only to colliding names. "
311+
"'full-prefix': add full module path prefix to colliding names.",
312+
choices=[s.value for s in AllExportsCollisionStrategy],
300313
default=None,
301314
)
302315

0 commit comments

Comments
 (0)