[python][Feat] Deserialize error responses (#17038)

* refactor: Clean up _response_types_map formatting

It matches black's behavior of having trailing commas now.

* test: Add test to reproduce #16967

* fix: deserialize responses even if no returnType

Closes #16967

* refactor: Simplify ApiException subclasses

* refactor: Move exception subtype choice to ApiException

* feat: Deserialize error responses and add to exceptions

* test: Add for error responses with model
This commit is contained in:
Robert Schweizer
2023-11-15 17:37:04 +01:00
committed by GitHub
parent 69fcfeff38
commit e47e7041f7
85 changed files with 4309 additions and 951 deletions

View File

@@ -93,8 +93,7 @@ class AuthApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -157,8 +156,7 @@ class AuthApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -221,8 +219,7 @@ class AuthApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,

View File

@@ -101,8 +101,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "bytearray"
'200': "bytearray",
}
response_data = self.api_client.call_api(
*_param,
@@ -165,8 +164,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "bytearray"
'200': "bytearray",
}
response_data = self.api_client.call_api(
*_param,
@@ -229,8 +227,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "bytearray"
'200': "bytearray",
}
response_data = self.api_client.call_api(
*_param,
@@ -351,8 +348,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -419,8 +415,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -487,8 +482,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -630,8 +624,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -698,8 +691,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -766,8 +758,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -905,8 +896,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "Pet"
'200': "Pet",
}
response_data = self.api_client.call_api(
*_param,
@@ -973,8 +963,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "Pet"
'200': "Pet",
}
response_data = self.api_client.call_api(
*_param,
@@ -1041,8 +1030,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "Pet"
'200': "Pet",
}
response_data = self.api_client.call_api(
*_param,
@@ -1179,8 +1167,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -1247,8 +1234,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -1315,8 +1301,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -1453,8 +1438,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "Pet"
'200': "Pet",
}
response_data = self.api_client.call_api(
*_param,
@@ -1521,8 +1505,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "Pet"
'200': "Pet",
}
response_data = self.api_client.call_api(
*_param,
@@ -1589,8 +1572,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "Pet"
'200': "Pet",
}
response_data = self.api_client.call_api(
*_param,
@@ -1727,8 +1709,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -1795,8 +1776,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -1863,8 +1843,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -2001,8 +1980,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -2069,8 +2047,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -2137,8 +2114,7 @@ class BodyApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,

View File

@@ -109,8 +109,7 @@ class FormApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -185,8 +184,7 @@ class FormApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -261,8 +259,7 @@ class FormApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -425,8 +422,7 @@ class FormApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -513,8 +509,7 @@ class FormApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -601,8 +596,7 @@ class FormApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,

View File

@@ -118,8 +118,7 @@ class HeaderApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -202,8 +201,7 @@ class HeaderApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -286,8 +284,7 @@ class HeaderApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,

View File

@@ -112,8 +112,7 @@ class PathApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -192,8 +191,7 @@ class PathApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -272,8 +270,7 @@ class PathApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,

View File

@@ -110,8 +110,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -182,8 +181,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -254,8 +252,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -394,8 +391,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -470,8 +466,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -546,8 +541,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -709,8 +703,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -785,8 +778,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -861,8 +853,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -998,8 +989,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -1066,8 +1056,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -1134,8 +1123,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -1261,8 +1249,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -1329,8 +1316,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -1397,8 +1383,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -1524,8 +1509,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -1592,8 +1576,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -1660,8 +1643,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -1787,8 +1769,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -1855,8 +1836,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -1923,8 +1903,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -2050,8 +2029,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -2118,8 +2096,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,
@@ -2186,8 +2163,7 @@ class QueryApi:
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "str"
'200': "str",
}
response_data = self.api_client.call_api(
*_param,

View File

@@ -282,7 +282,7 @@ class ApiClient:
def response_deserialize(
self,
response_data=None,
response_data: rest.RESTResponse = None,
response_types_map=None
) -> ApiResponse:
"""Deserializes response into an object.
@@ -297,39 +297,29 @@ class ApiClient:
# if not found, look for '1XX', '2XX', etc.
response_type = response_types_map.get(str(response_data.status)[0] + "XX", None)
if not 200 <= response_data.status <= 299:
if response_data.status == 400:
raise BadRequestException(http_resp=response_data)
if response_data.status == 401:
raise UnauthorizedException(http_resp=response_data)
if response_data.status == 403:
raise ForbiddenException(http_resp=response_data)
if response_data.status == 404:
raise NotFoundException(http_resp=response_data)
if 500 <= response_data.status <= 599:
raise ServiceException(http_resp=response_data)
raise ApiException(http_resp=response_data)
# deserialize response data
if response_type == "bytearray":
return_data = response_data.data
elif response_type is None:
return_data = None
elif response_type == "file":
return_data = self.__deserialize_file(response_data)
else:
match = None
content_type = response_data.getheader('content-type')
if content_type is not None:
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)
return_data = self.deserialize(response_text, response_type)
response_text = None
return_data = None
try:
if response_type == "bytearray":
return_data = response_data.data
elif response_type == "file":
return_data = self.__deserialize_file(response_data)
elif response_type is not None:
match = None
content_type = response_data.getheader('content-type')
if content_type is not None:
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)
return_data = self.deserialize(response_text, response_type)
finally:
if not 200 <= response_data.status <= 299:
raise ApiException.from_response(
http_resp=response_data,
body=response_text,
data=return_data,
)
return ApiResponse(
status_code = response_data.status,

View File

@@ -12,6 +12,9 @@
Do not edit the class manually.
""" # noqa: E501
from typing import Any, Optional
from typing_extensions import Self
class OpenApiException(Exception):
"""The base exception class for all OpenAPIExceptions"""
@@ -102,17 +105,56 @@ class ApiKeyError(OpenApiException, KeyError):
class ApiException(OpenApiException):
def __init__(self, status=None, reason=None, http_resp=None) -> None:
def __init__(
self,
status=None,
reason=None,
http_resp=None,
*,
body: Optional[str] = None,
data: Optional[Any] = None,
) -> None:
self.status = status
self.reason = reason
self.body = body
self.data = data
self.headers = None
if http_resp:
self.status = http_resp.status
self.reason = http_resp.reason
self.body = http_resp.data.decode('utf-8')
if self.status is None:
self.status = http_resp.status
if self.reason is None:
self.reason = http_resp.reason
if self.body is None:
try:
self.body = http_resp.data.decode('utf-8')
except Exception:
pass
self.headers = http_resp.getheaders()
else:
self.status = status
self.reason = reason
self.body = None
self.headers = None
@classmethod
def from_response(
cls,
*,
http_resp,
body: Optional[str],
data: Optional[Any],
) -> Self:
if http_resp.status == 400:
raise BadRequestException(http_resp=http_resp, body=body, data=data)
if http_resp.status == 401:
raise UnauthorizedException(http_resp=http_resp, body=body, data=data)
if http_resp.status == 403:
raise ForbiddenException(http_resp=http_resp, body=body, data=data)
if http_resp.status == 404:
raise NotFoundException(http_resp=http_resp, body=body, data=data)
if 500 <= http_resp.status <= 599:
raise ServiceException(http_resp=http_resp, body=body, data=data)
raise ApiException(http_resp=http_resp, body=body, data=data)
def __str__(self):
"""Custom error messages for exception"""
@@ -122,38 +164,30 @@ class ApiException(OpenApiException):
error_message += "HTTP response headers: {0}\n".format(
self.headers)
if self.body:
error_message += "HTTP response body: {0}\n".format(self.body)
if self.data or self.body:
error_message += "HTTP response body: {0}\n".format(self.data or self.body)
return error_message
class BadRequestException(ApiException):
def __init__(self, status=None, reason=None, http_resp=None) -> None:
super(BadRequestException, self).__init__(status, reason, http_resp)
class BadRequestException(ApiException):
pass
class NotFoundException(ApiException):
def __init__(self, status=None, reason=None, http_resp=None) -> None:
super(NotFoundException, self).__init__(status, reason, http_resp)
pass
class UnauthorizedException(ApiException):
def __init__(self, status=None, reason=None, http_resp=None) -> None:
super(UnauthorizedException, self).__init__(status, reason, http_resp)
pass
class ForbiddenException(ApiException):
def __init__(self, status=None, reason=None, http_resp=None) -> None:
super(ForbiddenException, self).__init__(status, reason, http_resp)
pass
class ServiceException(ApiException):
def __init__(self, status=None, reason=None, http_resp=None) -> None:
super(ServiceException, self).__init__(status, reason, http_resp)
pass
def render_path(path_to_item):