Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## UNRELEASED

### Changed

- Removed the dependency on the library `retry` (and the indirect dependency on
`py`), and replaced it with `tenacity`. This should be transparent.

## v1.1.4 (2024-02-01)

### Fixed
Expand Down
58 changes: 58 additions & 0 deletions ravenpackapi/tests/unit/upload/test_upload_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import pytest

from ravenpackapi.exceptions import APIException, APIException425
from ravenpackapi.upload.upload_utils import retry_on_too_early


class TestRetryOnTooEarly:
def test_fails_if_not_enough_retries(self):
eventually_success = EventuallySuccess(num_calls_to_fail=10)
with pytest.raises(APIException425):
retry_on_too_early(eventually_success, 1, 2, 3, namearg="namevalue")
assert eventually_success.num_calls == 10
assert eventually_success.calls == [((1, 2, 3), {"namearg": "namevalue"})] * 10
3
min_wait, max_wait = 3 * 2 ** 9, 3 * 2 ** 10
assert min_wait < self.seconds_slept <= max_wait

def test_succeeds_eventually(self):
eventually_success = EventuallySuccess(num_calls_to_fail=9)
retry_on_too_early(eventually_success, 1, 2, 3, namearg="namevalue")
assert eventually_success.num_calls == 10
assert eventually_success.calls == [((1, 2, 3), {"namearg": "namevalue"})] * 10

def test_only_handles_api_exception_425(self):
eventually_success = EventuallySuccess(
num_calls_to_fail=100, error_to_raise=APIException
)
with pytest.raises(APIException):
retry_on_too_early(eventually_success, 1, 2, 3, namearg="namevalue")
assert eventually_success.num_calls == 1
assert eventually_success.calls == [((1, 2, 3), {"namearg": "namevalue"})]
assert self.seconds_slept == 0

@pytest.fixture(autouse=True)
def mocked_sleep(self, mocker):
self.seconds_slept = 0

def sleep(seconds):
self.seconds_slept += seconds

time = mocker.patch("tenacity.nap.time")
time.sleep.side_effect = sleep
return time.sleep


class EventuallySuccess:
def __init__(self, num_calls_to_fail=0, error_to_raise=APIException425):
self.num_calls_to_fail = num_calls_to_fail
self.num_calls = 0
self.error_to_raise = error_to_raise
self.calls = []

def __call__(self, *args, **kwargs):
self.calls.append((args, kwargs))
self.num_calls += 1
if self.num_calls <= self.num_calls_to_fail:
raise self.error_to_raise
return self.num_calls
33 changes: 20 additions & 13 deletions ravenpackapi/upload/upload_utils.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
import logging

from retry.api import retry_call
from tenacity import (
retry,
retry_if_exception_type,
stop_after_attempt,
wait_exponential_jitter,
)

from ravenpackapi.exceptions import APIException425

logger = logging.getLogger("ravenpack.upload.retry")


def retry_on_too_early(func, *args, **kwargs):
response = retry_call(
func,
fargs=args,
fkwargs=kwargs,
exceptions=(APIException425,),
delay=3,
backoff=2,
jitter=(1, 5),
tries=10,
logger=logger,
)
return response
"""
Retry a function that may raise a 425 API exception.

Note: This was originally implemented using the library "retry" and migrated
to tenacity. It should not be used in new code, as the proper way to do this
using tenacity is to decorate the funciton with @retry.
"""
wrapped_func = retry(
retry=retry_if_exception_type(APIException425),
wait=wait_exponential_jitter(initial=3, exp_base=2, jitter=5),
stop=stop_after_attempt(10),
reraise=True,
)(func)
return wrapped_func(*args, **kwargs)
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
requests[security]
six
python-dateutil
retry
tenacity
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@
"License :: OSI Approved :: MIT License",
],
keywords="python analytics api rest news data",
install_requires=["requests[security]", "python-dateutil", "six", "retry"],
install_requires=["requests[security]", "python-dateutil", "six", "tenacity"],
)