Skip to content
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

chore: increase test coverage #10

Merged
merged 6 commits into from
Jan 16, 2024
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
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies = [
"python_dateutil",
"typing_extensions",
"python-magic",
"typeguard",
]


Expand Down Expand Up @@ -44,10 +45,10 @@ dev = [
'waylay[services,services-types]',
]
services = [
"waylay_registry_api @ git+https://github.com/waylayio/waylay-py-services@feat/multipart_uploads#subdirectory=services/registry/waylay_registry_api"
"waylay_registry_api @ git+https://github.com/waylayio/waylay-py-services@main#subdirectory=services/registry/waylay_registry_api"
]
services-types = [
"waylay_registry_types @ git+https://github.com/waylayio/waylay-py-services@feat/multipart_uploads#subdirectory=services/registry/waylay_registry_types"
"waylay_registry_types @ git+https://github.com/waylayio/waylay-py-services@main#subdirectory=services/registry/waylay_registry_types"
]
[tool.pytest.ini_options]
asyncio_mode = "auto"
Expand Down
152 changes: 42 additions & 110 deletions src/waylay/api/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,20 @@

from importlib import import_module
from types import SimpleNamespace
from typing import Any, Dict, Optional, List, cast
from typing import Any, Mapping, Optional, cast

import datetime
from dateutil.parser import parse
import json
import mimetypes
import os
import re
import tempfile

from urllib.parse import quote

from waylay.__version__ import __version__
from waylay.api.api_config import ApiConfig
from waylay.api.api_response import ApiResponse
from waylay.config import WaylayConfig

from waylay.api import rest
from waylay.api.api_exceptions import (
ApiValueError,
from ..api import rest
from .api_config import ApiConfig
from .api_response import ApiResponse
from .api_exceptions import (
ApiError,
)

Expand Down Expand Up @@ -51,40 +46,29 @@ class ApiClient:

def __init__(
self,
configuration: ApiConfig,
waylay_config: WaylayConfig,
) -> None:
"""Create an instance."""
self.configuration = configuration
self.rest_client = rest.RESTClient(configuration)
self.default_headers: Dict[str, Any] = {}

# Set default User-Agent.
self.user_agent = f"waylay-sdk/python/{__version__}"
self.client_side_validation = configuration.client_side_validation
self.config = ApiConfig(waylay_config)

@property
def user_agent(self):
"""User agent for this API client."""
return self.default_headers['User-Agent']
rest_client_kwargs = {"auth": waylay_config.auth}
rest_client_kwargs.update(self.config._client_options or {})
self.rest_client = rest.RESTClient(**rest_client_kwargs)

@user_agent.setter
def user_agent(self, value):
self.default_headers['User-Agent'] = value

def set_default_header(self, header_name: str, header_value: Any):
"""Set a default header."""
self.default_headers[header_name] = header_value
self.default_headers: dict[str, Any] = {
'User-Agent': "waylay-sdk/python/{0}".format(__version__)
}

def param_serialize(
self,
method: str,
resource_path: str,
path_params: Optional[Dict[str, str]] = None,
query_params: Optional[Dict[str, Any]] = None,
header_params: Optional[Dict[str, Optional[str]]] = None,
path_params: Optional[Mapping[str, str]] = None,
query_params: Optional[Mapping[str, Any]] = None,
header_params: Optional[Mapping[str, Optional[str]]] = None,
body: Optional[Any] = None,
files: Optional[Dict[str, str]] = None,
) -> Dict[str, Any]:
files: Optional[Mapping[str, str]] = None,
) -> dict[str, Any]:
"""Build the HTTP request params needed by the request.

:param method: Method to call.
Expand All @@ -96,14 +80,13 @@ def param_serialize(
:param body: Request body.
:param files dict: key -> filename, value -> filepath, for
`multipart/form-data`.
:return: Dict of form {path, method, query_params,
:return: dict of form {path, method, query_params,
header_params, body, files}

"""
config = self.configuration

# header parameters
header_params = header_params or {}
header_params = dict(header_params or {})
header_params.update(self.default_headers)
if header_params:
header_params = self.__sanitize_for_serialization(header_params)
Expand All @@ -116,7 +99,7 @@ def param_serialize(
# specified safe chars, encode everything
resource_path = resource_path.replace(
'{%s}' % k,
quote(str(v), safe=config.safe_chars_for_path_param)
quote(str(v))
)

# post parameters
Expand All @@ -127,7 +110,7 @@ def param_serialize(
body = self.__sanitize_for_serialization(body)

# request url
url = self.configuration.host + resource_path
url = self.config.host + resource_path

# query parameters
if query_params:
Expand All @@ -144,13 +127,13 @@ def param_serialize(

async def call_api(
self,
method,
url,
query_params: Optional[Dict[str, Any]] = None,
header_params=None,
body=None,
files=None,
_request_timeout=None
method: str,
url: str,
query_params: Optional[Mapping[str, Any]] = None,
header_params: Optional[Mapping[str, str]] = None,
body: Optional[Any] = None,
files: Optional[Mapping[str, str]] = None,
_request_timeout: Optional[rest.RESTTimeout] = None
) -> rest.RESTResponse:
"""Make the HTTP request (synchronous) :param method: Method to call.

Expand All @@ -164,19 +147,13 @@ async def call_api(
:return: RESTResponse

"""

try:
# perform request and return response
response_data = await self.rest_client.request(
method, url, query=query_params,
headers=header_params,
body=body, files=files,
_request_timeout=_request_timeout
)

except ApiError as e:
raise e

# perform request and return response
response_data = await self.rest_client.request(
method, url, query=query_params,
headers=header_params,
body=body, files=files,
_request_timeout=_request_timeout
)
return response_data

def response_deserialize(
Expand All @@ -195,18 +172,16 @@ def response_deserialize(
response_type = response_types_map.get(str(response_data.status_code), None)
if not response_type and isinstance(response_data.status_code, int) and 100 <= response_data.status_code <= 599:
# if not found, look for '1XX', '2XX', etc.
response_type = response_types_map.get(str(response_data.status_code)[0] + "XX", None)
response_type = response_types_map.get(str(response_data.status_code)[0] + "XX")
if not response_type:
# if still not found, look for default response type
response_type = response_types_map.get('*', None) or response_types_map.get('default', None)
# if still not found, look for default response type, otherwise use `Any`
response_type = response_types_map.get('*') or response_types_map.get('default') or Any

# deserialize response data
return_data = None
try:
if response_type in _PRIMITIVE_BYTE_TYPES + tuple(t.__name__ for t in _PRIMITIVE_BYTE_TYPES):
return_data = response_data.content
elif response_type == "file":
return_data = self.__deserialize_file(response_data)
elif response_type is not None:
try:
_data = response_data.json()
Expand Down Expand Up @@ -321,36 +296,6 @@ def __sanitize_files_parameters(self, files=None):

return files

def __deserialize_file(self, response: rest.RESTResponse):
"""Deserializes body to file.

Saves response body into a file in a temporary folder, using the
filename from the `Content-Disposition` header if provided.

handle file downloading save response body into a tmp file and
return the instance

:param response: RESTResponse.
:return: file path.

"""
fd, path = tempfile.mkstemp(dir=self.configuration.temp_folder_path)
os.close(fd)
os.remove(path)

content_disposition = response.headers.get("Content-Disposition")
if content_disposition:
filename = re.search(
r'filename=[\'"]?([^\'"\s]+)[\'"]?',
content_disposition
).group(1) # type: ignore[union-attr]
path = os.path.join(os.path.dirname(path), filename)

with open(path, "wb") as f:
f.write(response.content)

return path

def __deserialize_primitive(self, data, klass):
"""Deserializes string to primitive type.

Expand Down Expand Up @@ -383,13 +328,8 @@ def __deserialize_date(self, string):
"""
try:
return parse(string).date()
except ImportError:
return string
except ValueError:
raise ApiError(
status=0,
reason="Failed to parse `{0}` as date object".format(string)
)
return string

def __deserialize_datetime(self, string):
"""Deserializes string to datetime.
Expand All @@ -402,16 +342,8 @@ def __deserialize_datetime(self, string):
"""
try:
return parse(string)
except ImportError:
return string
except ValueError:
raise ApiError(
status=0,
reason=(
"Failed to parse `{0}` as datetime object"
.format(string)
)
)
return string

def __deserialize_model(self, data, klass):
"""Deserializes list or dict to model.
Expand Down
Loading