From 4ff8c57ba4620a8737e666419b2d55dcf7a8e220 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sat, 20 Dec 2025 14:45:06 +0000 Subject: [PATCH] Fix nullable field access in custom templates with strict_nullable --- .../parser/jsonschema.py | 14 ++++++--- .../parser/openapi.py | 10 +++++-- tests/parser/test_openapi.py | 29 +++++++++++++++++++ 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/datamodel_code_generator/parser/jsonschema.py b/src/datamodel_code_generator/parser/jsonschema.py index 1834937b9..b816ce07d 100644 --- a/src/datamodel_code_generator/parser/jsonschema.py +++ b/src/datamodel_code_generator/parser/jsonschema.py @@ -335,7 +335,7 @@ def validate_null_type(cls, value: Any) -> Any: # noqa: N805 properties: Optional[dict[str, Union[JsonSchemaObject, bool]]] = None # noqa: UP007, UP045 required: list[str] = [] # noqa: RUF012 ref: Optional[str] = Field(default=None, alias="$ref") # noqa: UP045 - nullable: Optional[bool] = False # noqa: UP045 + nullable: Optional[bool] = None # noqa: UP045 x_enum_varnames: list[str] = Field(default_factory=list, alias="x-enum-varnames") x_enum_names: list[str] = Field(default_factory=list, alias="x-enumNames") description: Optional[str] = None # noqa: UP045 @@ -1032,7 +1032,9 @@ def get_object_field( # noqa: PLR0913 required=required, alias=alias, constraints=constraints, - nullable=field.nullable if self.strict_nullable and (field.has_default or required) else None, + nullable=field.nullable + if self.strict_nullable and field.nullable is not None + else (False if self.strict_nullable and (field.has_default or required) else None), strip_default_none=self.strip_default_none, extras=self.get_field_extras(field), use_annotated=self.use_annotated, @@ -1075,7 +1077,9 @@ def get_ref_data_type(self, ref: str) -> DataType: reference = self.model_resolver.add_ref(ref) ref_schema = self._load_ref_schema_object(ref) is_optional = ( - ref_schema.type_has_null or ref_schema.type == "null" or (self.strict_nullable and ref_schema.nullable) + ref_schema.type_has_null + or ref_schema.type == "null" + or (self.strict_nullable and ref_schema.nullable is True) ) return self.data_type(reference=reference, is_optional=is_optional) @@ -2576,7 +2580,9 @@ def parse_root_type( # noqa: PLR0912 default=obj.default, required=required, constraints=obj.dict() if self.field_constraints else {}, - nullable=obj.nullable if self.strict_nullable else None, + nullable=obj.nullable + if self.strict_nullable and obj.nullable is not None + else (False if self.strict_nullable and obj.has_default else None), strip_default_none=self.strip_default_none, extras=self.get_field_extras(obj), use_annotated=self.use_annotated, diff --git a/src/datamodel_code_generator/parser/openapi.py b/src/datamodel_code_generator/parser/openapi.py index cef0fbddb..58ad5cf76 100644 --- a/src/datamodel_code_generator/parser/openapi.py +++ b/src/datamodel_code_generator/parser/openapi.py @@ -671,8 +671,14 @@ def parse_all_parameters( if object_schema and self.is_constraints_field(object_schema) else None, nullable=object_schema.nullable - if object_schema and self.strict_nullable and (object_schema.has_default or parameter.required) - else None, + if object_schema and self.strict_nullable and object_schema.nullable is not None + else ( + False + if object_schema + and self.strict_nullable + and (object_schema.has_default or parameter.required) + else None + ), strip_default_none=self.strip_default_none, extras=self.get_field_extras(object_schema) if object_schema else {}, use_annotated=self.use_annotated, diff --git a/tests/parser/test_openapi.py b/tests/parser/test_openapi.py index 2df603c9a..f25c601ae 100644 --- a/tests/parser/test_openapi.py +++ b/tests/parser/test_openapi.py @@ -946,3 +946,32 @@ def test_parse_responses_return( for content_type, expected_type_hint in expected_content_types.items(): assert content_type in result[status_code] assert result[status_code][content_type].type_hint == expected_type_hint + + +def test_parse_all_parameters_strict_nullable() -> None: + """Test that strict_nullable exposes nullable for optional parameters without default.""" + parser = OpenAPIParser( + data_model_field_type=DataModelField, + source="", + openapi_scopes=[OpenAPIScope.Parameters], + strict_nullable=True, + ) + parameters_data = [ + {"name": "nullable_param", "in": "query", "required": False, "schema": {"type": "string", "nullable": True}}, + { + "name": "non_nullable_param", + "in": "query", + "required": False, + "schema": {"type": "string", "nullable": False}, + }, + ] + result = parser.parse_all_parameters( + "TestParametersQuery", + [ParameterObject.parse_obj(param_data) for param_data in parameters_data], + ["test", "path"], + ) + assert result is not None + fields = parser.results[0].fields + assert len(fields) == 2 + assert fields[0].nullable is True + assert fields[1].nullable is False