Skip to content

Commit df2398b

Browse files
authored
feat: Add error data to ApifyApiError (#314)
Add error data to ApifyApiError when available. Add tests. Closes: #306
1 parent 3610a6c commit df2398b

File tree

4 files changed

+97
-27
lines changed

4 files changed

+97
-27
lines changed

poetry.lock

Lines changed: 41 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ keywords = [
4141
[tool.poetry.dependencies]
4242
python = "^3.9"
4343
apify-shared = ">=1.1.2"
44-
httpx = ">=0.25.0"
44+
# TODO: relax the upper bound once the issue is resolved:
45+
# https://github.com/apify/apify-client-python/issues/313
46+
httpx = ">=0.25 <0.28.0"
4547
more_itertools = ">=10.0.0"
4648

4749
[tool.poetry.group.dev.dependencies]
@@ -58,6 +60,7 @@ pytest-only = "~2.1.0"
5860
pytest-timeout = "~2.3.0"
5961
pytest-xdist = "~3.6.0"
6062
redbaron = "~0.9.0"
63+
respx = "^0.21.1"
6164
ruff = "~0.8.0"
6265
setuptools = "~75.6.0" # setuptools are used by pytest but not explicitly required
6366

src/apify_client/_errors.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@ def __init__(self, response: httpx.Response, attempt: int) -> None:
2626
"""
2727
self.message: str | None = None
2828
self.type: str | None = None
29+
self.data = dict[str, str]()
2930

3031
self.message = f'Unexpected error: {response.text}'
3132
try:
3233
response_data = response.json()
3334
if 'error' in response_data:
3435
self.message = response_data['error']['message']
3536
self.type = response_data['error']['type']
37+
if 'data' in response_data['error']:
38+
self.data = response_data['error']['data']
3639
except ValueError:
3740
pass
3841

tests/unit/test_client_errors.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import json
2+
3+
import httpx
4+
import pytest
5+
import respx
6+
from respx import MockRouter
7+
8+
from apify_client._errors import ApifyApiError
9+
from apify_client._http_client import HTTPClient, HTTPClientAsync
10+
11+
_TEST_URL = 'http://example.com'
12+
_EXPECTED_MESSAGE = 'some_message'
13+
_EXPECTED_TYPE = 'some_type'
14+
_EXPECTED_DATA = {
15+
'invalidItems': {'0': ["should have required property 'name'"], '1': ["should have required property 'name'"]}
16+
}
17+
18+
19+
@respx.mock
20+
@pytest.fixture(autouse=True)
21+
def mocked_response(respx_mock: MockRouter) -> None:
22+
response_content = json.dumps(
23+
{'error': {'message': _EXPECTED_MESSAGE, 'type': _EXPECTED_TYPE, 'data': _EXPECTED_DATA}}
24+
)
25+
respx_mock.get(_TEST_URL).mock(return_value=httpx.Response(400, content=response_content))
26+
27+
28+
def test_client_apify_api_error_with_data() -> None:
29+
"""Test that client correctly throws ApifyApiError with error data from response."""
30+
client = HTTPClient()
31+
32+
with pytest.raises(ApifyApiError) as e:
33+
client.call(method='GET', url=_TEST_URL)
34+
35+
assert e.value.message == _EXPECTED_MESSAGE
36+
assert e.value.type == _EXPECTED_TYPE
37+
assert e.value.data == _EXPECTED_DATA
38+
39+
40+
async def test_async_client_apify_api_error_with_data() -> None:
41+
"""Test that async client correctly throws ApifyApiError with error data from response."""
42+
client = HTTPClientAsync()
43+
44+
with pytest.raises(ApifyApiError) as e:
45+
await client.call(method='GET', url=_TEST_URL)
46+
47+
assert e.value.message == _EXPECTED_MESSAGE
48+
assert e.value.type == _EXPECTED_TYPE
49+
assert e.value.data == _EXPECTED_DATA

0 commit comments

Comments
 (0)