diff --git a/.github/workflows/release-sdk.yaml b/.github/workflows/release-sdk.yaml new file mode 100644 index 00000000..34e3dfb9 --- /dev/null +++ b/.github/workflows/release-sdk.yaml @@ -0,0 +1,31 @@ +name: Release Python SDK + +on: + release: + types: [published] + +env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v2 + - name: Install packaging tools + run: | + python -m pip install --upgrade pip + pip install twine + pip install poetry + - name: Build package + run: | + poetry build + - name: Verify package + run: | + twine check dist/* + - name: Release package + run: | + twine upload dist/* diff --git a/pyproject.toml b/pyproject.toml index 478f71d1..4274781b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ keywords = ["sinch", "sdk"] [tool.poetry.dependencies] python = ">=3.9" requests = "*" -aiohttp = "*" +httpx = "*" [build-system] requires = ["poetry-core"] diff --git a/sinch/core/adapters/asyncio_http_adapter.py b/sinch/core/adapters/asyncio_http_adapter.py deleted file mode 100644 index efd54abd..00000000 --- a/sinch/core/adapters/asyncio_http_adapter.py +++ /dev/null @@ -1,53 +0,0 @@ -import aiohttp -import json -from sinch.core.ports.http_transport import AsyncHTTPTransport, HttpRequest -from sinch.core.endpoint import HTTPEndpoint -from sinch.core.models.http_response import HTTPResponse - - -class HTTPTransportAioHTTP(AsyncHTTPTransport): - def __init__(self, sinch): - super().__init__(sinch) - self.http_session = None - - async def request(self, endpoint: HTTPEndpoint) -> HTTPResponse: - request_data: HttpRequest = self.prepare_request(endpoint) - request_data: HttpRequest = await self.authenticate(endpoint, request_data) - - if not self.http_session: - self.http_session = aiohttp.ClientSession() - - self.sinch.configuration.logger.debug( - f"Async HTTP {request_data.http_method} call with headers:" - f" {request_data.headers} and body: {request_data.request_body} to URL: {request_data.url}" - ) - - async with self.http_session.request( - method=request_data.http_method, - headers=request_data.headers, - url=request_data.url, - data=request_data.request_body, - auth=request_data.auth, - params=request_data.query_params, - timeout=aiohttp.ClientTimeout( - total=self.sinch.configuration.connection_timeout - ) - ) as response: - - response_body = await response.read() - if response_body: - response_body = json.loads(response_body) - - self.sinch.configuration.logger.debug( - f"Async HTTP {response.status} response with headers: {response.headers}" - f"and body: {response_body} from URL: {request_data.url}" - ) - - return await self.handle_response( - endpoint=endpoint, - http_response=HTTPResponse( - status_code=response.status, - body=response_body, - headers=response.headers - ) - ) diff --git a/sinch/core/adapters/httpx_adapter.py b/sinch/core/adapters/httpx_adapter.py new file mode 100644 index 00000000..54820037 --- /dev/null +++ b/sinch/core/adapters/httpx_adapter.py @@ -0,0 +1,62 @@ +import httpx +from sinch.core.ports.http_transport import AsyncHTTPTransport, HttpRequest +from sinch.core.endpoint import HTTPEndpoint +from sinch.core.models.http_response import HTTPResponse + + +class HTTPXTransport(AsyncHTTPTransport): + def __init__(self, sinch): + super().__init__(sinch) + self.http_session = None + + async def request(self, endpoint: HTTPEndpoint) -> HTTPResponse: + request_data: HttpRequest = self.prepare_request(endpoint) + request_data: HttpRequest = await self.authenticate(endpoint, request_data) + + if not self.http_session: + self.http_session = httpx.AsyncClient() + + self.sinch.configuration.logger.debug( + f"Async HTTP {request_data.http_method} call with headers:" + f" {request_data.headers} and body: {request_data.request_body} to URL: {request_data.url}" + ) + + if isinstance(request_data.request_body, str): + response = await self.http_session.request( + method=request_data.http_method, + headers=request_data.headers, + url=request_data.url, + content=request_data.request_body, + auth=request_data.auth, + params=request_data.query_params, + timeout=self.sinch.configuration.connection_timeout + ) + else: + response = await self.http_session.request( + method=request_data.http_method, + headers=request_data.headers, + url=request_data.url, + data=request_data.request_body, + auth=request_data.auth, + params=request_data.query_params, + timeout=self.sinch.configuration.connection_timeout, + ) + + response_body = self.deserialize_json_response(response) + + self.sinch.configuration.logger.debug( + f"Async HTTP {response.status_code} response with headers: {response.headers}" + f"and body: {response_body} from URL: {request_data.url}" + ) + + return await self.handle_response( + endpoint=endpoint, + http_response=HTTPResponse( + status_code=response.status_code, + body=response_body, + headers=response.headers + ) + ) + + async def close_session(self): + await self.http_session.aclose() diff --git a/sinch/core/adapters/requests_http_transport.py b/sinch/core/adapters/requests_http_transport.py index 1a689d5a..7163bd34 100644 --- a/sinch/core/adapters/requests_http_transport.py +++ b/sinch/core/adapters/requests_http_transport.py @@ -1,5 +1,4 @@ import requests -import json from sinch.core.ports.http_transport import HTTPTransport, HttpRequest from sinch.core.endpoint import HTTPEndpoint from sinch.core.models.http_response import HTTPResponse @@ -29,9 +28,7 @@ def request(self, endpoint: HTTPEndpoint) -> HTTPResponse: params=request_data.query_params ) - response_body = response.content - if response_body: - response_body = json.loads(response_body) + response_body = self.deserialize_json_response(response) self.sinch.configuration.logger.debug( f"Sync HTTP {response.status_code} response with headers: {response.headers}" diff --git a/sinch/core/clients/sinch_client_async.py b/sinch/core/clients/sinch_client_async.py index 27519986..72f34593 100644 --- a/sinch/core/clients/sinch_client_async.py +++ b/sinch/core/clients/sinch_client_async.py @@ -2,7 +2,7 @@ from sinch.core.clients.sinch_client_base import SinchClientBase from sinch.core.clients.sinch_client_configuration import Configuration from sinch.core.token_manager import TokenManagerAsync -from sinch.core.adapters.asyncio_http_adapter import HTTPTransportAioHTTP +from sinch.core.adapters.httpx_adapter import HTTPXTransport from sinch.domains.authentication import AuthenticationAsync from sinch.domains.numbers import NumbersAsync from sinch.domains.conversation import ConversationAsync @@ -33,7 +33,7 @@ def __init__( project_id=project_id, logger_name=logger_name, logger=logger, - transport=HTTPTransportAioHTTP(self), + transport=HTTPXTransport(self), token_manager=TokenManagerAsync(self), application_secret=application_secret, application_key=application_key diff --git a/sinch/core/ports/http_transport.py b/sinch/core/ports/http_transport.py index 06ff0554..865ea719 100644 --- a/sinch/core/ports/http_transport.py +++ b/sinch/core/ports/http_transport.py @@ -5,7 +5,7 @@ from sinch.core.signature import Signature from sinch.core.models.http_request import HttpRequest from sinch.core.models.http_response import HTTPResponse -from sinch.core.exceptions import ValidationException +from sinch.core.exceptions import ValidationException, SinchException from sinch.core.enums import HTTPAuthentication from sinch.core.token_manager import TokenState from sinch import __version__ as sdk_version @@ -83,6 +83,25 @@ def prepare_request(self, endpoint: HTTPEndpoint) -> HttpRequest: auth=() ) + @staticmethod + def deserialize_json_response(response): + if response.content: + try: + response_body = response.json() + except ValueError as err: + raise SinchException( + message=( + "Error while parsing json response.", + err.msg + ), + is_from_server=True, + response=response + ) + else: + response_body = {} + + return response_body + def handle_response(self, endpoint: HTTPEndpoint, http_response: HTTPResponse): if http_response.status_code == 401 and endpoint.HTTP_AUTHENTICATION == HTTPAuthentication.OAUTH.value: self.sinch.configuration.token_manager.handle_invalid_token(http_response)