Skip to content

Commit 6ae8a8f

Browse files
authored
[python] fix deserialize on basic str fails (OpenAPITools#18800)
* [python] fix OpenAPITools#18774 Deserialize on basic str fails * [python] update sample * [python] update test * [python] remove type * [python] fix test * [python] add top level type test * Update deserialize content_type parameter and quote * [python] restore echo_api test * [python] add allow empty json in Response
1 parent d1254cc commit 6ae8a8f

10 files changed

Lines changed: 197 additions & 64 deletions

File tree

modules/openapi-generator/src/main/resources/python/api_client.mustache

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -322,10 +322,7 @@ class ApiClient:
322322
match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type)
323323
encoding = match.group(1) if match else "utf-8"
324324
response_text = response_data.data.decode(encoding)
325-
if response_type in ["bytearray", "str"]:
326-
return_data = self.__deserialize_primitive(response_text, response_type)
327-
else:
328-
return_data = self.deserialize(response_text, response_type)
325+
return_data = self.deserialize(response_text, response_type, content_type)
329326
finally:
330327
if not 200 <= response_data.status <= 299:
331328
raise ApiException.from_response(
@@ -393,21 +390,35 @@ class ApiClient:
393390
for key, val in obj_dict.items()
394391
}
395392

396-
def deserialize(self, response_text, response_type):
393+
def deserialize(self, response_text: str, response_type: str, content_type: Optional[str]):
397394
"""Deserializes response into an object.
398395

399396
:param response: RESTResponse object to be deserialized.
400397
:param response_type: class literal for
401398
deserialized object, or string of class name.
399+
:param content_type: content type of response.
402400

403401
:return: deserialized object.
404402
"""
405403

406404
# fetch data from response object
407-
try:
408-
data = json.loads(response_text)
409-
except ValueError:
405+
if content_type is None:
406+
try:
407+
data = json.loads(response_text)
408+
except ValueError:
409+
data = response_text
410+
elif content_type.startswith("application/json"):
411+
if response_text == "":
412+
data = ""
413+
else:
414+
data = json.loads(response_text)
415+
elif content_type.startswith("text/plain"):
410416
data = response_text
417+
else:
418+
raise ApiException(
419+
status=0,
420+
reason="Unsupported content type: {0}".format(content_type)
421+
)
411422

412423
return self.__deserialize(data, response_type)
413424

samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/openapi_client/api_client.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -315,10 +315,7 @@ def response_deserialize(
315315
match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type)
316316
encoding = match.group(1) if match else "utf-8"
317317
response_text = response_data.data.decode(encoding)
318-
if response_type in ["bytearray", "str"]:
319-
return_data = self.__deserialize_primitive(response_text, response_type)
320-
else:
321-
return_data = self.deserialize(response_text, response_type)
318+
return_data = self.deserialize(response_text, response_type, content_type)
322319
finally:
323320
if not 200 <= response_data.status <= 299:
324321
raise ApiException.from_response(
@@ -386,21 +383,35 @@ def sanitize_for_serialization(self, obj):
386383
for key, val in obj_dict.items()
387384
}
388385

389-
def deserialize(self, response_text, response_type):
386+
def deserialize(self, response_text: str, response_type: str, content_type: Optional[str]):
390387
"""Deserializes response into an object.
391388
392389
:param response: RESTResponse object to be deserialized.
393390
:param response_type: class literal for
394391
deserialized object, or string of class name.
392+
:param content_type: content type of response.
395393
396394
:return: deserialized object.
397395
"""
398396

399397
# fetch data from response object
400-
try:
401-
data = json.loads(response_text)
402-
except ValueError:
398+
if content_type is None:
399+
try:
400+
data = json.loads(response_text)
401+
except ValueError:
402+
data = response_text
403+
elif content_type.startswith("application/json"):
404+
if response_text == "":
405+
data = ""
406+
else:
407+
data = json.loads(response_text)
408+
elif content_type.startswith("text/plain"):
403409
data = response_text
410+
else:
411+
raise ApiException(
412+
status=0,
413+
reason="Unsupported content type: {0}".format(content_type)
414+
)
404415

405416
return self.__deserialize(data, response_type)
406417

samples/client/echo_api/python-disallowAdditionalPropertiesIfNotPresent/tests/test_manual.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,12 @@ def testBodyParameter(self):
113113
n = openapi_client.Pet.from_dict({"name": "testing", "photoUrls": ["http://1", "http://2"]})
114114
api_instance = openapi_client.BodyApi()
115115
api_response = api_instance.test_echo_body_pet_response_string(n)
116-
self.assertEqual(api_response, '{"name": "testing", "photoUrls": ["http://1", "http://2"]}')
116+
self.assertEqual(api_response, "{'name': 'testing', 'photoUrls': ['http://1', 'http://2']}")
117117

118118
t = openapi_client.Tag()
119119
api_response = api_instance.test_echo_body_tag_response_string(t)
120120
self.assertEqual(api_response, "{}") # assertion to ensure {} is sent in the body
121121

122-
api_response = api_instance.test_echo_body_tag_response_string(None)
123-
self.assertEqual(api_response, "") # assertion to ensure emtpy string is sent in the body
124-
125122
api_response = api_instance.test_echo_body_free_form_object_response_string({})
126123
self.assertEqual(api_response, "{}") # assertion to ensure {} is sent in the body
127124

samples/client/echo_api/python/openapi_client/api_client.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -315,10 +315,7 @@ def response_deserialize(
315315
match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type)
316316
encoding = match.group(1) if match else "utf-8"
317317
response_text = response_data.data.decode(encoding)
318-
if response_type in ["bytearray", "str"]:
319-
return_data = self.__deserialize_primitive(response_text, response_type)
320-
else:
321-
return_data = self.deserialize(response_text, response_type)
318+
return_data = self.deserialize(response_text, response_type, content_type)
322319
finally:
323320
if not 200 <= response_data.status <= 299:
324321
raise ApiException.from_response(
@@ -386,21 +383,35 @@ def sanitize_for_serialization(self, obj):
386383
for key, val in obj_dict.items()
387384
}
388385

389-
def deserialize(self, response_text, response_type):
386+
def deserialize(self, response_text: str, response_type: str, content_type: Optional[str]):
390387
"""Deserializes response into an object.
391388
392389
:param response: RESTResponse object to be deserialized.
393390
:param response_type: class literal for
394391
deserialized object, or string of class name.
392+
:param content_type: content type of response.
395393
396394
:return: deserialized object.
397395
"""
398396

399397
# fetch data from response object
400-
try:
401-
data = json.loads(response_text)
402-
except ValueError:
398+
if content_type is None:
399+
try:
400+
data = json.loads(response_text)
401+
except ValueError:
402+
data = response_text
403+
elif content_type.startswith("application/json"):
404+
if response_text == "":
405+
data = ""
406+
else:
407+
data = json.loads(response_text)
408+
elif content_type.startswith("text/plain"):
403409
data = response_text
410+
else:
411+
raise ApiException(
412+
status=0,
413+
reason="Unsupported content type: {0}".format(content_type)
414+
)
404415

405416
return self.__deserialize(data, response_type)
406417

samples/client/echo_api/python/tests/test_manual.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,17 +174,17 @@ def testBodyParameter(self):
174174
n = openapi_client.Pet.from_dict({"name": "testing", "photoUrls": ["http://1", "http://2"]})
175175
api_instance = openapi_client.BodyApi()
176176
api_response = api_instance.test_echo_body_pet_response_string(n)
177-
self.assertEqual(api_response, '{"name": "testing", "photoUrls": ["http://1", "http://2"]}')
177+
self.assertEqual(api_response, "{'name': 'testing', 'photoUrls': ['http://1', 'http://2']}")
178178

179179
t = openapi_client.Tag()
180180
api_response = api_instance.test_echo_body_tag_response_string(t)
181181
self.assertEqual(api_response, "{}") # assertion to ensure {} is sent in the body
182182

183-
api_response = api_instance.test_echo_body_tag_response_string(None)
184-
self.assertEqual(api_response, "") # assertion to ensure emtpy string is sent in the body
185-
186183
api_response = api_instance.test_echo_body_free_form_object_response_string({})
187184
self.assertEqual(api_response, "{}") # assertion to ensure {} is sent in the body
185+
186+
api_response = api_instance.test_echo_body_tag_response_string(None)
187+
self.assertEqual(api_response, "") # assertion to ensure emtpy string is sent in the body
188188

189189
def testAuthHttpBasic(self):
190190
api_instance = openapi_client.AuthApi()

samples/openapi3/client/petstore/python-aiohttp/petstore_api/api_client.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -317,10 +317,7 @@ def response_deserialize(
317317
match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type)
318318
encoding = match.group(1) if match else "utf-8"
319319
response_text = response_data.data.decode(encoding)
320-
if response_type in ["bytearray", "str"]:
321-
return_data = self.__deserialize_primitive(response_text, response_type)
322-
else:
323-
return_data = self.deserialize(response_text, response_type)
320+
return_data = self.deserialize(response_text, response_type, content_type)
324321
finally:
325322
if not 200 <= response_data.status <= 299:
326323
raise ApiException.from_response(
@@ -388,21 +385,35 @@ def sanitize_for_serialization(self, obj):
388385
for key, val in obj_dict.items()
389386
}
390387

391-
def deserialize(self, response_text, response_type):
388+
def deserialize(self, response_text: str, response_type: str, content_type: Optional[str]):
392389
"""Deserializes response into an object.
393390
394391
:param response: RESTResponse object to be deserialized.
395392
:param response_type: class literal for
396393
deserialized object, or string of class name.
394+
:param content_type: content type of response.
397395
398396
:return: deserialized object.
399397
"""
400398

401399
# fetch data from response object
402-
try:
403-
data = json.loads(response_text)
404-
except ValueError:
400+
if content_type is None:
401+
try:
402+
data = json.loads(response_text)
403+
except ValueError:
404+
data = response_text
405+
elif content_type.startswith("application/json"):
406+
if response_text == "":
407+
data = ""
408+
else:
409+
data = json.loads(response_text)
410+
elif content_type.startswith("text/plain"):
405411
data = response_text
412+
else:
413+
raise ApiException(
414+
status=0,
415+
reason="Unsupported content type: {0}".format(content_type)
416+
)
406417

407418
return self.__deserialize(data, response_type)
408419

samples/openapi3/client/petstore/python/petstore_api/api_client.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -314,10 +314,7 @@ def response_deserialize(
314314
match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type)
315315
encoding = match.group(1) if match else "utf-8"
316316
response_text = response_data.data.decode(encoding)
317-
if response_type in ["bytearray", "str"]:
318-
return_data = self.__deserialize_primitive(response_text, response_type)
319-
else:
320-
return_data = self.deserialize(response_text, response_type)
317+
return_data = self.deserialize(response_text, response_type, content_type)
321318
finally:
322319
if not 200 <= response_data.status <= 299:
323320
raise ApiException.from_response(
@@ -385,21 +382,35 @@ def sanitize_for_serialization(self, obj):
385382
for key, val in obj_dict.items()
386383
}
387384

388-
def deserialize(self, response_text, response_type):
385+
def deserialize(self, response_text: str, response_type: str, content_type: Optional[str]):
389386
"""Deserializes response into an object.
390387
391388
:param response: RESTResponse object to be deserialized.
392389
:param response_type: class literal for
393390
deserialized object, or string of class name.
391+
:param content_type: content type of response.
394392
395393
:return: deserialized object.
396394
"""
397395

398396
# fetch data from response object
399-
try:
400-
data = json.loads(response_text)
401-
except ValueError:
397+
if content_type is None:
398+
try:
399+
data = json.loads(response_text)
400+
except ValueError:
401+
data = response_text
402+
elif content_type.startswith("application/json"):
403+
if response_text == "":
404+
data = ""
405+
else:
406+
data = json.loads(response_text)
407+
elif content_type.startswith("text/plain"):
402408
data = response_text
409+
else:
410+
raise ApiException(
411+
status=0,
412+
reason="Unsupported content type: {0}".format(content_type)
413+
)
403414

404415
return self.__deserialize(data, response_type)
405416

samples/openapi3/client/petstore/python/tests/test_api.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ def test_400(self):
5353
mock_resp.data = json.dumps({"reason400": "400 reason"}).encode("utf-8")
5454
mock_resp.getheaders.return_value = {}
5555
mock_resp.getheader.return_value = ""
56+
mock_resp.getheader = (
57+
lambda name: "application/json" if name == "content-type" else Mock()
58+
)
5659

5760
with patch(
5861
"petstore_api.api_client.ApiClient.call_api", return_value=mock_resp
@@ -71,6 +74,9 @@ def test_404(self):
7174
mock_resp.data = json.dumps({"reason404": "404 reason"}).encode("utf-8")
7275
mock_resp.getheaders.return_value = {}
7376
mock_resp.getheader.return_value = ""
77+
mock_resp.getheader = (
78+
lambda name: "application/json" if name == "content-type" else Mock()
79+
)
7480

7581
with patch(
7682
"petstore_api.api_client.ApiClient.call_api", return_value=mock_resp

0 commit comments

Comments
 (0)