Skip to content
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
1 change: 1 addition & 0 deletions doc/changelog.d/778.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Deserialize API responses for non 2XX status codes if defined
128 changes: 61 additions & 67 deletions src/ansys/openapi/common/_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@

from ._base import ApiClientBase, DeserializedType, ModelBase, PrimitiveType, SerializedType, Unset
from ._exceptions import ApiException, UndefinedObjectWarning
from ._util import SessionConfiguration, handle_response
from ._logger import logger
from ._util import SessionConfiguration


# noinspection DuplicatedCode
Expand Down Expand Up @@ -205,14 +206,21 @@ def __call_api(
)

self.last_response = response_data
logger.debug(f"response body: {response_data.text}")

return_data: Union[requests.Response, DeserializedType, None] = response_data
if _preload_content:
_response_type = response_type
if response_type_map is not None:
_response_type = response_type_map.get(response_data.status_code, None)

return_data = self.deserialize(response_data, _response_type)
deserialized_response = self.deserialize(response_data, _response_type)
if not 200 <= response_data.status_code <= 299:
raise ApiException.from_response(response_data, deserialized_response)
return_data = deserialized_response
else:
if not 200 <= response_data.status_code <= 299:
raise ApiException.from_response(response_data)

if _return_http_data_only:
return return_data
Expand Down Expand Up @@ -527,83 +535,69 @@ def request(
timeout setting.
"""
if method == "GET":
return handle_response(
self.rest_client.get(
url,
params=query_params,
stream=_preload_content,
timeout=_request_timeout,
headers=headers,
)
return self.rest_client.get(
url,
params=query_params,
stream=_preload_content,
timeout=_request_timeout,
headers=headers,
)
elif method == "HEAD":
return handle_response(
self.rest_client.head(
url,
params=query_params,
stream=_preload_content,
timeout=_request_timeout,
headers=headers,
)
return self.rest_client.head(
url,
params=query_params,
stream=_preload_content,
timeout=_request_timeout,
headers=headers,
)
elif method == "OPTIONS":
return handle_response(
self.rest_client.options(
url,
params=query_params,
headers=headers,
files=post_params,
stream=_preload_content,
timeout=_request_timeout,
data=body,
)
return self.rest_client.options(
url,
params=query_params,
headers=headers,
files=post_params,
stream=_preload_content,
timeout=_request_timeout,
data=body,
)
elif method == "POST":
return handle_response(
self.rest_client.post(
url,
params=query_params,
headers=headers,
files=post_params,
stream=_preload_content,
timeout=_request_timeout,
data=body,
)
return self.rest_client.post(
url,
params=query_params,
headers=headers,
files=post_params,
stream=_preload_content,
timeout=_request_timeout,
data=body,
)
elif method == "PUT":
return handle_response(
self.rest_client.put(
url,
params=query_params,
headers=headers,
files=post_params,
stream=_preload_content,
timeout=_request_timeout,
data=body,
)
return self.rest_client.put(
url,
params=query_params,
headers=headers,
files=post_params,
stream=_preload_content,
timeout=_request_timeout,
data=body,
)
elif method == "PATCH":
return handle_response(
self.rest_client.patch(
url,
params=query_params,
headers=headers,
files=post_params,
stream=_preload_content,
timeout=_request_timeout,
data=body,
)
return self.rest_client.patch(
url,
params=query_params,
headers=headers,
files=post_params,
stream=_preload_content,
timeout=_request_timeout,
data=body,
)
elif method == "DELETE":
return handle_response(
self.rest_client.delete(
url,
params=query_params,
headers=headers,
stream=_preload_content,
timeout=_request_timeout,
data=body,
)
return self.rest_client.delete(
url,
params=query_params,
headers=headers,
stream=_preload_content,
timeout=_request_timeout,
data=body,
)
else:
raise ValueError(
Expand Down
20 changes: 15 additions & 5 deletions src/ansys/openapi/common/_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from typing import Optional
from typing import TYPE_CHECKING, Optional

from requests.structures import CaseInsensitiveDict

MYPY = False
if MYPY:
if TYPE_CHECKING:
import requests

from ansys.openapi.common._base._types import DeserializedType

Check warning on line 30 in src/ansys/openapi/common/_exceptions.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/openapi/common/_exceptions.py#L30

Added line #L30 was not covered by tests


class ApiConnectionException(Exception):
"""
Expand Down Expand Up @@ -74,7 +75,8 @@
"""
Provides the exception to raise when the remote server returns an unsuccessful response.

For more information about the failure, inspect ``.status_code`` and ``.reason_phrase``.
For more information about the failure, inspect ``.status_code`` and ``.reason_phrase``. If the
server defines a custom exception model, ``.exception_model`` contains the deserialized response.

Parameters
----------
Expand All @@ -84,34 +86,42 @@
Description of the response provided by the server.
body : str, optional
Content of the response provided by the server. The default is ``None``.
exception_model: ModelBase, optional
The custom exception model if defined by the server. The default is ``None``.
headers : CaseInsensitiveDict, optional
Response headers provided by the server. The default is ``None``.
"""

status_code: int
reason_phrase: str
body: Optional[str]
exception_model: "DeserializedType"
headers: Optional[CaseInsensitiveDict]

def __init__(
self,
status_code: int,
reason_phrase: str,
body: Optional[str] = None,
exception_model: "DeserializedType" = None,
headers: Optional[CaseInsensitiveDict] = None,
):
self.status_code = status_code
self.reason_phrase = reason_phrase
self.body = body
self.exception_model = exception_model
self.headers = headers

@classmethod
def from_response(cls, http_response: "requests.Response") -> "ApiException":
def from_response(
cls, http_response: "requests.Response", exception_model: "DeserializedType" = None
) -> "ApiException":
"""Initialize object from a requests.Response object."""
new = cls(
status_code=http_response.status_code,
reason_phrase=http_response.reason,
body=http_response.text,
exception_model=exception_model,
headers=http_response.headers,
)
return new
Expand Down
3 changes: 1 addition & 2 deletions src/ansys/openapi/common/_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from enum import Enum
import os
from typing import Any, Literal, Mapping, Optional, Tuple, TypeVar, Union
from typing import TYPE_CHECKING, Any, Literal, Mapping, Optional, Tuple, TypeVar, Union
import warnings

import requests
Expand All @@ -43,7 +43,6 @@
set_session_kwargs,
)

TYPE_CHECKING = False
if TYPE_CHECKING:
from ._util import CaseInsensitiveOrderedDict

Expand Down
36 changes: 11 additions & 25 deletions src/ansys/openapi/common/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,23 @@
import http.cookiejar
from itertools import chain
import tempfile
from typing import Any, Collection, Dict, List, Optional, Tuple, TypedDict, Union, cast
from typing import (
Any,
Collection,
Dict,
List,
Optional,
Tuple,
TypedDict,
Union,
cast,
)

import pyparsing as pp
from pyparsing import Word
import requests
from requests.structures import CaseInsensitiveDict

from ._exceptions import ApiException
from ._logger import logger


class CaseInsensitiveOrderedDict(OrderedDict):
"""Preserves order of insertion and is case-insensitive.
Expand Down Expand Up @@ -359,27 +366,6 @@ def from_dict(cls, configuration_dict: "RequestsConfiguration") -> "SessionConfi
return new


def handle_response(response: requests.Response) -> requests.Response:
"""Check the status code of a response.

If the response is 2XX, it is returned as-is. Otherwise an :class:`ApiException` class will be raised.

Throws
------
ApiException
If the status code was not 2XX.

Parameters
----------
response : requests.Response
Response from the API server.
"""
logger.debug(f"response body: {response.text}")
if not 200 <= response.status_code <= 299:
raise ApiException.from_response(response)
return response


def generate_user_agent(package_name: str, package_version: str) -> str:
"""Generate a user-agent string in the form *<package info> <python info> <os info>*.

Expand Down
1 change: 1 addition & 0 deletions tests/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

from .example_base_model import ExampleBaseModel
from .example_enum import ExampleEnum
from .example_exception import ExampleException
from .example_int_enum import ExampleIntEnum
from .example_model import ExampleModel
from .example_model_with_enum import ExampleModelWithEnum
Loading