Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add return-value/exception based backoff time #124

Closed
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
3 changes: 2 additions & 1 deletion backoff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
"""
from backoff._decorator import on_predicate, on_exception
from backoff._jitter import full_jitter, random_jitter
from backoff._wait_gen import constant, expo, fibo
from backoff._wait_gen import constant, expo, fibo, from_value

__all__ = [
'on_predicate',
'on_exception',
'constant',
'expo',
'fibo',
'from_value',
'full_jitter',
'random_jitter'
]
Expand Down
4 changes: 2 additions & 2 deletions backoff/_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async def retry(*args, **kwargs):
break

try:
seconds = _next_wait(wait, jitter, elapsed, max_time_)
seconds = _next_wait(wait, ret, jitter, elapsed, max_time_)
except StopIteration:
await _call_handlers(on_giveup, *details, value=ret)
break
Expand Down Expand Up @@ -142,7 +142,7 @@ async def retry(*args, **kwargs):
raise

try:
seconds = _next_wait(wait, jitter, elapsed, max_time_)
seconds = _next_wait(wait, e, jitter, elapsed, max_time_)
except StopIteration:
await _call_handlers(on_giveup, *details)
raise e
Expand Down
12 changes: 6 additions & 6 deletions backoff/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@
import traceback
import warnings


# python 2.7 -> 3.x compatibility for str and unicode
try:
basestring
except NameError: # pragma: python=3.5
basestring = str


# Use module-specific logger with a default null handler.
_logger = logging.getLogger('backoff')
_logger.addHandler(logging.NullHandler()) # pragma: no cover
Expand All @@ -27,11 +25,13 @@ def _maybe_call(f, *args, **kwargs):

def _init_wait_gen(wait_gen, wait_gen_kwargs):
kwargs = {k: _maybe_call(v) for k, v in wait_gen_kwargs.items()}
return wait_gen(**kwargs)
initialized = wait_gen(**kwargs)
initialized.send(None) # Initialize with an empty send
return initialized


def _next_wait(wait, jitter, elapsed, max_time):
value = next(wait)
def _next_wait(wait, send_value, jitter, elapsed, max_time):
value = wait.send(send_value)
try:
if jitter is not None:
seconds = jitter(value)
Expand Down Expand Up @@ -64,7 +64,7 @@ def _prepare_logger(logger):
# Configure handler list with user specified handler and optionally
# with a default handler bound to the specified logger.
def _config_handlers(
user_handlers, default_handler=None, logger=None, log_level=None
user_handlers, default_handler=None, logger=None, log_level=None
):
handlers = []
if logger is not None:
Expand Down
4 changes: 2 additions & 2 deletions backoff/_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def retry(*args, **kwargs):
break

try:
seconds = _next_wait(wait, jitter, elapsed, max_time_)
seconds = _next_wait(wait, ret, jitter, elapsed, max_time_)
except StopIteration:
_call_handlers(on_giveup, *details)
break
Expand Down Expand Up @@ -102,7 +102,7 @@ def retry(*args, **kwargs):
raise

try:
seconds = _next_wait(wait, jitter, elapsed, max_time_)
seconds = _next_wait(wait, e, jitter, elapsed, max_time_)
except StopIteration:
_call_handlers(on_giveup, *details)
raise e
Expand Down
17 changes: 17 additions & 0 deletions backoff/_wait_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@
import itertools


def from_value(parser):
""" Generator that is based on parsing the return value or thrown
exception of the decorated method

Args:
parser: a callable which takes as input the decorated
function's return value or thrown exception and
determines how long to wait
"""
value = yield
while True:
value = yield parser(value)


def expo(base=2, factor=1, max_value=None):
"""Generator for exponential decay.

Expand All @@ -13,6 +27,7 @@ def expo(base=2, factor=1, max_value=None):
true exponential sequence exceeds this, the value
of max_value will forever after be yielded.
"""
yield # Advance past initial .send() call
n = 0
while True:
a = factor * base ** n
Expand All @@ -31,6 +46,7 @@ def fibo(max_value=None):
true fibonacci sequence exceeds this, the value
of max_value will forever after be yielded.
"""
yield # Advance past initial .send() call
a = 1
b = 1
while True:
Expand All @@ -47,6 +63,7 @@ def constant(interval=1):
Args:
interval: A constant value to yield or an iterable of such values.
"""
yield # Advance past initial .send() call
try:
itr = iter(interval)
except TypeError:
Expand Down
25 changes: 20 additions & 5 deletions tests/test_wait_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,65 @@

def test_expo():
gen = backoff.expo()
gen.send(None)
for i in range(9):
assert 2**i == next(gen)
assert 2 ** i == next(gen)


def test_expo_base3():
gen = backoff.expo(base=3)
gen.send(None)
for i in range(9):
assert 3**i == next(gen)
assert 3 ** i == next(gen)


def test_expo_factor3():
gen = backoff.expo(factor=3)
gen.send(None)
for i in range(9):
assert 3 * 2**i == next(gen)
assert 3 * 2 ** i == next(gen)


def test_expo_base3_factor5():
gen = backoff.expo(base=3, factor=5)
gen.send(None)
for i in range(9):
assert 5 * 3**i == next(gen)
assert 5 * 3 ** i == next(gen)


def test_expo_max_value():
gen = backoff.expo(max_value=2**4)
gen = backoff.expo(max_value=2 ** 4)
gen.send(None)
expected = [1, 2, 4, 8, 16, 16, 16]
for expect in expected:
assert expect == next(gen)


def test_fibo():
gen = backoff.fibo()
gen.send(None)
expected = [1, 1, 2, 3, 5, 8, 13]
for expect in expected:
assert expect == next(gen)


def test_fibo_max_value():
gen = backoff.fibo(max_value=8)
gen.send(None)
expected = [1, 1, 2, 3, 5, 8, 8, 8]
for expect in expected:
assert expect == next(gen)


def test_constant():
gen = backoff.constant(interval=3)
gen.send(None)
for i in range(9):
assert 3 == next(gen)


def test_from_value():
gen = backoff.from_value(lambda x: x)
gen.send(None)
for i in range(20):
assert i == gen.send(i)