Skip to content

Commit

Permalink
[python] fix deserialize on basic str fails (#18800)
Browse files Browse the repository at this point in the history
* [python] fix #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
  • Loading branch information
fa0311 authored Jun 6, 2024
1 parent d1254cc commit 6ae8a8f
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -322,10 +322,7 @@ class ApiClient:
match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type)
encoding = match.group(1) if match else "utf-8"
response_text = response_data.data.decode(encoding)
if response_type in ["bytearray", "str"]:
return_data = self.__deserialize_primitive(response_text, response_type)
else:
return_data = self.deserialize(response_text, response_type)
return_data = self.deserialize(response_text, response_type, content_type)
finally:
if not 200 <= response_data.status <= 299:
raise ApiException.from_response(
Expand Down Expand Up @@ -393,21 +390,35 @@ class ApiClient:
for key, val in obj_dict.items()
}

def deserialize(self, response_text, response_type):
def deserialize(self, response_text: str, response_type: str, content_type: Optional[str]):
"""Deserializes response into an object.

:param response: RESTResponse object to be deserialized.
:param response_type: class literal for
deserialized object, or string of class name.
:param content_type: content type of response.

:return: deserialized object.
"""

# fetch data from response object
try:
data = json.loads(response_text)
except ValueError:
if content_type is None:
try:
data = json.loads(response_text)
except ValueError:
data = response_text
elif content_type.startswith("application/json"):
if response_text == "":
data = ""
else:
data = json.loads(response_text)
elif content_type.startswith("text/plain"):
data = response_text
else:
raise ApiException(
status=0,
reason="Unsupported content type: {0}".format(content_type)
)

return self.__deserialize(data, response_type)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,10 +315,7 @@ def response_deserialize(
match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type)
encoding = match.group(1) if match else "utf-8"
response_text = response_data.data.decode(encoding)
if response_type in ["bytearray", "str"]:
return_data = self.__deserialize_primitive(response_text, response_type)
else:
return_data = self.deserialize(response_text, response_type)
return_data = self.deserialize(response_text, response_type, content_type)
finally:
if not 200 <= response_data.status <= 299:
raise ApiException.from_response(
Expand Down Expand Up @@ -386,21 +383,35 @@ def sanitize_for_serialization(self, obj):
for key, val in obj_dict.items()
}

def deserialize(self, response_text, response_type):
def deserialize(self, response_text: str, response_type: str, content_type: Optional[str]):
"""Deserializes response into an object.
:param response: RESTResponse object to be deserialized.
:param response_type: class literal for
deserialized object, or string of class name.
:param content_type: content type of response.
:return: deserialized object.
"""

# fetch data from response object
try:
data = json.loads(response_text)
except ValueError:
if content_type is None:
try:
data = json.loads(response_text)
except ValueError:
data = response_text
elif content_type.startswith("application/json"):
if response_text == "":
data = ""
else:
data = json.loads(response_text)
elif content_type.startswith("text/plain"):
data = response_text
else:
raise ApiException(
status=0,
reason="Unsupported content type: {0}".format(content_type)
)

return self.__deserialize(data, response_type)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,12 @@ def testBodyParameter(self):
n = openapi_client.Pet.from_dict({"name": "testing", "photoUrls": ["http://1", "http://2"]})
api_instance = openapi_client.BodyApi()
api_response = api_instance.test_echo_body_pet_response_string(n)
self.assertEqual(api_response, '{"name": "testing", "photoUrls": ["http://1", "http://2"]}')
self.assertEqual(api_response, "{'name': 'testing', 'photoUrls': ['http://1', 'http://2']}")

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

api_response = api_instance.test_echo_body_tag_response_string(None)
self.assertEqual(api_response, "") # assertion to ensure emtpy string is sent in the body

api_response = api_instance.test_echo_body_free_form_object_response_string({})
self.assertEqual(api_response, "{}") # assertion to ensure {} is sent in the body

Expand Down
27 changes: 19 additions & 8 deletions samples/client/echo_api/python/openapi_client/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,10 +315,7 @@ def response_deserialize(
match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type)
encoding = match.group(1) if match else "utf-8"
response_text = response_data.data.decode(encoding)
if response_type in ["bytearray", "str"]:
return_data = self.__deserialize_primitive(response_text, response_type)
else:
return_data = self.deserialize(response_text, response_type)
return_data = self.deserialize(response_text, response_type, content_type)
finally:
if not 200 <= response_data.status <= 299:
raise ApiException.from_response(
Expand Down Expand Up @@ -386,21 +383,35 @@ def sanitize_for_serialization(self, obj):
for key, val in obj_dict.items()
}

def deserialize(self, response_text, response_type):
def deserialize(self, response_text: str, response_type: str, content_type: Optional[str]):
"""Deserializes response into an object.
:param response: RESTResponse object to be deserialized.
:param response_type: class literal for
deserialized object, or string of class name.
:param content_type: content type of response.
:return: deserialized object.
"""

# fetch data from response object
try:
data = json.loads(response_text)
except ValueError:
if content_type is None:
try:
data = json.loads(response_text)
except ValueError:
data = response_text
elif content_type.startswith("application/json"):
if response_text == "":
data = ""
else:
data = json.loads(response_text)
elif content_type.startswith("text/plain"):
data = response_text
else:
raise ApiException(
status=0,
reason="Unsupported content type: {0}".format(content_type)
)

return self.__deserialize(data, response_type)

Expand Down
8 changes: 4 additions & 4 deletions samples/client/echo_api/python/tests/test_manual.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,17 +174,17 @@ def testBodyParameter(self):
n = openapi_client.Pet.from_dict({"name": "testing", "photoUrls": ["http://1", "http://2"]})
api_instance = openapi_client.BodyApi()
api_response = api_instance.test_echo_body_pet_response_string(n)
self.assertEqual(api_response, '{"name": "testing", "photoUrls": ["http://1", "http://2"]}')
self.assertEqual(api_response, "{'name': 'testing', 'photoUrls': ['http://1', 'http://2']}")

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

api_response = api_instance.test_echo_body_tag_response_string(None)
self.assertEqual(api_response, "") # assertion to ensure emtpy string is sent in the body

api_response = api_instance.test_echo_body_free_form_object_response_string({})
self.assertEqual(api_response, "{}") # assertion to ensure {} is sent in the body

api_response = api_instance.test_echo_body_tag_response_string(None)
self.assertEqual(api_response, "") # assertion to ensure emtpy string is sent in the body

def testAuthHttpBasic(self):
api_instance = openapi_client.AuthApi()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,7 @@ def response_deserialize(
match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type)
encoding = match.group(1) if match else "utf-8"
response_text = response_data.data.decode(encoding)
if response_type in ["bytearray", "str"]:
return_data = self.__deserialize_primitive(response_text, response_type)
else:
return_data = self.deserialize(response_text, response_type)
return_data = self.deserialize(response_text, response_type, content_type)
finally:
if not 200 <= response_data.status <= 299:
raise ApiException.from_response(
Expand Down Expand Up @@ -388,21 +385,35 @@ def sanitize_for_serialization(self, obj):
for key, val in obj_dict.items()
}

def deserialize(self, response_text, response_type):
def deserialize(self, response_text: str, response_type: str, content_type: Optional[str]):
"""Deserializes response into an object.
:param response: RESTResponse object to be deserialized.
:param response_type: class literal for
deserialized object, or string of class name.
:param content_type: content type of response.
:return: deserialized object.
"""

# fetch data from response object
try:
data = json.loads(response_text)
except ValueError:
if content_type is None:
try:
data = json.loads(response_text)
except ValueError:
data = response_text
elif content_type.startswith("application/json"):
if response_text == "":
data = ""
else:
data = json.loads(response_text)
elif content_type.startswith("text/plain"):
data = response_text
else:
raise ApiException(
status=0,
reason="Unsupported content type: {0}".format(content_type)
)

return self.__deserialize(data, response_type)

Expand Down
27 changes: 19 additions & 8 deletions samples/openapi3/client/petstore/python/petstore_api/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,7 @@ def response_deserialize(
match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type)
encoding = match.group(1) if match else "utf-8"
response_text = response_data.data.decode(encoding)
if response_type in ["bytearray", "str"]:
return_data = self.__deserialize_primitive(response_text, response_type)
else:
return_data = self.deserialize(response_text, response_type)
return_data = self.deserialize(response_text, response_type, content_type)
finally:
if not 200 <= response_data.status <= 299:
raise ApiException.from_response(
Expand Down Expand Up @@ -385,21 +382,35 @@ def sanitize_for_serialization(self, obj):
for key, val in obj_dict.items()
}

def deserialize(self, response_text, response_type):
def deserialize(self, response_text: str, response_type: str, content_type: Optional[str]):
"""Deserializes response into an object.
:param response: RESTResponse object to be deserialized.
:param response_type: class literal for
deserialized object, or string of class name.
:param content_type: content type of response.
:return: deserialized object.
"""

# fetch data from response object
try:
data = json.loads(response_text)
except ValueError:
if content_type is None:
try:
data = json.loads(response_text)
except ValueError:
data = response_text
elif content_type.startswith("application/json"):
if response_text == "":
data = ""
else:
data = json.loads(response_text)
elif content_type.startswith("text/plain"):
data = response_text
else:
raise ApiException(
status=0,
reason="Unsupported content type: {0}".format(content_type)
)

return self.__deserialize(data, response_type)

Expand Down
6 changes: 6 additions & 0 deletions samples/openapi3/client/petstore/python/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ def test_400(self):
mock_resp.data = json.dumps({"reason400": "400 reason"}).encode("utf-8")
mock_resp.getheaders.return_value = {}
mock_resp.getheader.return_value = ""
mock_resp.getheader = (
lambda name: "application/json" if name == "content-type" else Mock()
)

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

with patch(
"petstore_api.api_client.ApiClient.call_api", return_value=mock_resp
Expand Down
Loading

0 comments on commit 6ae8a8f

Please sign in to comment.