Skip to content

Commit b6151d5

Browse files
authored
Fix empty dict/list defaults not generating default_factory for Pydantic models (#2655)
* Add support for empty dict and list default values in PodSpec models * Refactor default values in PodSpec and related models to use empty list and literals * Refactor default values in models to use Field with default_factory for better initialization * Use STANDARD_LIST for empty list default in base_model
1 parent fd9728f commit b6151d5

11 files changed

Lines changed: 198 additions & 11 deletions

src/datamodel_code_generator/model/pydantic/base_model.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
IMPORT_EXTRA,
2424
IMPORT_FIELD,
2525
)
26-
from datamodel_code_generator.types import UnionIntFloat, chain_as_tuple
26+
from datamodel_code_generator.types import STANDARD_LIST, UnionIntFloat, chain_as_tuple
2727

2828
if TYPE_CHECKING:
2929
from collections import defaultdict
@@ -134,6 +134,8 @@ def _get_default_as_pydantic_model(self) -> str | None:
134134
and isinstance(data_type_child.reference.source, BaseModelBase)
135135
and isinstance(self.default, list)
136136
): # pragma: no cover
137+
if not self.default:
138+
return STANDARD_LIST
137139
return (
138140
f"lambda :[{data_type_child.alias or data_type_child.reference.source.class_name}."
139141
f"{self._PARSE_METHOD}(v) for v in {self.default!r}]"
@@ -197,7 +199,7 @@ def __str__(self) -> str: # noqa: PLR0912
197199

198200
if self.required:
199201
default_factory = None
200-
elif self.default and "default_factory" not in data:
202+
elif self.default is not UNDEFINED and self.default is not None and "default_factory" not in data:
201203
default_factory = self._get_default_as_pydantic_model()
202204
else:
203205
default_factory = data.pop("default_factory", None)

tests/data/expected/main/jsonschema/all_of_any_of_base_class_ref.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class MapState1(BaseModel):
1616
class MapState2(BaseModel):
1717
latitude: Latitude
1818
longitude: Longitude
19-
zoom: Optional[Zoom] = 0
19+
zoom: Optional[Zoom] = Field(default_factory=lambda: Zoom.parse_obj(0))
2020
bearing: Optional[Bearing] = None
2121
pitch: Pitch
2222
drag_rotate: Optional[DragRotate] = Field(None, alias="dragRotate")

tests/data/expected/main/jsonschema/jsonschema_root_model_ordering.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,22 @@
1010

1111

1212
class Zoo(BaseModel):
13-
animals: Optional[List[Animals]] = Field([], title='Animals')
13+
animals: Optional[List[Animals]] = Field(default_factory=list, title='Animals')
1414

1515

1616
class Dog(BaseModel):
1717
name: Literal['dog'] = Field('dog', title='woof')
18-
friends: Optional[List[Friends]] = Field([], title='Friends')
18+
friends: Optional[List[Friends]] = Field(default_factory=list, title='Friends')
1919

2020

2121
class Cat(BaseModel):
2222
name: Literal['cat'] = Field('cat', title='meow')
23-
friends: Optional[List[Friends]] = Field([], title='Friends')
23+
friends: Optional[List[Friends]] = Field(default_factory=list, title='Friends')
2424

2525

2626
class Bird(BaseModel):
2727
name: Literal['bird'] = Field('bird', title='chirp')
28-
friends: Optional[List[Friends]] = Field([], title='Friends')
28+
friends: Optional[List[Friends]] = Field(default_factory=list, title='Friends')
2929

3030

3131
class Animals(RootModel[Union[Dog, Cat, Bird]]):

tests/data/expected/main/jsonschema/jsonschema_root_model_ordering_keep_model_order.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,25 @@
1111

1212
class Bird(BaseModel):
1313
name: Literal['bird'] = Field('bird', title='chirp')
14-
friends: Optional[List[Friends]] = Field([], title='Friends')
14+
friends: Optional[List[Friends]] = Field(default_factory=list, title='Friends')
1515

1616

1717
class Cat(BaseModel):
1818
name: Literal['cat'] = Field('cat', title='meow')
19-
friends: Optional[List[Friends]] = Field([], title='Friends')
19+
friends: Optional[List[Friends]] = Field(default_factory=list, title='Friends')
2020

2121

2222
class Dog(BaseModel):
2323
name: Literal['dog'] = Field('dog', title='woof')
24-
friends: Optional[List[Friends]] = Field([], title='Friends')
24+
friends: Optional[List[Friends]] = Field(default_factory=list, title='Friends')
2525

2626

2727
class Friends(RootModel[Union[Dog, Cat, Bird]]):
2828
root: Union[Dog, Cat, Bird] = Field(..., discriminator='name', title='Animal')
2929

3030

3131
class Zoo(BaseModel):
32-
animals: Optional[List[Animals]] = Field([], title='Animals')
32+
animals: Optional[List[Animals]] = Field(default_factory=list, title='Animals')
3333

3434

3535
class Animals(RootModel[Union[Dog, Cat, Bird]]):
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# generated by datamodel-codegen:
2+
# filename: empty_dict_default.yaml
3+
# timestamp: 2019-07-26T00:00:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from typing import Optional
8+
9+
from pydantic import BaseModel, Field
10+
11+
12+
class ObjectMeta(BaseModel):
13+
name: Optional[str] = None
14+
namespace: Optional[str] = None
15+
16+
17+
class PodSpec(BaseModel):
18+
metadata: Optional[ObjectMeta] = Field(
19+
default_factory=lambda: ObjectMeta.parse_obj({})
20+
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# generated by datamodel-codegen:
2+
# filename: empty_list_default.yaml
3+
# timestamp: 2019-07-26T00:00:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from typing import List, Optional
8+
9+
from pydantic import BaseModel, Field
10+
11+
12+
class Container(BaseModel):
13+
name: Optional[str] = None
14+
15+
16+
class PodSpec(BaseModel):
17+
containers: Optional[List[Container]] = Field(default_factory=list)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# generated by datamodel-codegen:
2+
# filename: empty_dict_default.yaml
3+
# timestamp: 2019-07-26T00:00:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from typing import Optional
8+
9+
from pydantic import BaseModel, Field
10+
11+
12+
class ObjectMeta(BaseModel):
13+
name: Optional[str] = None
14+
namespace: Optional[str] = None
15+
16+
17+
class PodSpec(BaseModel):
18+
metadata: Optional[ObjectMeta] = Field(
19+
default_factory=lambda: ObjectMeta.model_validate({})
20+
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# generated by datamodel-codegen:
2+
# filename: empty_list_default.yaml
3+
# timestamp: 2019-07-26T00:00:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from typing import List, Optional
8+
9+
from pydantic import BaseModel, Field
10+
11+
12+
class Container(BaseModel):
13+
name: Optional[str] = None
14+
15+
16+
class PodSpec(BaseModel):
17+
containers: Optional[List[Container]] = Field(default_factory=list)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
openapi: 3.1.0
2+
info:
3+
title: Empty Dict Default Test
4+
version: 0.1.0
5+
servers:
6+
- url: http://example.com
7+
paths:
8+
/test:
9+
get:
10+
responses:
11+
'200':
12+
description: OK
13+
components:
14+
schemas:
15+
ObjectMeta:
16+
type: object
17+
properties:
18+
name:
19+
type: string
20+
namespace:
21+
type: string
22+
PodSpec:
23+
type: object
24+
properties:
25+
metadata:
26+
allOf:
27+
- $ref: '#/components/schemas/ObjectMeta'
28+
default: {}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
openapi: 3.1.0
2+
info:
3+
title: Empty List Default Test
4+
version: 0.1.0
5+
servers:
6+
- url: http://example.com
7+
paths:
8+
/test:
9+
get:
10+
responses:
11+
'200':
12+
description: OK
13+
components:
14+
schemas:
15+
Container:
16+
type: object
17+
properties:
18+
name:
19+
type: string
20+
PodSpec:
21+
type: object
22+
properties:
23+
containers:
24+
type: array
25+
items:
26+
$ref: '#/components/schemas/Container'
27+
default: []

0 commit comments

Comments
 (0)