Skip to content

[python][Feat] Deserialize error responses #17038

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,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.
Expand All @@ -304,39 +304,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,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# coding: utf-8

{{>partial_header}}
from typing import Any, Optional

from typing_extensions import Self

class OpenApiException(Exception):
"""The base exception class for all OpenAPIExceptions"""
Expand Down Expand Up @@ -91,17 +94,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"""
Expand All @@ -111,38 +153,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):
pass

def __init__(self, status=None, reason=None, http_resp=None) -> None:
super(BadRequestException, self).__init__(status, reason, http_resp)

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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
)

_response_types_map: Dict[str, Optional[str]] = {
{{#returnType}}{{#responses}}{{^isWildcard}}'{{code}}': {{#dataType}}"{{.}}"{{/dataType}}{{^dataType}}None{{/dataType}}{{/isWildcard}}{{^-last}},{{/-last}}
{{/responses}}{{/returnType}}
{{#responses}}
{{^isWildcard}}
'{{code}}': {{#dataType}}"{{.}}"{{/dataType}}{{^dataType}}None{{/dataType}},
{{/isWildcard}}
{{/responses}}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,49 @@ paths:
schema:
$ref: '#/components/schemas/User'
required: true
/fake/empty_and_non_empty_responses:
post:
tags:
- fake
summary: test empty and non-empty responses
description: ''
operationId: testEmptyAndNonEmptyResponses
responses:
'204':
description: Success, but no response content
'206':
description: Partial response content
content:
text/plain:
schema:
type: string
/fake/error_responses_with_model:
post:
tags:
- fake
summary: test error responses with model
operationId: testErrorResponsesWithModel
responses:
'204':
description: Success, but no response content
'400':
description: ''
content:
application/json:
schema:
type: object
properties:
reason400:
type: string
'404':
description: ''
content:
application/json:
schema:
type: object
properties:
reason404:
type: string
/another-fake/dummy:
patch:
tags:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,7 @@ def test_auth_http_basic(
)

_response_types_map: Dict[str, Optional[str]] = {
'200': "str"

'200': "str",
}
response_data = self.api_client.call_api(
*_param,
Expand Down Expand Up @@ -157,8 +156,7 @@ def test_auth_http_basic_with_http_info(
)

_response_types_map: Dict[str, Optional[str]] = {
'200': "str"

'200': "str",
}
response_data = self.api_client.call_api(
*_param,
Expand Down Expand Up @@ -221,8 +219,7 @@ def test_auth_http_basic_without_preload_content(
)

_response_types_map: Dict[str, Optional[str]] = {
'200': "str"

'200': "str",
}
response_data = self.api_client.call_api(
*_param,
Expand Down
Loading