Skip to content

Commit 960f7f9

Browse files
Add support for multiple base classes in base_class_map and customBasePath (#2916)
* Add support for multiple base classes in base_class_map and customBasePath * Add comprehensive documentation for base class options * docs: update llms.txt files Generated by GitHub Actions --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 1b931d5 commit 960f7f9

28 files changed

+397
-34
lines changed

docs/cli-reference/model-customization.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1125,7 +1125,20 @@ The `--base-class` flag configures the code generation behavior.
11251125
Specify different base classes for specific models via JSON mapping.
11261126

11271127
The `--base-class-map` option allows you to assign different base classes
1128-
to specific models. Priority: base-class-map > customBasePath > base-class.
1128+
to specific models. This is useful when you want selective base class inheritance,
1129+
for example, applying custom base classes only to specific models while leaving
1130+
others with the default `BaseModel`.
1131+
1132+
Priority: `--base-class-map` > `customBasePath` (schema extension) > `--base-class`
1133+
1134+
You can specify either a single base class as a string, or multiple base classes
1135+
(mixins) as a list:
1136+
1137+
- Single: `{"Person": "custom.bases.PersonBase"}`
1138+
- Multiple: `{"User": ["mixins.AuditMixin", "mixins.TimestampMixin"]}`
1139+
1140+
When using multiple base classes, the specified classes are used directly without
1141+
adding `BaseModel`. Ensure your mixins inherit from `BaseModel` if needed.
11291142

11301143
**Related:** [`--base-class`](model-customization.md#base-class)
11311144

docs/jsonschema.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,90 @@ class Defaults(BaseModel):
124124

125125
---
126126

127+
## Custom Base Class with `customBasePath`
128+
129+
You can specify custom base classes directly in your JSON Schema using the `customBasePath` extension. This allows you to define base classes at the schema level without using CLI options.
130+
131+
### Single Base Class
132+
133+
```json
134+
{
135+
"$schema": "http://json-schema.org/draft-07/schema#",
136+
"title": "User",
137+
"type": "object",
138+
"customBasePath": "myapp.models.UserBase",
139+
"properties": {
140+
"name": {"type": "string"},
141+
"email": {"type": "string"}
142+
},
143+
"required": ["name", "email"]
144+
}
145+
```
146+
147+
**Generated Output:**
148+
149+
```python
150+
from __future__ import annotations
151+
152+
from myapp.models import UserBase
153+
154+
155+
class User(UserBase):
156+
name: str
157+
email: str
158+
```
159+
160+
### Multiple Base Classes (Mixins)
161+
162+
You can also specify multiple base classes as a list to implement mixin patterns:
163+
164+
```json
165+
{
166+
"$schema": "http://json-schema.org/draft-07/schema#",
167+
"title": "User",
168+
"type": "object",
169+
"customBasePath": ["mixins.AuditMixin", "mixins.TimestampMixin"],
170+
"properties": {
171+
"name": {"type": "string"},
172+
"email": {"type": "string"}
173+
},
174+
"required": ["name", "email"]
175+
}
176+
```
177+
178+
**Generated Output:**
179+
180+
```python
181+
from __future__ import annotations
182+
183+
from mixins import AuditMixin, TimestampMixin
184+
185+
186+
class User(AuditMixin, TimestampMixin):
187+
name: str
188+
email: str
189+
```
190+
191+
!!! note "Mixin Usage"
192+
When using multiple base classes, the specified classes are used directly without adding `BaseModel`.
193+
Ensure your mixins inherit from `pydantic.BaseModel` if you need Pydantic model behavior.
194+
195+
### Priority Resolution
196+
197+
When multiple base class configurations are present, they are resolved in this order:
198+
199+
1. **`--base-class-map`** (CLI option) - Highest priority
200+
2. **`customBasePath`** (JSON Schema extension)
201+
3. **`--base-class`** (CLI option) - Lowest priority (default for all models)
202+
203+
This allows you to set a default base class with `--base-class`, override specific models in the schema with `customBasePath`, and further override at the CLI level with `--base-class-map`.
204+
205+
---
206+
127207
## 📖 See Also
128208

129209
- 🖥️ [CLI Reference](cli-reference/index.md) - Complete CLI options reference
130210
- 🔧 [CLI Reference: Typing Customization](cli-reference/typing-customization.md) - Type annotation options
131211
- 🏷️ [CLI Reference: Field Customization](cli-reference/field-customization.md) - Field naming and constraint options
132212
- 📊 [Supported Data Types](supported-data-types.md) - JSON Schema data type support
213+
- 🏗️ [CLI Reference: Model Customization](cli-reference/model-customization.md) - Base class and model customization options

docs/llms-full.txt

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1988,7 +1988,20 @@ The `--base-class` flag configures the code generation behavior.
19881988
Specify different base classes for specific models via JSON mapping.
19891989

19901990
The `--base-class-map` option allows you to assign different base classes
1991-
to specific models. Priority: base-class-map > customBasePath > base-class.
1991+
to specific models. This is useful when you want selective base class inheritance,
1992+
for example, applying custom base classes only to specific models while leaving
1993+
others with the default `BaseModel`.
1994+
1995+
Priority: `--base-class-map` > `customBasePath` (schema extension) > `--base-class`
1996+
1997+
You can specify either a single base class as a string, or multiple base classes
1998+
(mixins) as a list:
1999+
2000+
- Single: `{"Person": "custom.bases.PersonBase"}`
2001+
- Multiple: `{"User": ["mixins.AuditMixin", "mixins.TimestampMixin"]}`
2002+
2003+
When using multiple base classes, the specified classes are used directly without
2004+
adding `BaseModel`. Ensure your mixins inherit from `BaseModel` if needed.
19922005

19932006
**Related:** [`--base-class`](model-customization.md#base-class)
19942007

@@ -24171,12 +24184,93 @@ class Defaults(BaseModel):
2417124184

2417224185
---
2417324186

24187+
## Custom Base Class with `customBasePath`
24188+
24189+
You can specify custom base classes directly in your JSON Schema using the `customBasePath` extension. This allows you to define base classes at the schema level without using CLI options.
24190+
24191+
### Single Base Class
24192+
24193+
```json
24194+
{
24195+
"$schema": "http://json-schema.org/draft-07/schema#",
24196+
"title": "User",
24197+
"type": "object",
24198+
"customBasePath": "myapp.models.UserBase",
24199+
"properties": {
24200+
"name": {"type": "string"},
24201+
"email": {"type": "string"}
24202+
},
24203+
"required": ["name", "email"]
24204+
}
24205+
```
24206+
24207+
**Generated Output:**
24208+
24209+
```python
24210+
from __future__ import annotations
24211+
24212+
from myapp.models import UserBase
24213+
24214+
24215+
class User(UserBase):
24216+
name: str
24217+
email: str
24218+
```
24219+
24220+
### Multiple Base Classes (Mixins)
24221+
24222+
You can also specify multiple base classes as a list to implement mixin patterns:
24223+
24224+
```json
24225+
{
24226+
"$schema": "http://json-schema.org/draft-07/schema#",
24227+
"title": "User",
24228+
"type": "object",
24229+
"customBasePath": ["mixins.AuditMixin", "mixins.TimestampMixin"],
24230+
"properties": {
24231+
"name": {"type": "string"},
24232+
"email": {"type": "string"}
24233+
},
24234+
"required": ["name", "email"]
24235+
}
24236+
```
24237+
24238+
**Generated Output:**
24239+
24240+
```python
24241+
from __future__ import annotations
24242+
24243+
from mixins import AuditMixin, TimestampMixin
24244+
24245+
24246+
class User(AuditMixin, TimestampMixin):
24247+
name: str
24248+
email: str
24249+
```
24250+
24251+
!!! note "Mixin Usage"
24252+
When using multiple base classes, the specified classes are used directly without adding `BaseModel`.
24253+
Ensure your mixins inherit from `pydantic.BaseModel` if you need Pydantic model behavior.
24254+
24255+
### Priority Resolution
24256+
24257+
When multiple base class configurations are present, they are resolved in this order:
24258+
24259+
1. **`--base-class-map`** (CLI option) - Highest priority
24260+
2. **`customBasePath`** (JSON Schema extension)
24261+
3. **`--base-class`** (CLI option) - Lowest priority (default for all models)
24262+
24263+
This allows you to set a default base class with `--base-class`, override specific models in the schema with `customBasePath`, and further override at the CLI level with `--base-class-map`.
24264+
24265+
---
24266+
2417424267
## 📖 See Also
2417524268

2417624269
- 🖥️ [CLI Reference](cli-reference/index.md) - Complete CLI options reference
2417724270
- 🔧 [CLI Reference: Typing Customization](cli-reference/typing-customization.md) - Type annotation options
2417824271
- 🏷️ [CLI Reference: Field Customization](cli-reference/field-customization.md) - Field naming and constraint options
2417924272
- 📊 [Supported Data Types](supported-data-types.md) - JSON Schema data type support
24273+
- 🏗️ [CLI Reference: Model Customization](cli-reference/model-customization.md) - Base class and model customization options
2418024274

2418124275
---
2418224276

src/datamodel_code_generator/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ def validate_class_name_affix_scope(cls, v: str | ClassNameAffixScope | None) ->
499499
target_python_version: PythonVersion = PythonVersionMin
500500
target_pydantic_version: Optional[TargetPydanticVersion] = None # noqa: UP045
501501
base_class: str = ""
502-
base_class_map: Optional[dict[str, str]] = None # noqa: UP045
502+
base_class_map: Optional[dict[str, str | list[str]]] = None # noqa: UP045
503503
additional_imports: Optional[list[str]] = None # noqa: UP045
504504
class_decorators: Optional[list[str]] = None # noqa: UP045
505505
custom_template_dir: Optional[Path] = None # noqa: UP045

src/datamodel_code_generator/_types/generate_config_dict.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class GenerateConfigDict(TypedDict):
4646
target_python_version: NotRequired[PythonVersion]
4747
target_pydantic_version: NotRequired[TargetPydanticVersion | None]
4848
base_class: NotRequired[str]
49-
base_class_map: NotRequired[dict[str, str] | None]
49+
base_class_map: NotRequired[dict[str, str | list[str]] | None]
5050
additional_imports: NotRequired[list[str] | None]
5151
class_decorators: NotRequired[list[str] | None]
5252
custom_template_dir: NotRequired[Path | None]

src/datamodel_code_generator/_types/parser_config_dicts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class ParserConfigDict(TypedDict):
4646
data_type_manager_type: NotRequired[type[DataTypeManager]]
4747
data_model_field_type: NotRequired[type[DataModelFieldBase]]
4848
base_class: NotRequired[str | None]
49-
base_class_map: NotRequired[dict[str, str] | None]
49+
base_class_map: NotRequired[dict[str, str | list[str]] | None]
5050
additional_imports: NotRequired[list[str] | None]
5151
class_decorators: NotRequired[list[str] | None]
5252
custom_template_dir: NotRequired[Path | None]

src/datamodel_code_generator/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class Config:
8383
target_python_version: PythonVersion = PythonVersionMin
8484
target_pydantic_version: TargetPydanticVersion | None = None
8585
base_class: str = ""
86-
base_class_map: dict[str, str] | None = None
86+
base_class_map: dict[str, str | list[str]] | None = None
8787
additional_imports: list[str] | None = None
8888
class_decorators: list[str] | None = None
8989
custom_template_dir: Path | None = None
@@ -225,7 +225,7 @@ class Config:
225225
data_type_manager_type: type[DataTypeManager] = pydantic_model.DataTypeManager
226226
data_model_field_type: type[DataModelFieldBase] = pydantic_model.DataModelField
227227
base_class: str | None = None
228-
base_class_map: dict[str, str] | None = None
228+
base_class_map: dict[str, str | list[str]] | None = None
229229
additional_imports: list[str] | None = None
230230
class_decorators: list[str] | None = None
231231
custom_template_dir: Path | None = None

src/datamodel_code_generator/model/base.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ def __init__( # noqa: PLR0913
643643
fields: list[DataModelFieldBase],
644644
decorators: list[str] | None = None,
645645
base_classes: list[Reference] | None = None,
646-
custom_base_class: str | None = None,
646+
custom_base_class: str | list[str] | None = None,
647647
custom_template_dir: Path | None = None,
648648
extra_template_data: defaultdict[str, dict[str, Any]] | None = None,
649649
methods: list[str] | None = None,
@@ -781,14 +781,24 @@ def replace_children_in_models(self, models: list[DataModel], new_ref: Reference
781781
child.replace_reference(new_ref)
782782

783783
def set_base_class(self) -> None:
784-
"""Set up the base class for this model."""
785-
base_class = self.custom_base_class or self.BASE_CLASS
786-
if not base_class:
784+
"""Set up the base class(es) for this model."""
785+
if self.custom_base_class is None:
786+
base_class_list = [self.BASE_CLASS] if self.BASE_CLASS else []
787+
elif isinstance(self.custom_base_class, list):
788+
base_class_list = self.custom_base_class
789+
else:
790+
base_class_list = [self.custom_base_class]
791+
792+
if not base_class_list:
787793
self.base_classes = []
788794
return
789-
base_class_import = Import.from_full_path(base_class)
790-
self._additional_imports.append(base_class_import)
791-
self.base_classes = [BaseClassDataType.from_import(base_class_import)]
795+
796+
result = []
797+
for base_class in base_class_list:
798+
base_class_import = Import.from_full_path(base_class)
799+
self._additional_imports.append(base_class_import)
800+
result.append(BaseClassDataType.from_import(base_class_import))
801+
self.base_classes = result
792802

793803
@cached_property
794804
def template_file_path(self) -> Path:

src/datamodel_code_generator/model/dataclass.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def __init__( # noqa: PLR0913
5858
fields: list[DataModelFieldBase],
5959
decorators: list[str] | None = None,
6060
base_classes: list[Reference] | None = None,
61-
custom_base_class: str | None = None,
61+
custom_base_class: str | list[str] | None = None,
6262
custom_template_dir: Path | None = None,
6363
extra_template_data: defaultdict[str, dict[str, Any]] | None = None,
6464
methods: list[str] | None = None,

src/datamodel_code_generator/model/enum.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def __init__( # noqa: PLR0913
5151
fields: list[DataModelFieldBase],
5252
decorators: list[str] | None = None,
5353
base_classes: list[Reference] | None = None,
54-
custom_base_class: str | None = None,
54+
custom_base_class: str | list[str] | None = None,
5555
custom_template_dir: Path | None = None,
5656
extra_template_data: defaultdict[str, dict[str, Any]] | None = None,
5757
methods: list[str] | None = None,

0 commit comments

Comments
 (0)