Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions src/datamodel_code_generator/model/pydantic/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,22 @@ def _get_strict_field_constraint_value(self, constraint: str, value: Any) -> Any
return value
return int(value)

def _get_default_as_pydantic_model(self) -> str | None:
def _get_default_as_pydantic_model(self) -> str | None: # noqa: PLR0911, PLR0912
if isinstance(self.default, WrappedDefault):
return f"lambda :{self.default!r}"
if self.data_type.is_list and len(self.data_type.data_types) == 1:
data_type_child = self.data_type.data_types[0]
if (
data_type_child.reference
and isinstance(data_type_child.reference.source, BaseModelBase)
and isinstance(self.default, list)
):
if not self.default:
return STANDARD_LIST
return ( # pragma: no cover
f"lambda :[{data_type_child.alias or data_type_child.reference.source.class_name}."
f"{self._PARSE_METHOD}(v) for v in {self.default!r}]"
)
for data_type in self.data_type.data_types or (self.data_type,):
# TODO: Check nested data_types
if data_type.is_dict:
Expand Down Expand Up @@ -220,7 +233,7 @@ def __str__(self) -> str: # noqa: PLR0912
elif isinstance(discriminator, dict): # pragma: no cover
data["discriminator"] = discriminator["propertyName"]

if self.required:
if self.required and not self.has_default:
default_factory = None
elif self.default is not UNDEFINED and self.default is not None and "default_factory" not in data:
default_factory = self._get_default_as_pydantic_model()
Expand Down Expand Up @@ -249,7 +262,7 @@ def __str__(self) -> str: # noqa: PLR0912

if self.use_annotated:
field_arguments = self._process_annotated_field_arguments(field_arguments)
elif self.required:
elif self.required and not default_factory:
field_arguments = ["...", *field_arguments]
elif not default_factory:
default_repr = repr_set_sorted(self.default) if isinstance(self.default, set) else repr(self.default)
Expand Down
34 changes: 34 additions & 0 deletions tests/data/expected/main/graphql/empty_list_default.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# generated by datamodel-codegen:
# filename: empty_list_default.graphql
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, Field

type Boolean = bool
"""
The `Boolean` scalar type represents `true` or `false`.
"""


type String = str
"""
The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.
"""


class Container(BaseModel):
name: String
typename__: Literal['Container'] | None = Field('Container', alias='__typename')


class PodSpec(BaseModel):
container_list: list[Container] = Field(default_factory=list)
container_list_or_none: list[Container | None] = Field(default_factory=list)
container_or_none_list_or_none: list[Container | None] | None = Field(
default_factory=list
)
typename__: Literal['PodSpec'] | None = Field('PodSpec', alias='__typename')
34 changes: 34 additions & 0 deletions tests/data/expected/main/graphql/pydantic_v2_empty_list_default.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# generated by datamodel-codegen:
# filename: empty_list_default.graphql
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, Field

type Boolean = bool
"""
The `Boolean` scalar type represents `true` or `false`.
"""


type String = str
"""
The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.
"""
Comment thread
koxudaxi marked this conversation as resolved.


class Container(BaseModel):
name: String
typename__: Literal['Container'] | None = Field('Container', alias='__typename')


class PodSpec(BaseModel):
container_list: list[Container] = Field(default_factory=list)
container_list_or_none: list[Container | None] = Field(default_factory=list)
container_or_none_list_or_none: list[Container | None] | None = Field(
default_factory=list
)
typename__: Literal['PodSpec'] | None = Field('PodSpec', alias='__typename')
6 changes: 2 additions & 4 deletions tests/data/expected/main/openapi/empty_list_default.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@

from __future__ import annotations

from typing import List, Optional

from pydantic import BaseModel, Field


class Container(BaseModel):
name: Optional[str] = None
name: str | None = None


class PodSpec(BaseModel):
containers: Optional[List[Container]] = Field(default_factory=list)
containers: list[Container] | None = Field(default_factory=list)
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@

from __future__ import annotations

from typing import List, Optional

from pydantic import BaseModel, Field


class Container(BaseModel):
name: Optional[str] = None
name: str | None = None


class PodSpec(BaseModel):
containers: Optional[List[Container]] = Field(default_factory=list)
containers: list[Container] | None = Field(default_factory=list)
10 changes: 10 additions & 0 deletions tests/data/graphql/empty_list_default.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

input Container {
name: String!
}

input PodSpec {
container_list: [Container!]! = []
container_list_or_none: [Container]! = []
container_or_none_list_or_none: [Container] = []
}
42 changes: 41 additions & 1 deletion tests/main/graphql/test_main_graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
import black
import pytest

from tests.main.conftest import DEFAULT_VALUES_DATA_PATH, GRAPHQL_DATA_PATH, LEGACY_BLACK_SKIP, run_main_and_assert
from tests.main.conftest import (
DEFAULT_VALUES_DATA_PATH,
EXPECTED_GRAPHQL_PATH,
GRAPHQL_DATA_PATH,
LEGACY_BLACK_SKIP,
run_main_and_assert,
)
from tests.main.graphql.conftest import assert_file_content

if TYPE_CHECKING:
Expand Down Expand Up @@ -104,6 +110,40 @@ def test_main_use_default_kwarg(output_file: Path) -> None:
)


@pytest.mark.parametrize(
("output_model", "expected_output"),
[
(
"pydantic.BaseModel",
"empty_list_default.py",
),
(
"pydantic_v2.BaseModel",
"pydantic_v2_empty_list_default.py",
),
],
)
@pytest.mark.skipif(
black.__version__.split(".")[0] in {"19", "22"},
reason="Installed black doesn't support Python 3.12 target version",
)
def test_main_graphql_empty_list_default(output_model: str, expected_output: str, output_file: Path) -> None:
"""Test GraphQL generation with empty list default values."""
run_main_and_assert(
input_path=GRAPHQL_DATA_PATH / "empty_list_default.graphql",
Comment thread
koxudaxi marked this conversation as resolved.
output_path=output_file,
assert_func=assert_file_content,
expected_file=EXPECTED_GRAPHQL_PATH / expected_output,
input_file_type="graphql",
extra_args=[
"--output-model-type",
output_model,
"--target-python-version",
"3.12",
],
)


@pytest.mark.skipif(
black.__version__.split(".")[0] == "19",
reason="Installed black doesn't support the old style",
Expand Down
1 change: 1 addition & 0 deletions tests/main/openapi/test_main_openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -2808,6 +2808,7 @@ def test_main_openapi_empty_list_default(output_model: str, expected_output: str
input_path=OPEN_API_DATA_PATH / "empty_list_default.yaml",
output_path=output_file,
expected_file=EXPECTED_OPENAPI_PATH / expected_output,
assert_func=assert_file_content,
input_file_type="openapi",
extra_args=[
"--output-model-type",
Expand Down
Loading