Module Structure and Exports¶
When generating models to a directory structure, datamodel-code-generator can automatically create __init__.py files with __all__ exports. This page explains how to control this behavior.
Quick Overview¶
| Option | Description |
|---|---|
--all-exports-scope |
Control which modules get __all__ exports |
--all-exports-collision-strategy |
Handle name collisions in recursive exports |
--treat-dot-as-module |
Convert dots in names to nested modules |
--strict-dotted-module-names |
Restrict automatic dotted-name inference to canonical Python identifiers |
--all-exports-scope¶
Controls the scope of __all__ generation in __init__.py files.
| Value | Description |
|---|---|
none |
No __all__ generation (default) |
local |
Export only the module's own definitions |
recursive |
Export all definitions from child modules |
Example: none (default)¶
Example: local¶
Example: recursive¶
# models/__init__.py
from .user import User
from .order import Order
from .common.status import Status
from .common.types import ID, Timestamp
__all__ = ["User", "Order", "Status", "ID", "Timestamp"]
--all-exports-collision-strategy¶
When using --all-exports-scope recursive, name collisions can occur if multiple modules define the same class name. This option controls how to handle them.
| Value | Description |
|---|---|
minimal-prefix |
Add minimum module path prefix to disambiguate |
full-prefix |
Use complete module path for all exports |
The Problem¶
models/
├── user/
│ └── types.py # defines `ID`
└── order/
└── types.py # also defines `ID`
Both modules define ID, causing a collision when exporting recursively.
Solution: minimal-prefix¶
datamodel-codegen --input schemas/ --output models/ \
--all-exports-scope recursive \
--all-exports-collision-strategy minimal-prefix
# models/__init__.py
from .user.types import ID as user_ID
from .order.types import ID as order_ID
__all__ = ["user_ID", "order_ID"]
Only colliding names get prefixed.
Solution: full-prefix¶
datamodel-codegen --input schemas/ --output models/ \
--all-exports-scope recursive \
--all-exports-collision-strategy full-prefix
# models/__init__.py
from .user.types import ID as user_types_ID
from .order.types import ID as order_types_ID
__all__ = ["user_types_ID", "order_types_ID"]
All names use the full module path prefix.
A prefixed name that is another export's name¶
A prefixed name that is also the name of another export is written over that export, as in every release. The export it
replaces takes the next free name (Name1, Name2) instead of being left out, so every name keeps the model it has
always referred to. A prefixed name that is the name of a model in the __init__.py itself is an error.
--treat-dot-as-module¶
Converts dots in schema names to nested module directories.
Without --treat-dot-as-module¶
Schema with title: "api.v1.User" generates:
With --treat-dot-as-module¶
Schema with title: "api.v1.User" generates:
models/
├── __init__.py
└── api/
├── __init__.py
└── v1/
├── __init__.py
└── user.py # contains class User
This is useful for:
- Organizing large schemas by namespace
- Mirroring API versioning structure
- Keeping related models together
Each dot of a directory or file name of the input is a package, so 1.0/version.json is written below _1/0/. Python
cannot import a module by a part that is not an identifier (0), so a generated module that has to import one is
refused: generation fails and names the module and the part. Use --no-treat-dot-as-module, which writes the dots as
_, or rename the input. A module that nothing imports is written as before. A directory or file named like a
keyword (class/, class.json) cannot be imported by its name either, with or without the option: a generated module
that has to import it is refused the same way.
A package whose __init__.py defines a class named like a module or package below it (the class Warn in
pkg/__init__.py beside pkg/Warn.py) is refused too: importing pkg.Warn binds Warn in pkg over the class, so the
models that use the class fail to import or decode. Generation fails and names the class and the module; rename either
in the input. A package whose __init__.py imports from the module before it defines its classes (a model of the
module that its models use, or --all-exports-scope) is written as before.
When writing plain Python text to stdout with the standard renderer and without
an explicit --treat-dot-as-module setting, historical automatic module
inference is preserved whenever the final concatenated result remains usable. A
retry coalesces the stdout models into one flat namespace only when a
non-canonical inferred module boundary leaves conflicting generated
definitions, repeats a future import after generated code, hides a generated
model with a later import, or emits a relative import that cannot resolve in
standalone stdout. Existing root and canonical model names are reserved first.
Directory output, JSON output, schema runtime validator or generic-base
generation, custom rendering, and the Python API retain their existing default
inference. Use directory or JSON output when module paths must be preserved.
--strict-dotted-module-names¶
By default, automatic inference retains the historical behavior of treating dots
as module separators. Enable --strict-dotted-module-names to infer a module path
only when every raw segment, including the model name, is a canonical Python
identifier. Hard keywords, numeric or empty segments, punctuation, and names that
change under NFKC normalization fall back to a flat model name.
This option affects automatic inference only. Explicit
--treat-dot-as-module or --no-treat-dot-as-module takes precedence. The
negative form, --no-strict-dotted-module-names, can disable a value enabled in
pyproject.toml; it does not disable the narrow unusable-stdout repair described
above. Use explicit --treat-dot-as-module when module boundaries must be kept.
Common Patterns¶
Pattern 1: Flat directory output with child exports¶
Best for small to medium projects with a single generated package or simple directory structure.
Pattern 2: Hierarchical with recursive exports¶
Best for large projects with many schemas organized by domain.
datamodel-codegen --input schemas/ --output models/ \
--all-exports-scope recursive \
--all-exports-collision-strategy minimal-prefix \
--treat-dot-as-module
Pattern 3: OpenAPI with module structure¶
Best for OpenAPI schemas with versioned endpoints.
datamodel-codegen --input openapi.yaml --output models/ \
--treat-dot-as-module \
--all-exports-scope recursive
Troubleshooting¶
Import errors after generation¶
If you see ImportError: cannot import name 'X':
- Check if
__all__is generated correctly - Verify the module structure matches your imports
- Try
--all-exports-scope childrenfirst, thenrecursive
Name collisions¶
If you see duplicate class names:
- Use
--all-exports-collision-strategy minimal-prefix - Or use
--all-exports-collision-strategy full-prefixfor maximum clarity - Consider restructuring your schemas to avoid collisions
Circular imports¶
If you encounter circular import errors:
- Check the generated
__init__.pyfiles - Consider using
--all-exports-scope childreninstead ofrecursive - Use lazy imports in your application code