Skip to content

Commit 345c41b

Browse files
forest-benchlingNementon
authored andcommitted
fix: Deserialization of optional nullable properties when no value is returned from the API [openapi-generators#420 & openapi-generators#381]. Thanks @forest-benchling!
* Handle optional nullable deserialization * Fix test
1 parent ea75894 commit 345c41b

File tree

10 files changed

+89
-63
lines changed

10 files changed

+89
-63
lines changed

end_to_end_tests/golden-record/my_test_api_client/models/a_model.py

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,6 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
186186

187187
def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.datetime]:
188188
try:
189-
a_camel_date_time_type_0: datetime.datetime
190189
if not isinstance(data, str):
191190
raise TypeError()
192191
a_camel_date_time_type_0 = isoparse(data)
@@ -196,7 +195,6 @@ def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.date
196195
pass
197196
if not isinstance(data, str):
198197
raise TypeError()
199-
a_camel_date_time_type_1: datetime.date
200198
a_camel_date_time_type_1 = isoparse(data).date()
201199

202200
return a_camel_date_time_type_1
@@ -209,7 +207,6 @@ def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.date
209207

210208
def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty]:
211209
try:
212-
one_of_models_type_0: FreeFormModel
213210
if not isinstance(data, dict):
214211
raise TypeError()
215212
one_of_models_type_0 = FreeFormModel.from_dict(data)
@@ -219,7 +216,6 @@ def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionPro
219216
pass
220217
if not isinstance(data, dict):
221218
raise TypeError()
222-
one_of_models_type_1: ModelWithUnionProperty
223219
one_of_models_type_1 = ModelWithUnionProperty.from_dict(data)
224220

225221
return one_of_models_type_1
@@ -228,9 +224,11 @@ def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionPro
228224

229225
model = ModelWithUnionProperty.from_dict(d.pop("model"))
230226

231-
an_optional_allof_enum: Union[Unset, AnAllOfEnum] = UNSET
232227
_an_optional_allof_enum = d.pop("an_optional_allof_enum", UNSET)
233-
if not isinstance(_an_optional_allof_enum, Unset):
228+
an_optional_allof_enum: Union[Unset, AnAllOfEnum]
229+
if isinstance(_an_optional_allof_enum, Unset):
230+
an_optional_allof_enum = UNSET
231+
else:
234232
an_optional_allof_enum = AnAllOfEnum(_an_optional_allof_enum)
235233

236234
nested_list_of_enums = []
@@ -245,14 +243,18 @@ def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionPro
245243

246244
nested_list_of_enums.append(nested_list_of_enums_item)
247245

248-
a_nullable_date = None
249246
_a_nullable_date = d.pop("a_nullable_date")
250-
if _a_nullable_date is not None:
247+
a_nullable_date: Optional[datetime.date]
248+
if _a_nullable_date is None:
249+
a_nullable_date = None
250+
else:
251251
a_nullable_date = isoparse(_a_nullable_date).date()
252252

253-
a_not_required_date: Union[Unset, datetime.date] = UNSET
254253
_a_not_required_date = d.pop("a_not_required_date", UNSET)
255-
if not isinstance(_a_not_required_date, Unset):
254+
a_not_required_date: Union[Unset, datetime.date]
255+
if isinstance(_a_not_required_date, Unset):
256+
a_not_required_date = UNSET
257+
else:
256258
a_not_required_date = isoparse(_a_not_required_date).date()
257259

258260
attr_1_leading_digit = d.pop("1_leading_digit", UNSET)
@@ -267,7 +269,6 @@ def _parse_nullable_one_of_models(data: object) -> Union[FreeFormModel, ModelWit
267269
if data is None:
268270
return data
269271
try:
270-
nullable_one_of_models_type_0: FreeFormModel
271272
if not isinstance(data, dict):
272273
raise TypeError()
273274
nullable_one_of_models_type_0 = FreeFormModel.from_dict(data)
@@ -277,7 +278,6 @@ def _parse_nullable_one_of_models(data: object) -> Union[FreeFormModel, ModelWit
277278
pass
278279
if not isinstance(data, dict):
279280
raise TypeError()
280-
nullable_one_of_models_type_1: ModelWithUnionProperty
281281
nullable_one_of_models_type_1 = ModelWithUnionProperty.from_dict(data)
282282

283283
return nullable_one_of_models_type_1
@@ -288,23 +288,25 @@ def _parse_not_required_one_of_models(data: object) -> Union[FreeFormModel, Mode
288288
if isinstance(data, Unset):
289289
return data
290290
try:
291-
not_required_one_of_models_type_0: Union[Unset, FreeFormModel]
292291
if not isinstance(data, dict):
293292
raise TypeError()
294-
not_required_one_of_models_type_0 = UNSET
295293
_not_required_one_of_models_type_0 = data
296-
if not isinstance(_not_required_one_of_models_type_0, Unset):
294+
not_required_one_of_models_type_0: Union[Unset, FreeFormModel]
295+
if isinstance(_not_required_one_of_models_type_0, Unset):
296+
not_required_one_of_models_type_0 = UNSET
297+
else:
297298
not_required_one_of_models_type_0 = FreeFormModel.from_dict(_not_required_one_of_models_type_0)
298299

299300
return not_required_one_of_models_type_0
300301
except: # noqa: E722
301302
pass
302303
if not isinstance(data, dict):
303304
raise TypeError()
304-
not_required_one_of_models_type_1: Union[Unset, ModelWithUnionProperty]
305-
not_required_one_of_models_type_1 = UNSET
306305
_not_required_one_of_models_type_1 = data
307-
if not isinstance(_not_required_one_of_models_type_1, Unset):
306+
not_required_one_of_models_type_1: Union[Unset, ModelWithUnionProperty]
307+
if isinstance(_not_required_one_of_models_type_1, Unset):
308+
not_required_one_of_models_type_1 = UNSET
309+
else:
308310
not_required_one_of_models_type_1 = ModelWithUnionProperty.from_dict(_not_required_one_of_models_type_1)
309311

310312
return not_required_one_of_models_type_1
@@ -319,12 +321,13 @@ def _parse_not_required_nullable_one_of_models(
319321
if isinstance(data, Unset):
320322
return data
321323
try:
322-
not_required_nullable_one_of_models_type_0: Union[Unset, FreeFormModel]
323324
if not isinstance(data, dict):
324325
raise TypeError()
325-
not_required_nullable_one_of_models_type_0 = UNSET
326326
_not_required_nullable_one_of_models_type_0 = data
327-
if not isinstance(_not_required_nullable_one_of_models_type_0, Unset):
327+
not_required_nullable_one_of_models_type_0: Union[Unset, FreeFormModel]
328+
if isinstance(_not_required_nullable_one_of_models_type_0, Unset):
329+
not_required_nullable_one_of_models_type_0 = UNSET
330+
else:
328331
not_required_nullable_one_of_models_type_0 = FreeFormModel.from_dict(
329332
_not_required_nullable_one_of_models_type_0
330333
)
@@ -333,12 +336,13 @@ def _parse_not_required_nullable_one_of_models(
333336
except: # noqa: E722
334337
pass
335338
try:
336-
not_required_nullable_one_of_models_type_1: Union[Unset, ModelWithUnionProperty]
337339
if not isinstance(data, dict):
338340
raise TypeError()
339-
not_required_nullable_one_of_models_type_1 = UNSET
340341
_not_required_nullable_one_of_models_type_1 = data
341-
if not isinstance(_not_required_nullable_one_of_models_type_1, Unset):
342+
not_required_nullable_one_of_models_type_1: Union[Unset, ModelWithUnionProperty]
343+
if isinstance(_not_required_nullable_one_of_models_type_1, Unset):
344+
not_required_nullable_one_of_models_type_1 = UNSET
345+
else:
342346
not_required_nullable_one_of_models_type_1 = ModelWithUnionProperty.from_dict(
343347
_not_required_nullable_one_of_models_type_1
344348
)
@@ -352,19 +356,27 @@ def _parse_not_required_nullable_one_of_models(
352356
d.pop("not_required_nullable_one_of_models", UNSET)
353357
)
354358

355-
nullable_model = None
356359
_nullable_model = d.pop("nullable_model")
357-
if _nullable_model is not None:
360+
nullable_model: Optional[ModelWithUnionProperty]
361+
if _nullable_model is None:
362+
nullable_model = None
363+
else:
358364
nullable_model = ModelWithUnionProperty.from_dict(_nullable_model)
359365

360-
not_required_model: Union[Unset, ModelWithUnionProperty] = UNSET
361366
_not_required_model = d.pop("not_required_model", UNSET)
362-
if not isinstance(_not_required_model, Unset):
367+
not_required_model: Union[Unset, ModelWithUnionProperty]
368+
if isinstance(_not_required_model, Unset):
369+
not_required_model = UNSET
370+
else:
363371
not_required_model = ModelWithUnionProperty.from_dict(_not_required_model)
364372

365-
not_required_nullable_model = None
366373
_not_required_nullable_model = d.pop("not_required_nullable_model", UNSET)
367-
if _not_required_nullable_model is not None and not isinstance(_not_required_nullable_model, Unset):
374+
not_required_nullable_model: Union[Unset, None, ModelWithUnionProperty]
375+
if _not_required_nullable_model is None:
376+
not_required_nullable_model = None
377+
elif isinstance(_not_required_nullable_model, Unset):
378+
not_required_nullable_model = UNSET
379+
else:
368380
not_required_nullable_model = ModelWithUnionProperty.from_dict(_not_required_nullable_model)
369381

370382
a_model = cls(

end_to_end_tests/golden-record/my_test_api_client/models/model_with_any_json_properties.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ def _parse_additional_property(
4646
data: object,
4747
) -> Union[List[str], ModelWithAnyJsonPropertiesAdditionalPropertyType0, bool, float, int, str]:
4848
try:
49-
additional_property_type_0: ModelWithAnyJsonPropertiesAdditionalPropertyType0
5049
if not isinstance(data, dict):
5150
raise TypeError()
5251
additional_property_type_0 = ModelWithAnyJsonPropertiesAdditionalPropertyType0.from_dict(data)
@@ -55,7 +54,6 @@ def _parse_additional_property(
5554
except: # noqa: E722
5655
pass
5756
try:
58-
additional_property_type_1: List[str]
5957
if not isinstance(data, list):
6058
raise TypeError()
6159
additional_property_type_1 = cast(List[str], data)

end_to_end_tests/golden-record/my_test_api_client/models/model_with_primitive_additional_properties.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ def to_dict(self) -> Dict[str, Any]:
3333
@classmethod
3434
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
3535
d = src_dict.copy()
36-
a_date_holder: Union[Unset, ModelWithPrimitiveAdditionalPropertiesADateHolder] = UNSET
3736
_a_date_holder = d.pop("a_date_holder", UNSET)
38-
if not isinstance(_a_date_holder, Unset):
37+
a_date_holder: Union[Unset, ModelWithPrimitiveAdditionalPropertiesADateHolder]
38+
if isinstance(_a_date_holder, Unset):
39+
a_date_holder = UNSET
40+
else:
3941
a_date_holder = ModelWithPrimitiveAdditionalPropertiesADateHolder.from_dict(_a_date_holder)
4042

4143
model_with_primitive_additional_properties = cls(

end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ def to_dict(self) -> Dict[str, Any]:
3131
@classmethod
3232
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
3333
d = src_dict.copy()
34-
inner: Union[Unset, ModelName] = UNSET
3534
_inner = d.pop("inner", UNSET)
36-
if not isinstance(_inner, Unset):
35+
inner: Union[Unset, ModelName]
36+
if isinstance(_inner, Unset):
37+
inner = UNSET
38+
else:
3739
inner = ModelName.from_dict(_inner)
3840

3941
model_with_property_ref = cls(

end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,25 @@ def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]:
4444
if isinstance(data, Unset):
4545
return data
4646
try:
47-
a_property_type_0: Union[Unset, AnEnum]
4847
if not isinstance(data, str):
4948
raise TypeError()
50-
a_property_type_0 = UNSET
5149
_a_property_type_0 = data
52-
if not isinstance(_a_property_type_0, Unset):
50+
a_property_type_0: Union[Unset, AnEnum]
51+
if isinstance(_a_property_type_0, Unset):
52+
a_property_type_0 = UNSET
53+
else:
5354
a_property_type_0 = AnEnum(_a_property_type_0)
5455

5556
return a_property_type_0
5657
except: # noqa: E722
5758
pass
5859
if not isinstance(data, int):
5960
raise TypeError()
60-
a_property_type_1: Union[Unset, AnIntEnum]
61-
a_property_type_1 = UNSET
6261
_a_property_type_1 = data
63-
if not isinstance(_a_property_type_1, Unset):
62+
a_property_type_1: Union[Unset, AnIntEnum]
63+
if isinstance(_a_property_type_1, Unset):
64+
a_property_type_1 = UNSET
65+
else:
6466
a_property_type_1 = AnIntEnum(_a_property_type_1)
6567

6668
return a_property_type_1

end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property_inlined.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,23 +46,25 @@ def _parse_fruit(
4646
if isinstance(data, Unset):
4747
return data
4848
try:
49-
fruit_type_0: Union[Unset, ModelWithUnionPropertyInlinedFruitType0]
5049
if not isinstance(data, dict):
5150
raise TypeError()
52-
fruit_type_0 = UNSET
5351
_fruit_type_0 = data
54-
if not isinstance(_fruit_type_0, Unset):
52+
fruit_type_0: Union[Unset, ModelWithUnionPropertyInlinedFruitType0]
53+
if isinstance(_fruit_type_0, Unset):
54+
fruit_type_0 = UNSET
55+
else:
5556
fruit_type_0 = ModelWithUnionPropertyInlinedFruitType0.from_dict(_fruit_type_0)
5657

5758
return fruit_type_0
5859
except: # noqa: E722
5960
pass
6061
if not isinstance(data, dict):
6162
raise TypeError()
62-
fruit_type_1: Union[Unset, ModelWithUnionPropertyInlinedFruitType1]
63-
fruit_type_1 = UNSET
6463
_fruit_type_1 = data
65-
if not isinstance(_fruit_type_1, Unset):
64+
fruit_type_1: Union[Unset, ModelWithUnionPropertyInlinedFruitType1]
65+
if isinstance(_fruit_type_1, Unset):
66+
fruit_type_1 = UNSET
67+
else:
6668
fruit_type_1 = ModelWithUnionPropertyInlinedFruitType1.from_dict(_fruit_type_1)
6769

6870
return fruit_type_1
Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
{% macro construct_template(construct_function, property, source, initial_value=None) %}
22
{% if property.required and not property.nullable %}
33
{{ property.python_name }} = {{ construct_function(property, source) }}
4-
{% else %}
5-
{% if initial_value != None %}
6-
{{ property.python_name }} = {{ initial_value }}
7-
{% elif property.nullable %}
8-
{{ property.python_name }} = None
9-
{% else %}
10-
{{ property.python_name }}: {{ property.get_type_string() }} = UNSET
11-
{% endif %}
4+
{% else %}{# Must be nullable OR non-required #}
125
_{{ property.python_name }} = {{ source }}
13-
if {% if property.nullable %}_{{ property.python_name }} is not None{% endif %}{% if property.nullable and not property.required %} and {% endif %}{% if not property.required %}not isinstance(_{{ property.python_name }}, Unset){% endif %}:
6+
{{ property.python_name }}: {{ property.get_type_string() }}
7+
{% if property.nullable %}
8+
if _{{ property.python_name }} is None:
9+
{{ property.python_name }} = {% if initial_value != None %}{{ initial_value }}{% else %}None{% endif %}
10+
11+
{% endif %}
12+
{% if not property.required %}
13+
{% if property.nullable %}elif{% else %}if{% endif %} isinstance(_{{ property.python_name }}, Unset):
14+
{{ property.python_name }} = {% if initial_value != None %}{{ initial_value }}{% else %}UNSET{% endif %}
15+
16+
{% endif %}
17+
else:
1418
{{ property.python_name }} = {{ construct_function(property, "_" + property.python_name) }}
1519
{% endif %}
1620
{% endmacro %}

openapi_python_client/templates/property_templates/union_property.py.jinja

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ def _parse_{{ property.python_name }}(data: object) -> {{ property.get_type_stri
1111
{% for inner_property in property.inner_properties_with_template() %}
1212
{% if not loop.last or property.has_properties_without_templates %}
1313
try:
14-
{{ inner_property.python_name }}: {{ inner_property.get_type_string() }}
1514
{% from "property_templates/" + inner_property.template import construct, check_type_for_construct %}
1615
if not {{ check_type_for_construct(inner_property, "data") }}:
1716
raise TypeError()
@@ -23,7 +22,6 @@ def _parse_{{ property.python_name }}(data: object) -> {{ property.get_type_stri
2322
{% from "property_templates/" + inner_property.template import construct, check_type_for_construct %}
2423
if not {{ check_type_for_construct(inner_property, "data") }}:
2524
raise TypeError()
26-
{{ inner_property.python_name }}: {{ inner_property.get_type_string() }}
2725
{{ construct(inner_property, "data", initial_value="UNSET") | indent(4) }}
2826
return {{ inner_property.python_name }}
2927
{% endif %}

tests/test_templates/test_property_templates/test_date_property/optional_nullable.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@
77
if not isinstance(some_source, Unset):
88
some_destination = some_source.isoformat() if some_source else None
99

10-
a_prop = None
1110
_a_prop = some_destination
12-
if _a_prop is not None and not isinstance(_a_prop, Unset):
11+
a_prop: Union[Unset, None, datetime.date]
12+
if _a_prop is None:
13+
a_prop = None
14+
elif isinstance(_a_prop, Unset):
15+
a_prop = UNSET
16+
else:
1317
a_prop = isoparse(_a_prop).date()
1418

1519

tests/test_templates/test_property_templates/test_date_property/required_nullable.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
from dateutil.parser import isoparse
55
some_source = date(2020, 10, 12)
66
some_destination = some_source.isoformat() if some_source else None
7-
a_prop = None
87
_a_prop = some_destination
9-
if _a_prop is not None:
8+
a_prop: Optional[datetime.date]
9+
if _a_prop is None:
10+
a_prop = None
11+
else:
1012
a_prop = isoparse(_a_prop).date()
1113

1214

0 commit comments

Comments
 (0)