Skip to content
Merged
1 change: 1 addition & 0 deletions src/datamodel_code_generator/model/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ class DataModelFieldBase(_BaseModel):
use_frozen_field: bool = False
use_serialization_alias: bool = False
use_default_factory_for_optional_nested_models: bool = False
use_default_with_required: bool = False

if not TYPE_CHECKING: # pragma: no branch

Expand Down
5 changes: 3 additions & 2 deletions src/datamodel_code_generator/model/dataclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
def has_field_assignment(field: DataModelFieldBase) -> bool:
"""Check if a dataclass field has a default value or field() assignment."""
return bool(field.field) or not (
field.required or (field.represented_default == "None" and field.strip_default_none)
(field.required and not field.use_default_with_required)
or (field.represented_default == "None" and field.strip_default_none)
)


Expand Down Expand Up @@ -172,7 +173,7 @@ def __str__(self) -> str:
if self.default != UNDEFINED and self.default is not None:
data["default"] = self.default

if self.required:
if self.required and not self.use_default_with_required:
data = {
k: v
for k, v in data.items()
Expand Down
8 changes: 6 additions & 2 deletions src/datamodel_code_generator/model/msgspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ def __str__(self) -> str:


def _has_field_assignment(field: DataModelFieldBase) -> bool:
return not (field.required or (field.represented_default == "None" and field.strip_default_none))
return (
bool(field.field)
or field.use_default_with_required
or not (field.required or (field.represented_default == "None" and field.strip_default_none))
)


DataModelFieldBaseT = TypeVar("DataModelFieldBaseT", bound=DataModelFieldBase)
Expand Down Expand Up @@ -311,7 +315,7 @@ def __str__(self) -> str: # noqa: PLR0912
elif self._not_required and "default_factory" not in data:
data["default"] = None if self.nullable else UNSET

if self.required:
if self.required and not self.use_default_with_required:
data = {
k: v
for k, v in data.items()
Expand Down
9 changes: 7 additions & 2 deletions src/datamodel_code_generator/model/pydantic_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ def __str__(self) -> str: # noqa: PLR0912
field_arguments = sorted(f"{k}={v!r}" for k, v in data.items() if v is not None)

if not field_arguments and not default_factory:
if self.nullable and self.required:
if self.nullable and self.required and not self.use_default_with_required:
return "Field(...)" # Field() is for mypy
return ""

Expand All @@ -211,7 +211,12 @@ def __str__(self) -> str: # noqa: PLR0912

if self.use_annotated:
field_arguments = self._process_annotated_field_arguments(field_arguments)
elif self.required and not default_factory and not self.extras.get("validate_default"):
elif (
self.required
and not self.use_default_with_required
and not default_factory
and not self.extras.get("validate_default")
):
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
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class {{ class_name }}:
{{ field.name }}: {{ field.type_hint }} = {{ field.field }}
{%- else %}
{{ field.name }}: {{ field.type_hint }}
{%- if not (field.required or (field.represented_default == 'None' and field.strip_default_none))
{%- if not ((field.required and not field.use_default_with_required) or (field.represented_default == 'None' and field.strip_default_none))
%} = {{ field.represented_default }}
{%- endif -%}
{%- endif %}
Expand Down
2 changes: 1 addition & 1 deletion src/datamodel_code_generator/model/template/msgspec.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class {{ class_name }}:
{%- else %}
{{ field.name }}: {{ field.type_hint }}
{%- endif %}
{%- if not field.field and (not field.required or field.data_type.is_optional or field.nullable)
{%- if not field.field and (not field.required or field.use_default_with_required or field.data_type.is_optional or field.nullable)
%} = {{ field.represented_default }}
{%- endif -%}
{%- endif %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class {{ class_name }}({{ base_class }}):{% if comment is defined %} # {{ comme
{%- else %}
{{ field.name }}: {{ field.type_hint }}
{%- endif %}
{%- if not field.has_default_factory_in_field and not field.required and (field.represented_default != 'None' or not field.strip_default_none or field.data_type.is_optional)
{%- if not field.has_default_factory_in_field and (not field.required or field.use_default_with_required) and (field.represented_default != 'None' or not field.strip_default_none or field.data_type.is_optional)
%} = {{ field.represented_default }}
{%- endif -%}
{%- endif %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class {{ class_name }}:
{%- else %}
{{ field.name }}: {{ field.type_hint }}
{%- endif %}
{%- if not field.has_default_factory_in_field and not field.required and (field.represented_default != 'None' or not field.strip_default_none or field.data_type.is_optional)
{%- if not field.has_default_factory_in_field and (not field.required or field.use_default_with_required) and (field.represented_default != 'None' or not field.strip_default_none or field.data_type.is_optional)
%} = {{ field.represented_default }}
{%- endif -%}
{%- endif %}
Expand Down
4 changes: 4 additions & 0 deletions src/datamodel_code_generator/parser/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2180,6 +2180,8 @@ def __set_validate_default_on_fields( # noqa: PLR6301
if isinstance(model, Enum):
continue
for model_field in model.fields:
if model_field.required and not model_field.use_default_with_required:
continue
if model_field.default is None or model_field.default is UNDEFINED:
continue
if isinstance(model_field.default, Member):
Expand Down Expand Up @@ -2227,6 +2229,8 @@ def __override_required_field(
copied_original_field.data_type = data_type
copied_original_field.parent = model
copied_original_field.required = True
if self.apply_default_values_for_required_fields and copied_original_field.has_default:
copied_original_field.use_default_with_required = True
model.fields.insert(index, copied_original_field)
model.fields.remove(model_field)

Expand Down
4 changes: 2 additions & 2 deletions src/datamodel_code_generator/parser/graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,7 @@ def parse_field(
class_name=class_name,
)

if self.apply_default_values_for_required_fields and effective_has_default:
required = False
use_default_with_required = required and self.apply_default_values_for_required_fields and effective_has_default

extras = {} if self.default_field_extras is None else self.default_field_extras.copy()

Expand Down Expand Up @@ -405,6 +404,7 @@ def parse_field(
original_name=field_name,
has_default=effective_has_default,
use_serialization_alias=self.use_serialization_alias,
use_default_with_required=use_default_with_required,
)

def parse_object_like(
Expand Down
22 changes: 13 additions & 9 deletions src/datamodel_code_generator/parser/jsonschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,7 @@ def get_object_field( # noqa: PLR0913
original_field_name: str | None,
effective_default: Any = None,
effective_has_default: bool | None = None,
use_default_with_required: bool = False,
) -> DataModelFieldBase:
"""Create a data model field from a JSON Schema object field."""
default_value = effective_default if effective_has_default is not None else field.default
Expand Down Expand Up @@ -1252,6 +1253,7 @@ def get_object_field( # noqa: PLR0913
use_frozen_field=self.use_frozen_field,
use_serialization_alias=self.use_serialization_alias,
use_default_factory_for_optional_nested_models=self.use_default_factory_for_optional_nested_models,
use_default_with_required=use_default_with_required,
)

def get_data_type(self, obj: JsonSchemaObject) -> DataType:
Expand Down Expand Up @@ -2478,22 +2480,22 @@ def _parse_object_common_part( # noqa: PLR0912, PLR0913, PLR0915
return self.data_type(reference=base_classes[0])
if required:
for field in fields:
if self.force_optional_for_required_fields or ( # pragma: no cover
self.apply_default_values_for_required_fields and field.has_default
):
if self.force_optional_for_required_fields: # pragma: no cover
continue # pragma: no cover
if (field.original_name or field.name) in required:
field.required = True
if self.apply_default_values_for_required_fields and field.has_default:
field.use_default_with_required = True
if obj.required:
field_name_to_field = {f.original_name or f.name: f for f in fields}
for required_ in obj.required:
if required_ in field_name_to_field:
field = field_name_to_field[required_]
if self.force_optional_for_required_fields or (
self.apply_default_values_for_required_fields and field.has_default
):
if self.force_optional_for_required_fields:
continue
field.required = True
if self.apply_default_values_for_required_fields and field.has_default:
field.use_default_with_required = True
else:
fields.append(
self.data_model_field_type(required=True, original_name=required_, data_type=DataType())
Expand Down Expand Up @@ -2829,12 +2831,13 @@ def parse_object_fields(
class_name=class_name,
)

if self.force_optional_for_required_fields or (
self.apply_default_values_for_required_fields and effective_has_default
):
if self.force_optional_for_required_fields:
required: bool = False
else:
required = original_field_name in requires
use_default_with_required = (
required and self.apply_default_values_for_required_fields and effective_has_default
)
fields.append(
self.get_object_field(
field_name=field_name,
Expand All @@ -2845,6 +2848,7 @@ def parse_object_fields(
original_field_name=original_field_name,
effective_default=effective_default,
effective_has_default=effective_has_default,
use_default_with_required=use_default_with_required,
)
)
return fields
Expand Down
14 changes: 9 additions & 5 deletions src/datamodel_code_generator/parser/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ def _get_model_name(cls, path_name: str, method: str, suffix: str) -> str:
camel_path_name = snake_to_upper_camel(normalized)
return f"{camel_path_name}{method.capitalize()}{suffix}"

def parse_all_parameters( # noqa: PLR0912, PLR0914, PLR0915
def parse_all_parameters( # noqa: PLR0912, PLR0914
self,
name: str,
parameters: list[ReferenceObject | ParameterObject],
Expand Down Expand Up @@ -551,8 +551,9 @@ def parse_all_parameters( # noqa: PLR0912, PLR0914, PLR0915
class_name=reference.name,
)
effective_required = parameter.required
if self.apply_default_values_for_required_fields and effective_has_default:
effective_required = False
use_default_with_required = (
effective_required and self.apply_default_values_for_required_fields and effective_has_default
)
fields.append(
self.get_object_field(
field_name=field_name,
Expand All @@ -563,6 +564,7 @@ def parse_all_parameters( # noqa: PLR0912, PLR0914, PLR0915
alias=alias,
effective_default=effective_default,
effective_has_default=effective_has_default,
use_default_with_required=use_default_with_required,
)
)
else:
Expand Down Expand Up @@ -600,8 +602,9 @@ def parse_all_parameters( # noqa: PLR0912, PLR0914, PLR0915
class_name=reference.name,
)
effective_required = parameter.required
if self.apply_default_values_for_required_fields and effective_has_default:
effective_required = False
use_default_with_required = (
effective_required and self.apply_default_values_for_required_fields and effective_has_default
)
# Handle multiple aliases (Pydantic v2 AliasChoices)
single_alias: str | None = None
validation_aliases: list[str] | None = None
Expand Down Expand Up @@ -639,6 +642,7 @@ def parse_all_parameters( # noqa: PLR0912, PLR0914, PLR0915
has_default=effective_has_default,
type_has_null=object_schema.type_has_null if object_schema else None,
use_serialization_alias=self.use_serialization_alias,
use_default_with_required=use_default_with_required,
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@

class User(BaseModel):
id: ID
name: String | None = 'default_user'
status: String | None = 'active'
name: String = 'default_user'
status: String = 'active'
typename__: Literal['User'] | None = Field('User', alias='__typename')
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ class Container(BaseModel):


class PodSpec(BaseModel):
container_list: list[Container] = Field([], validate_default=True)
container_list_or_none: list[Container | None] = Field([], validate_default=True)
container_list: list[Container]
container_list_or_none: list[Container | None]
container_or_none_list_or_none: list[Container | None] | None = Field(
[], validate_default=True
)
Expand Down
4 changes: 2 additions & 2 deletions tests/data/expected/main/jsonschema/all_of_use_default.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@


class Item(BaseModel):
test: str | None = 'test123'
testarray: list[str] | None = Field(['test123'], min_length=1, title='test array')
test: str = 'test123'
testarray: list[str] = Field(['test123'], min_length=1, title='test array')
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# generated by datamodel-codegen:
# filename: allof_inherited_required_use_default.json
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from pydantic import BaseModel


class Base(BaseModel):
id: int | None = 1


class Container(Base):
id: int = 1
16 changes: 16 additions & 0 deletions tests/data/expected/main/jsonschema/allof_required_use_default.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# generated by datamodel-codegen:
# filename: allof_required_use_default.json
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from pydantic import BaseModel


class Base(BaseModel):
id: int


class Container(Base):
name: str = 'unnamed'
tag: str
16 changes: 16 additions & 0 deletions tests/data/expected/main/jsonschema/force_optional_required.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# generated by datamodel-codegen:
# filename: force_optional_required.json
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from pydantic import BaseModel


class Base(BaseModel):
id: int | None = None


class Container(Base):
name: str | None = None
value: int | None = None
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# generated by datamodel-codegen:
# filename: allof_required_use_default.json
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from msgspec import Struct


class Base(Struct):
id: int


class Container(Base):
tag: str
name: str = 'unnamed'
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# generated by datamodel-codegen:
# filename: use_default_required_null_default.json
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from dataclasses import dataclass


@dataclass
class Container:
b: int
a: str | None = None
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# generated by datamodel-codegen:
# filename: use_default_strict_nullable_required.json
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from pydantic import BaseModel


class Container(BaseModel):
x: str | None = 'hello'
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class Filter(BaseModel):


class UsersGetParametersQuery(BaseModel):
status: str | None = 'active'
filter: Filter | None = Field({}, validate_default=True)
status: str = 'active'
filter: Filter = Field({}, validate_default=True)


class User(BaseModel):
Expand Down
Loading
Loading