Skip to content

Commit c2f1c81

Browse files
authored
Change: Allow RemoteProtocolError to trigger retry (#1238)
* Change: Allow RemoteProtocolError to trigger retry * Add: Unit tests * Change: Variable name
1 parent 6934c3f commit c2f1c81

2 files changed

Lines changed: 66 additions & 8 deletions

File tree

pontos/nvd/api.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@
2323
TypeVar,
2424
)
2525

26-
from httpx import URL, AsyncClient, Response, Timeout
26+
from httpx import (
27+
URL,
28+
AsyncClient,
29+
RemoteProtocolError,
30+
Response,
31+
Timeout,
32+
)
2733

2834
from pontos.errors import PontosError
2935
from pontos.helper import snake_case
@@ -450,19 +456,34 @@ async def _get(
450456
"""
451457
headers = self._request_headers()
452458

459+
latest_error: Response | RemoteProtocolError
460+
453461
for attempt in range(self._request_attempts):
454462
if attempt > 0:
455463
delay = RETRY_DELAY**attempt
456464
await asyncio.sleep(delay)
457465

458466
await self._consider_rate_limit()
459-
response = await self._client.get(
460-
self._url, headers=headers, params=params
461-
)
462-
if not response.is_server_error:
463-
break
464467

465-
return response
468+
try:
469+
response = await self._client.get(
470+
self._url, headers=headers, params=params
471+
)
472+
473+
if response.is_server_error:
474+
latest_error = response
475+
continue
476+
477+
return response
478+
479+
except RemoteProtocolError as e:
480+
latest_error = e
481+
continue
482+
483+
if isinstance(latest_error, RemoteProtocolError):
484+
raise latest_error
485+
486+
return latest_error
466487

467488
async def __aenter__(self) -> "NVDApi":
468489
# reset rate limit counter

tests/nvd/test_api.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from typing import Any
1212
from unittest.mock import AsyncMock, MagicMock, call, patch
1313

14-
from httpx import AsyncClient, Response
14+
from httpx import AsyncClient, RemoteProtocolError, Response
1515

1616
from pontos.nvd.api import (
1717
JSON,
@@ -206,6 +206,43 @@ async def test_no_retry(
206206
sleep_mock.assert_not_called()
207207
self.assertFalse(result.is_server_error)
208208

209+
@patch("pontos.nvd.api.asyncio.sleep", autospec=True)
210+
@patch("pontos.nvd.api.AsyncClient", spec=AsyncClient)
211+
async def test_exceed_attempts(
212+
self,
213+
async_client: MagicMock,
214+
sleep_mock: MagicMock,
215+
):
216+
response_mocks = [
217+
RemoteProtocolError("RIP connection"),
218+
MagicMock(spec=Response, is_server_error=True),
219+
]
220+
http_client = AsyncMock()
221+
http_client.get.side_effect = response_mocks
222+
async_client.return_value = http_client
223+
224+
api = NVDApi("https://foo.bar/baz", request_attempts=2)
225+
226+
result = await api._get()
227+
228+
calls = [call(2.0)]
229+
sleep_mock.assert_has_calls(calls)
230+
self.assertIsInstance(result, Response)
231+
232+
@patch("pontos.nvd.api.AsyncClient", spec=AsyncClient)
233+
async def test_remote_protocol_error(
234+
self,
235+
async_client: MagicMock,
236+
):
237+
http_client = AsyncMock()
238+
http_client.get.side_effect = RemoteProtocolError("RIP connection")
239+
async_client.return_value = http_client
240+
241+
api = NVDApi("https://foo.bar/baz")
242+
243+
with self.assertRaises(RemoteProtocolError):
244+
await api._get()
245+
209246

210247
class Result:
211248
def __init__(self, value: int) -> None:

0 commit comments

Comments
 (0)