Skip to content

Commit 75f7c31

Browse files
committed
MPT-14266 Add custom exceptions
1 parent 47eb5c4 commit 75f7c31

File tree

10 files changed

+62
-18
lines changed

10 files changed

+62
-18
lines changed

mpt_api_client/exceptions.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class MPTAPIError(Exception):
2+
"""Base class for all MPT API exceptions."""
3+
4+
5+
class MPTHTTPError(MPTAPIError):
6+
"""HTTP error."""
7+
8+
def __init__(self, status_code: int, content: str): # noqa: WPS110
9+
self.status_code = status_code
10+
self.content = content # noqa: WPS110
11+
super().__init__(f"{self.status_code} - {self.content}")

mpt_api_client/http/async_client.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import os
22

3-
from httpx import AsyncClient, AsyncHTTPTransport
3+
from httpx import AsyncClient, AsyncHTTPTransport, HTTPStatusError, Response
4+
5+
from mpt_api_client.exceptions import MPTHTTPError
46

57

68
class AsyncHTTPClient(AsyncClient):
@@ -40,3 +42,12 @@ def __init__(
4042
timeout=timeout,
4143
transport=AsyncHTTPTransport(retries=retries),
4244
)
45+
46+
def raise_for_status(self, response: Response) -> None:
47+
"""Raise an exception for the given response."""
48+
try:
49+
response.raise_for_status()
50+
except HTTPStatusError as ex:
51+
raise MPTHTTPError(
52+
status_code=ex.response.status_code, content=ex.response.text
53+
) from ex

mpt_api_client/http/async_service.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ async def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> ht
102102
"""
103103
pagination_params: dict[str, int] = {"limit": limit, "offset": offset}
104104
response = await self.http_client.get(self.build_url(pagination_params))
105-
response.raise_for_status()
105+
self.http_client.raise_for_status(response)
106106

107107
return response
108108

@@ -136,7 +136,7 @@ async def _resource_do_request( # noqa: WPS211
136136
response = await self.http_client.request(
137137
method, url, json=json, params=query_params, headers=headers
138138
)
139-
response.raise_for_status()
139+
self.http_client.raise_for_status(response)
140140
return response
141141

142142
async def _resource_action(

mpt_api_client/http/client.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import os
22

3-
from httpx import Client, HTTPTransport
3+
from httpx import Client, HTTPStatusError, HTTPTransport, Response
4+
5+
from mpt_api_client.exceptions import MPTHTTPError
46

57

68
class HTTPClient(Client):
@@ -40,3 +42,12 @@ def __init__(
4042
timeout=timeout,
4143
transport=HTTPTransport(retries=retries),
4244
)
45+
46+
def raise_for_status(self, response: Response) -> None:
47+
"""Raise an exception for the given response."""
48+
try:
49+
response.raise_for_status()
50+
except HTTPStatusError as ex:
51+
raise MPTHTTPError(
52+
status_code=ex.response.status_code, content=ex.response.text
53+
) from ex

mpt_api_client/http/mixins.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def create(self, resource_data: ResourceData) -> Model:
2323
New resource created.
2424
"""
2525
response = self.http_client.post(self.endpoint, json=resource_data) # type: ignore[attr-defined]
26-
response.raise_for_status()
26+
self.http_client.raise_for_status(response) # type: ignore[attr-defined]
2727

2828
return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]
2929

@@ -38,7 +38,7 @@ def delete(self, resource_id: str) -> None:
3838
resource_id: Resource ID.
3939
"""
4040
response = self._resource_do_request(resource_id, "DELETE") # type: ignore[attr-defined]
41-
response.raise_for_status()
41+
self.http_client.raise_for_status(response) # type: ignore[attr-defined]
4242

4343

4444
class UpdateMixin[Model]:
@@ -87,7 +87,7 @@ def create(
8787
)
8888

8989
response = self.http_client.post(self.endpoint, files=files) # type: ignore[attr-defined]
90-
response.raise_for_status()
90+
self.http_client.raise_for_status(response) # type: ignore[attr-defined]
9191
return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]
9292

9393
def download(self, resource_id: str) -> FileModel:
@@ -115,7 +115,7 @@ async def create(self, resource_data: ResourceData) -> Model:
115115
New resource created.
116116
"""
117117
response = await self.http_client.post(self.endpoint, json=resource_data) # type: ignore[attr-defined]
118-
response.raise_for_status()
118+
self.http_client.raise_for_status(response) # type: ignore[attr-defined]
119119

120120
return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]
121121

@@ -131,7 +131,7 @@ async def delete(self, resource_id: str) -> None:
131131
"""
132132
url = urljoin(f"{self.endpoint}/", resource_id) # type: ignore[attr-defined]
133133
response = await self.http_client.delete(url) # type: ignore[attr-defined]
134-
response.raise_for_status()
134+
self.http_client.raise_for_status(response) # type: ignore[attr-defined]
135135

136136

137137
class AsyncUpdateMixin[Model]:
@@ -180,7 +180,7 @@ async def create(
180180
)
181181

182182
response = await self.http_client.post(self.endpoint, files=files) # type: ignore[attr-defined]
183-
response.raise_for_status()
183+
self.http_client.raise_for_status(response) # type: ignore[attr-defined]
184184
return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]
185185

186186
async def download(self, resource_id: str) -> FileModel:

mpt_api_client/http/service.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def _fetch_page_as_response(self, limit: int = 100, offset: int = 0) -> httpx.Re
102102
"""
103103
pagination_params: dict[str, int] = {"limit": limit, "offset": offset}
104104
response = self.http_client.get(self.build_url(pagination_params))
105-
response.raise_for_status()
105+
self.http_client.raise_for_status(response)
106106

107107
return response
108108

@@ -136,7 +136,7 @@ def _resource_do_request( # noqa: WPS211
136136
response = self.http_client.request(
137137
method, url, json=json, params=query_params, headers=headers
138138
)
139-
response.raise_for_status()
139+
self.http_client.raise_for_status(response)
140140
return response
141141

142142
def _resource_action(
@@ -164,4 +164,5 @@ def _resource_action(
164164
query_params=query_params,
165165
headers={"Accept": "application/json"},
166166
)
167+
self.http_client.raise_for_status(response)
167168
return self._model_class.from_response(response)

mpt_api_client/resources/notifications/accounts.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from typing import override
22

3+
from mpt_api_client.exceptions import MPTAPIError
34
from mpt_api_client.http import AsyncService, Service
45
from mpt_api_client.models import Model
56

67

7-
class MethodNotAllowedError(Exception):
8+
class MethodNotAllowedError(MPTAPIError):
89
"""Method not allowed error."""
910

1011

mpt_api_client/resources/notifications/batches.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def create(
4949
)
5050

5151
response = self.http_client.post(self.endpoint, files=files)
52-
response.raise_for_status()
52+
self.http_client.raise_for_status(response)
5353
return self._model_class.from_response(response)
5454

5555
def get_batch_attachment(self, batch_id: str, attachment_id: str) -> FileModel:
@@ -63,7 +63,7 @@ def get_batch_attachment(self, batch_id: str, attachment_id: str) -> FileModel:
6363
FileModel containing the attachment.
6464
"""
6565
response = self.http_client.get(f"{self.endpoint}/{batch_id}/attachments/{attachment_id}")
66-
response.raise_for_status()
66+
self.http_client.raise_for_status(response)
6767
return FileModel(response)
6868

6969

@@ -99,7 +99,7 @@ async def create(
9999
)
100100

101101
response = await self.http_client.post(self.endpoint, files=files)
102-
response.raise_for_status()
102+
self.http_client.raise_for_status(response)
103103
return self._model_class.from_response(response)
104104

105105
async def get_batch_attachment(self, batch_id: str, attachment_id: str) -> FileModel:
@@ -115,5 +115,5 @@ async def get_batch_attachment(self, batch_id: str, attachment_id: str) -> FileM
115115
response = await self.http_client.get(
116116
f"{self.endpoint}/{batch_id}/attachments/{attachment_id}"
117117
)
118-
response.raise_for_status()
118+
self.http_client.raise_for_status(response)
119119
return FileModel(response)

tests/http/test_service.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pytest
33
import respx
44

5+
from mpt_api_client.exceptions import MPTHTTPError
56
from tests.conftest import DummyModel
67
from tests.http.conftest import DummyService
78

@@ -263,5 +264,5 @@ def test_sync_iterate_handles_api_errors(dummy_service):
263264

264265
iterator = dummy_service.iterate()
265266

266-
with pytest.raises(httpx.HTTPStatusError):
267+
with pytest.raises(MPTHTTPError):
267268
list(iterator)

tests/test_exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from mpt_api_client.exceptions import MPTHTTPError
2+
3+
4+
def test_http_error():
5+
exception = MPTHTTPError(status_code=400, content="Content")
6+
7+
assert exception.status_code == 400
8+
assert exception.content == "Content"

0 commit comments

Comments
 (0)