Skip to content

Commit 3e7b16b

Browse files
authored
Fix nullable field access in custom templates with strict_nullable (#2715)
1 parent 90659d9 commit 3e7b16b

File tree

3 files changed

+47
-6
lines changed

3 files changed

+47
-6
lines changed

src/datamodel_code_generator/parser/jsonschema.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ def validate_null_type(cls, value: Any) -> Any: # noqa: N805
335335
properties: Optional[dict[str, Union[JsonSchemaObject, bool]]] = None # noqa: UP007, UP045
336336
required: list[str] = [] # noqa: RUF012
337337
ref: Optional[str] = Field(default=None, alias="$ref") # noqa: UP045
338-
nullable: Optional[bool] = False # noqa: UP045
338+
nullable: Optional[bool] = None # noqa: UP045
339339
x_enum_varnames: list[str] = Field(default_factory=list, alias="x-enum-varnames")
340340
x_enum_names: list[str] = Field(default_factory=list, alias="x-enumNames")
341341
description: Optional[str] = None # noqa: UP045
@@ -1034,7 +1034,9 @@ def get_object_field( # noqa: PLR0913
10341034
required=required,
10351035
alias=alias,
10361036
constraints=constraints,
1037-
nullable=field.nullable if self.strict_nullable and (field.has_default or required) else None,
1037+
nullable=field.nullable
1038+
if self.strict_nullable and field.nullable is not None
1039+
else (False if self.strict_nullable and (field.has_default or required) else None),
10381040
strip_default_none=self.strip_default_none,
10391041
extras=self.get_field_extras(field),
10401042
use_annotated=self.use_annotated,
@@ -1081,7 +1083,9 @@ def get_ref_data_type(self, ref: str) -> DataType:
10811083
reference = self.model_resolver.add_ref(ref)
10821084
ref_schema = self._load_ref_schema_object(ref)
10831085
is_optional = (
1084-
ref_schema.type_has_null or ref_schema.type == "null" or (self.strict_nullable and ref_schema.nullable)
1086+
ref_schema.type_has_null
1087+
or ref_schema.type == "null"
1088+
or (self.strict_nullable and ref_schema.nullable is True)
10851089
)
10861090
return self.data_type(reference=reference, is_optional=is_optional)
10871091

@@ -2582,7 +2586,9 @@ def parse_root_type( # noqa: PLR0912
25822586
default=obj.default,
25832587
required=required,
25842588
constraints=obj.dict() if self.field_constraints else {},
2585-
nullable=obj.nullable if self.strict_nullable else None,
2589+
nullable=obj.nullable
2590+
if self.strict_nullable and obj.nullable is not None
2591+
else (False if self.strict_nullable and obj.has_default else None),
25862592
strip_default_none=self.strip_default_none,
25872593
extras=self.get_field_extras(obj),
25882594
use_annotated=self.use_annotated,

src/datamodel_code_generator/parser/openapi.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -673,8 +673,14 @@ def parse_all_parameters(
673673
if object_schema and self.is_constraints_field(object_schema)
674674
else None,
675675
nullable=object_schema.nullable
676-
if object_schema and self.strict_nullable and (object_schema.has_default or parameter.required)
677-
else None,
676+
if object_schema and self.strict_nullable and object_schema.nullable is not None
677+
else (
678+
False
679+
if object_schema
680+
and self.strict_nullable
681+
and (object_schema.has_default or parameter.required)
682+
else None
683+
),
678684
strip_default_none=self.strip_default_none,
679685
extras=self.get_field_extras(object_schema) if object_schema else {},
680686
use_annotated=self.use_annotated,

tests/parser/test_openapi.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,3 +946,32 @@ def test_parse_responses_return(
946946
for content_type, expected_type_hint in expected_content_types.items():
947947
assert content_type in result[status_code]
948948
assert result[status_code][content_type].type_hint == expected_type_hint
949+
950+
951+
def test_parse_all_parameters_strict_nullable() -> None:
952+
"""Test that strict_nullable exposes nullable for optional parameters without default."""
953+
parser = OpenAPIParser(
954+
data_model_field_type=DataModelField,
955+
source="",
956+
openapi_scopes=[OpenAPIScope.Parameters],
957+
strict_nullable=True,
958+
)
959+
parameters_data = [
960+
{"name": "nullable_param", "in": "query", "required": False, "schema": {"type": "string", "nullable": True}},
961+
{
962+
"name": "non_nullable_param",
963+
"in": "query",
964+
"required": False,
965+
"schema": {"type": "string", "nullable": False},
966+
},
967+
]
968+
result = parser.parse_all_parameters(
969+
"TestParametersQuery",
970+
[ParameterObject.parse_obj(param_data) for param_data in parameters_data],
971+
["test", "path"],
972+
)
973+
assert result is not None
974+
fields = parser.results[0].fields
975+
assert len(fields) == 2
976+
assert fields[0].nullable is True
977+
assert fields[1].nullable is False

0 commit comments

Comments
 (0)