[python] fix deserialize on basic str fails (#18800)

* [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
This commit is contained in:
ふぁ
2024-06-06 17:15:50 +09:00
committed by GitHub
parent d1254ccfda
commit 6ae8a8f4c7
10 changed files with 197 additions and 64 deletions

View File

@@ -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(
@@ -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)

View File

@@ -315,10 +315,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(
@@ -386,21 +383,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)

View File

@@ -113,15 +113,12 @@ class TestManual(unittest.TestCase):
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

View File

@@ -315,10 +315,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(
@@ -386,21 +383,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)

View File

@@ -174,18 +174,18 @@ class TestManual(unittest.TestCase):
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()
api_response = api_instance.test_auth_http_basic()

View File

@@ -317,10 +317,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(
@@ -388,21 +385,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)

View File

@@ -314,10 +314,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(
@@ -385,21 +382,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)

View File

@@ -53,6 +53,9 @@ class TestErrorResponsesWithModels(unittest.TestCase):
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
@@ -71,6 +74,9 @@ class TestErrorResponsesWithModels(unittest.TestCase):
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

View File

@@ -39,7 +39,7 @@ class DeserializationTests(unittest.TestCase):
}
response = json.dumps(data)
deserialized = self.deserialize(response, 'Dict[str, EnumTest]')
deserialized = self.deserialize(response, 'Dict[str, EnumTest]', 'application/json')
self.assertTrue(isinstance(deserialized, dict))
self.assertTrue(isinstance(deserialized['enum_test'], petstore_api.EnumTest))
self.assertEqual(deserialized['enum_test'],
@@ -73,7 +73,7 @@ class DeserializationTests(unittest.TestCase):
}
response = json.dumps(data)
deserialized = self.deserialize(response, 'Dict[str, Pet]')
deserialized = self.deserialize(response, 'Dict[str, Pet]', 'application/json')
self.assertTrue(isinstance(deserialized, dict))
self.assertTrue(isinstance(deserialized['pet'], petstore_api.Pet))
@@ -90,7 +90,7 @@ class DeserializationTests(unittest.TestCase):
}
response = json.dumps(data)
deserialized = self.deserialize(response, 'Dict[str, Animal]')
deserialized = self.deserialize(response, 'Dict[str, Animal]', 'application/json')
self.assertTrue(isinstance(deserialized, dict))
self.assertTrue(isinstance(deserialized['dog'], petstore_api.Dog))
@@ -102,7 +102,7 @@ class DeserializationTests(unittest.TestCase):
}
response = json.dumps(data)
deserialized = self.deserialize(response, 'Dict[str, int]')
deserialized = self.deserialize(response, 'Dict[str, int]', 'application/json')
self.assertTrue(isinstance(deserialized, dict))
self.assertTrue(isinstance(deserialized['integer'], int))
@@ -111,7 +111,7 @@ class DeserializationTests(unittest.TestCase):
data = "test str"
response = data=json.dumps(data)
deserialized = self.deserialize(response, "str")
deserialized = self.deserialize(response, "str", 'application/json')
self.assertTrue(isinstance(deserialized, str))
def test_deserialize_date(self):
@@ -119,7 +119,7 @@ class DeserializationTests(unittest.TestCase):
data = "1997-07-16"
response = data=json.dumps(data)
deserialized = self.deserialize(response, "date")
deserialized = self.deserialize(response, "date", 'application/json')
self.assertTrue(isinstance(deserialized, datetime.date))
def test_deserialize_datetime(self):
@@ -127,7 +127,7 @@ class DeserializationTests(unittest.TestCase):
data = "1997-07-16T19:20:30.45+01:00"
response = json.dumps(data)
deserialized = self.deserialize(response, "datetime")
deserialized = self.deserialize(response, "datetime", 'application/json')
self.assertTrue(isinstance(deserialized, datetime.datetime))
def test_deserialize_pet(self):
@@ -152,7 +152,7 @@ class DeserializationTests(unittest.TestCase):
}
response = json.dumps(data)
deserialized = self.deserialize(response, "Pet")
deserialized = self.deserialize(response, "Pet", 'application/json')
self.assertTrue(isinstance(deserialized, petstore_api.Pet))
self.assertEqual(deserialized.id, 0)
self.assertEqual(deserialized.name, "doggie")
@@ -202,7 +202,7 @@ class DeserializationTests(unittest.TestCase):
}]
response = json.dumps(data)
deserialized = self.deserialize(response, "List[Pet]")
deserialized = self.deserialize(response, "List[Pet]", 'application/json')
self.assertTrue(isinstance(deserialized, list))
self.assertTrue(isinstance(deserialized[0], petstore_api.Pet))
self.assertEqual(deserialized[0].id, 0)
@@ -219,7 +219,7 @@ class DeserializationTests(unittest.TestCase):
}
response = json.dumps(data)
deserialized = self.deserialize(response, "Dict[str, Dict[str, int]]")
deserialized = self.deserialize(response, "Dict[str, Dict[str, int]]", 'application/json')
self.assertTrue(isinstance(deserialized, dict))
self.assertTrue(isinstance(deserialized["foo"], dict))
self.assertTrue(isinstance(deserialized["foo"]["bar"], int))
@@ -229,7 +229,7 @@ class DeserializationTests(unittest.TestCase):
data = [["foo"]]
response = json.dumps(data)
deserialized = self.deserialize(response, "List[List[str]]")
deserialized = self.deserialize(response, "List[List[str]]", 'application/json')
self.assertTrue(isinstance(deserialized, list))
self.assertTrue(isinstance(deserialized[0], list))
self.assertTrue(isinstance(deserialized[0][0], str))
@@ -238,7 +238,7 @@ class DeserializationTests(unittest.TestCase):
""" deserialize None """
response = json.dumps(None)
deserialized = self.deserialize(response, "datetime")
deserialized = self.deserialize(response, "datetime", 'application/json')
self.assertIsNone(deserialized)
def test_deserialize_pig(self):
@@ -249,7 +249,7 @@ class DeserializationTests(unittest.TestCase):
}
response = json.dumps(data)
deserialized = self.deserialize(response, "Pig")
deserialized = self.deserialize(response, "Pig", 'application/json')
self.assertTrue(isinstance(deserialized.actual_instance,
petstore_api.BasquePig))
self.assertEqual(deserialized.actual_instance.class_name, "BasqueBig")
@@ -265,7 +265,7 @@ class DeserializationTests(unittest.TestCase):
response = json.dumps(data)
with pytest.raises(ValueError) as ex:
deserialized = self.deserialize(response, "Animal")
deserialized = self.deserialize(response, "Animal", 'application/json')
assert str(
ex.value) == 'Animal failed to lookup discriminator value from {"declawed": true, "className": ' \
'"Cat2222"}. Discriminator property name: className, mapping: {"Cat": "Cat", "Dog": "Dog"}'
@@ -277,7 +277,7 @@ class DeserializationTests(unittest.TestCase):
response = json.dumps(data)
deserialized = self.deserialize(response, "Animal")
deserialized = self.deserialize(response, "Animal", 'application/json')
self.assertTrue(isinstance(deserialized, petstore_api.Cat))
self.assertEqual(deserialized.class_name, "Cat")
self.assertEqual(deserialized.declawed, True)

View File

@@ -155,7 +155,7 @@ class TestFakeApi(unittest.TestCase):
mock_resp.data = b'{"value": "0"}'
mock_resp.getheaders.return_value = {}
mock_resp.getheader = (
lambda name: "text/plain" if name == "content-type" else Mock()
lambda name: "application/json" if name == "content-type" else Mock()
)
with patch(
"petstore_api.api_client.ApiClient.call_api", return_value=mock_resp
@@ -165,3 +165,78 @@ class TestFakeApi(unittest.TestCase):
param=[OuterEnumInteger.NUMBER_0])
self.assertEqual(call_api_mock.call_args[0][1],
'http://petstore.swagger.io:80/v2/fake/property/enum-int?param=0')
def testTopLevelStrJson(self):
"""Test TopLevelStrJson"""
mock_resp = Mock()
mock_resp.status = 200
mock_resp.data = b'"a"'
mock_resp.getheaders.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
):
returned = self.fake_api.fake_return_string()
self.assertEqual('a', returned)
def testTopLevelIntJson(self):
"""Test TopLevelIntJson"""
mock_resp = Mock()
mock_resp.status = 200
mock_resp.data = b'1'
mock_resp.getheaders.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
):
returned = self.fake_api.fake_return_int()
self.assertEqual(1, returned)
def testTopLevelFloatJson(self):
"""Test TopLevelFloatJson"""
mock_resp = Mock()
mock_resp.status = 200
mock_resp.data = b'3.4'
mock_resp.getheaders.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
):
returned = self.fake_api.fake_return_float()
self.assertEqual(3.4, returned)
def testTopLevelBoolJson(self):
"""Test TopLevelBoolJson"""
mock_resp = Mock()
mock_resp.status = 200
mock_resp.data = b'true'
mock_resp.getheaders.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
):
returned = self.fake_api.fake_return_boolean()
self.assertEqual(True, returned)
def testTopLevelEnumJson(self):
"""Test TopLevelEnumJson"""
mock_resp = Mock()
mock_resp.status = 200
mock_resp.data = b'"a"'
mock_resp.getheaders.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
):
returned = self.fake_api.fake_return_enum()
self.assertEqual("a", returned)