Skip to content

Commit 162012f

Browse files
committed
feat(http): add max retry error handling in http client
1 parent 57e3e07 commit 162012f

5 files changed

Lines changed: 23 additions & 9 deletions

File tree

mpt_api_client/exceptions.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ def __init__(self, status_code: int, message: str, body: str):
1717
super().__init__(f"HTTP {status_code}: {message}")
1818

1919

20+
class MPTMaxRetryError(MPTError):
21+
"""Represents an error when maximum retry attempts are exceeded."""
22+
23+
def __init__(self, message: str, attempts: int):
24+
super().__init__(f"{message} error after {attempts} retry attempts.")
25+
26+
2027
class MPTAPIError(MPTHttpError):
2128
"""Represents an API error."""
2229

mpt_api_client/http/async_client.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import os
22
from typing import Any
33

4-
from httpx import AsyncClient, HTTPError, HTTPStatusError
4+
from httpx import AsyncClient, HTTPError, HTTPStatusError, RequestError
55
from httpx_retries import Retry, RetryTransport
66

77
from mpt_api_client.constants import APPLICATION_JSON
8-
from mpt_api_client.exceptions import MPTError, transform_http_status_exception
8+
from mpt_api_client.exceptions import MPTError, MPTMaxRetryError, transform_http_status_exception
99
from mpt_api_client.http.client import json_to_file_payload
1010
from mpt_api_client.http.client_utils import get_query_params, validate_base_url
1111
from mpt_api_client.http.query_options import QueryOptions
@@ -23,6 +23,7 @@ def __init__(
2323
timeout: float = 20.0,
2424
retries: int = 5,
2525
):
26+
self._retries = retries
2627
retry = Retry(total=retries)
2728
transport = RetryTransport(retry=retry)
2829

@@ -47,7 +48,7 @@ def __init__(
4748
follow_redirects=True,
4849
)
4950

50-
async def request( # noqa: WPS211
51+
async def request( # noqa: WPS211 # NOSONAR
5152
self,
5253
method: str,
5354
url: str,
@@ -80,6 +81,7 @@ async def request( # noqa: WPS211
8081
MPTError: If the request fails.
8182
MPTApiError: If the response contains an error.
8283
MPTHttpError: If the response contains an HTTP error.
84+
MPTMaxRetryError: If the request fails after maximum retry attempts.
8385
"""
8486
files = dict(files or {})
8587
if force_multipart or (files and json):
@@ -95,6 +97,8 @@ async def request( # noqa: WPS211
9597
params=params_str or None,
9698
headers=headers,
9799
)
100+
except RequestError as err:
101+
raise MPTMaxRetryError(str(err), self._retries + 1) from err
98102
except HTTPError as err:
99103
raise MPTError(f"HTTP Error: {err}") from err
100104

mpt_api_client/http/client.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
import os
33
from typing import Any
44

5-
from httpx import Client, HTTPError, HTTPStatusError
5+
from httpx import Client, HTTPError, HTTPStatusError, RequestError
66
from httpx_retries import Retry, RetryTransport
77

88
from mpt_api_client.constants import APPLICATION_JSON
9-
from mpt_api_client.exceptions import MPTError, transform_http_status_exception
9+
from mpt_api_client.exceptions import MPTError, MPTMaxRetryError, transform_http_status_exception
1010
from mpt_api_client.http.client_utils import get_query_params, validate_base_url
1111
from mpt_api_client.http.query_options import QueryOptions
1212
from mpt_api_client.http.types import HeaderTypes, QueryParam, RequestFiles, Response
@@ -33,7 +33,8 @@ def __init__(
3333
timeout: float = 20.0,
3434
retries: int = 5,
3535
):
36-
retry = Retry(total=retries)
36+
self._retries = retries
37+
retry = Retry(total=self._retries)
3738
transport = RetryTransport(retry=retry)
3839

3940
api_token = api_token or os.getenv("MPT_API_TOKEN")
@@ -105,6 +106,8 @@ def request( # noqa: WPS211
105106
params=params_str or None,
106107
headers=headers,
107108
)
109+
except RequestError as err:
110+
raise MPTMaxRetryError(str(err), self._retries + 1) from err
108111
except HTTPError as err:
109112
raise MPTError(f"HTTP Error: {err}") from err
110113

tests/unit/http/test_async_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ async def test_async_http_call_success(async_http_client, mock_response):
7979
async def test_async_http_call_failure(async_http_client):
8080
timeout_route = respx.get(f"{API_URL}/timeout").mock(side_effect=ConnectTimeout("Mock Timeout"))
8181

82-
with pytest.raises(MPTError, match="HTTP Error: Mock Timeout"):
82+
with pytest.raises(MPTError, match=r"Mock Timeout error after 6 retry attempts."):
8383
await async_http_client.request("GET", "/timeout")
8484

8585
assert timeout_route.called

tests/unit/http/test_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import respx
66
from httpx import ConnectTimeout, Response, codes
77

8-
from mpt_api_client.exceptions import MPTError
8+
from mpt_api_client.exceptions import MPTMaxRetryError
99
from mpt_api_client.http.client import HTTPClient
1010
from mpt_api_client.http.query_options import QueryOptions
1111
from tests.unit.conftest import API_TOKEN, API_URL
@@ -71,7 +71,7 @@ def test_http_call_success(http_client):
7171
def test_http_call_failure(http_client):
7272
timeout_route = respx.get(f"{API_URL}/timeout").mock(side_effect=ConnectTimeout("Mock Timeout"))
7373

74-
with pytest.raises(MPTError, match="HTTP Error: Mock Timeout"):
74+
with pytest.raises(MPTMaxRetryError, match=r"Mock Timeout error after 6 retry attempts."):
7575
http_client.request("GET", "/timeout")
7676

7777
assert timeout_route.called

0 commit comments

Comments
 (0)