Skip to content

Commit

Permalink
Move backward-compatibility-related utils to tenacity.compat
Browse files Browse the repository at this point in the history
  • Loading branch information
immerrr committed Jul 23, 2018
1 parent b478a85 commit 52e0181
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 132 deletions.
8 changes: 3 additions & 5 deletions tenacity/__init__.py
Expand Up @@ -34,8 +34,7 @@
import six

from tenacity import _utils
from tenacity import before_sleep as _before_sleep
from tenacity import wait as _wait
from tenacity import compat as _compat

# Import all built-in retry strategies for easier usage.
from .retry import retry_all # noqa
Expand Down Expand Up @@ -202,12 +201,11 @@ def __init__(self,

@_utils.cached_property
def wait(self):
return _wait._wait_func_accept_retry_state(self._wait)
return _compat.wait_func_accept_retry_state(self._wait)

@_utils.cached_property
def before_sleep(self):
return _before_sleep._before_sleep_func_accept_retry_state(
self._before_sleep)
return _compat.before_sleep_func_accept_retry_state(self._before_sleep)

def copy(self, sleep=_unset, stop=_unset, wait=_unset,
retry=_unset, before=_unset, after=_unset, before_sleep=_unset,
Expand Down
10 changes: 0 additions & 10 deletions tenacity/_utils.py
Expand Up @@ -152,13 +152,3 @@ def __get__(self, obj, cls):
return self
value = obj.__dict__[self.func.__name__] = self.func(obj)
return value


def _func_takes_retry_state(func):
if not six.callable(func):
return False
if not inspect.isfunction(func):
# func is a callable object rather than a function
func = func.__call__
func_spec = getargspec(func)
return 'retry_state' in func_spec.args
19 changes: 0 additions & 19 deletions tenacity/before_sleep.py
Expand Up @@ -14,8 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import six

from tenacity import _utils


Expand All @@ -37,20 +35,3 @@ def log_it(retry_state):
getattr(retry_state.next_action, 'sleep'),
verb, value)
return log_it


def _before_sleep_func_accept_retry_state(fn):
if not six.callable(fn):
return fn

takes_retry_state = _utils._func_takes_retry_state(fn)
if takes_retry_state:
return fn

@six.wraps(fn)
def wrapped_before_sleep_func(retry_state):
return fn(
retry_state.retry_object,
sleep=getattr(retry_state.next_action, 'sleep'),
last_result=retry_state.outcome)
return wrapped_before_sleep_func
131 changes: 131 additions & 0 deletions tenacity/compat.py
@@ -0,0 +1,131 @@
"""Utilities for providing backward compatibility."""

import six
from tenacity import _utils
import inspect

from fractions import Fraction


def func_takes_retry_state(func):
if not six.callable(func):
return False
if not inspect.isfunction(func):
# func is a callable object rather than a function
func = func.__call__
func_spec = _utils.getargspec(func)
return 'retry_state' in func_spec.args


_unset = object()


def make_wait_retry_state(previous_attempt_number, delay_since_first_attempt,
last_result=None):
required_parameter_unset = (previous_attempt_number is _unset or
delay_since_first_attempt is _unset)
if required_parameter_unset:
missing = []
if previous_attempt_number is _unset:
missing.append('previous_attempt_number')
if delay_since_first_attempt is _unset:
missing.append('delay_since_first_attempt')
missing_str = ', '.join(repr(s) for s in missing)
raise TypeError('wait func missing parameters: ' + missing_str)

from tenacity import RetryCallState
retry_state = RetryCallState(None, None, (), {})
retry_state.attempt_number = previous_attempt_number
if last_result is not None:
retry_state.outcome = last_result
else:
retry_state.set_result(None)
# Ensure outcome_timestamp - start_time is *exactly* equal to the delay to
# avoid complexity in test code.
retry_state.start_time = Fraction(retry_state.start_time)
retry_state.outcome_timestamp = (
retry_state.start_time + Fraction(delay_since_first_attempt))
assert retry_state.seconds_since_start == delay_since_first_attempt
return retry_state


def wait_dunder_call_accept_old_params(fn):
"""Wrap wait fn taking "retry_state" to accept old parameter tuple.
This is a backward compatibility shim to ensure tests keep working.
"""
@six.wraps(fn)
def new_fn(self,
previous_attempt_number=_unset,
delay_since_first_attempt=_unset,
last_result=None,
retry_state=None):
if retry_state is None:
retry_state = make_wait_retry_state(
previous_attempt_number=previous_attempt_number,
delay_since_first_attempt=delay_since_first_attempt,
last_result=last_result)
return fn(self, retry_state=retry_state)
return new_fn


def func_takes_last_result(waiter):
"""Check if function has a "last_result" parameter.
Needed to provide backward compatibility for wait functions that didn't
take "last_result" in the beginning.
"""
if not six.callable(waiter):
return False
if not inspect.isfunction(waiter):
# waiter is a class, check dunder-call rather than dunder-init.
waiter = waiter.__call__
waiter_spec = _utils.getargspec(waiter)
return 'last_result' in waiter_spec.args


def wait_func_accept_retry_state(wait_func):
"""Wrap wait function to accept "retry_state" parameter."""
if not six.callable(wait_func):
return wait_func

takes_retry_state = func_takes_retry_state(wait_func)
if takes_retry_state:
return wait_func

takes_last_result = func_takes_last_result(wait_func)
if takes_last_result:
@six.wraps(wait_func)
def wrapped_wait_func(retry_state):
return wait_func(
retry_state.attempt_number,
retry_state.seconds_since_start,
last_result=retry_state.outcome,
)
else:
@six.wraps(wait_func)
def wrapped_wait_func(retry_state):
return wait_func(
retry_state.attempt_number,
retry_state.seconds_since_start,
)
return wrapped_wait_func


def before_sleep_func_accept_retry_state(fn):
"""Wrap "before_sleep" function to accept "retry_state"."""
if not six.callable(fn):
return fn

takes_retry_state = func_takes_retry_state(fn)
if takes_retry_state:
return fn

@six.wraps(fn)
def wrapped_before_sleep_func(retry_state):
# retry_object, sleep, last_result
return fn(
retry_state.retry_object,
sleep=getattr(retry_state.next_action, 'sleep'),
last_result=retry_state.outcome)
return wrapped_before_sleep_func
10 changes: 5 additions & 5 deletions tenacity/tests/test_tenacity.py
Expand Up @@ -23,7 +23,7 @@
from tenacity import RetryError
from tenacity import Retrying
from tenacity import retry
from tenacity.wait import _make_wait_retry_state
from tenacity.compat import make_wait_retry_state


class TestBase(unittest.TestCase):
Expand Down Expand Up @@ -197,9 +197,9 @@ def dying():

def test_wait_func(self):
r = Retrying(wait=lambda attempt, delay: attempt * delay)
self.assertEqual(r.wait(_make_wait_retry_state(1, 5)), 5)
self.assertEqual(r.wait(_make_wait_retry_state(2, 11)), 22)
self.assertEqual(r.wait(_make_wait_retry_state(10, 100)), 1000)
self.assertEqual(r.wait(make_wait_retry_state(1, 5)), 5)
self.assertEqual(r.wait(make_wait_retry_state(2, 11)), 22)
self.assertEqual(r.wait(make_wait_retry_state(10, 100)), 1000)

def test_wait_combine(self):
r = Retrying(wait=tenacity.wait_combine(tenacity.wait_random(0, 3),
Expand Down Expand Up @@ -370,7 +370,7 @@ def test_wait_class_backward_compatibility(self):
self.assertEqual(waitobj(1, 0.1), 5)
self.assertEqual(
waitobj(1, 0.1, tenacity.Future.construct(1, 1, False)), 5)
retry_state = _make_wait_retry_state(123, 456)
retry_state = make_wait_retry_state(123, 456)
self.assertEqual(retry_state.attempt_number, 123)
self.assertEqual(retry_state.seconds_since_start, 456)
self.assertEqual(waitobj(retry_state=retry_state), 5)
Expand Down
103 changes: 10 additions & 93 deletions tenacity/wait.py
Expand Up @@ -21,54 +21,7 @@
import six

from tenacity import _utils


_unset = object()


def _make_wait_retry_state(previous_attempt_number, delay_since_first_attempt,
last_result=None):
required_parameter_unset = (previous_attempt_number is _unset or
delay_since_first_attempt is _unset)
if required_parameter_unset:
missing = []
if previous_attempt_number is _unset:
missing.append('previous_attempt_number')
if delay_since_first_attempt is _unset:
missing.append('delay_since_first_attempt')
missing_str = ', '.join(repr(s) for s in missing)
raise TypeError('wait func missing parameters: ' + missing_str)

from tenacity import RetryCallState
retry_state = RetryCallState(None, None, (), {})
retry_state.attempt_number = previous_attempt_number
if last_result is not None:
retry_state.outcome = last_result
else:
retry_state.set_result(None)
# Ensure outcome_timestamp - start_time is *exactly* equal to the delay to
# avoid complexity in test code.
retry_state.start_time = Fraction(retry_state.start_time)
retry_state.outcome_timestamp = (
retry_state.start_time + Fraction(delay_since_first_attempt))
assert retry_state.seconds_since_start == delay_since_first_attempt
return retry_state


def _wait_dunder_call_accept_old_params(fn):
@six.wraps(fn)
def new_fn(self,
previous_attempt_number=_unset,
delay_since_first_attempt=_unset,
last_result=None,
retry_state=None):
if retry_state is None:
retry_state = _make_wait_retry_state(
previous_attempt_number=previous_attempt_number,
delay_since_first_attempt=delay_since_first_attempt,
last_result=last_result)
return fn(self, retry_state=retry_state)
return new_fn
from tenacity import compat as _compat


@six.add_metaclass(abc.ABCMeta)
Expand All @@ -95,7 +48,7 @@ class wait_fixed(wait_base):
def __init__(self, wait):
self.wait_fixed = wait

@_wait_dunder_call_accept_old_params
@_compat.wait_dunder_call_accept_old_params
def __call__(self, retry_state):
return self.wait_fixed

Expand All @@ -114,7 +67,7 @@ def __init__(self, min=0, max=1): # noqa
self.wait_random_min = min
self.wait_random_max = max

@_wait_dunder_call_accept_old_params
@_compat.wait_dunder_call_accept_old_params
def __call__(self, retry_state):
return (self.wait_random_min +
(random.random() *
Expand All @@ -125,10 +78,10 @@ class wait_combine(wait_base):
"""Combine several waiting strategies."""

def __init__(self, *strategies):
self.wait_funcs = tuple(_wait_func_accept_retry_state(strategy)
self.wait_funcs = tuple(_compat.wait_func_accept_retry_state(strategy)
for strategy in strategies)

@_wait_dunder_call_accept_old_params
@_compat.wait_dunder_call_accept_old_params
def __call__(self, retry_state):
return sum(x(retry_state=retry_state) for x in self.wait_funcs)

Expand All @@ -150,10 +103,10 @@ def wait_chained():
"""

def __init__(self, *strategies):
self.strategies = [_wait_func_accept_retry_state(strategy)
self.strategies = [_compat.wait_func_accept_retry_state(strategy)
for strategy in strategies]

@_wait_dunder_call_accept_old_params
@_compat.wait_dunder_call_accept_old_params
def __call__(self, retry_state):
wait_func_no = min(max(retry_state.attempt_number, 1),
len(self.strategies))
Expand All @@ -173,7 +126,7 @@ def __init__(self, start=0, increment=100, max=_utils.MAX_WAIT): # noqa
self.increment = increment
self.max = max

@_wait_dunder_call_accept_old_params
@_compat.wait_dunder_call_accept_old_params
def __call__(self, retry_state):
result = self.start + (
self.increment * (retry_state.attempt_number - 1)
Expand All @@ -199,7 +152,7 @@ def __init__(self, multiplier=1, max=_utils.MAX_WAIT, exp_base=2): # noqa
self.max = max
self.exp_base = exp_base

@_wait_dunder_call_accept_old_params
@_compat.wait_dunder_call_accept_old_params
def __call__(self, retry_state):
try:
exp = self.exp_base ** retry_state.attempt_number
Expand Down Expand Up @@ -234,44 +187,8 @@ class wait_random_exponential(wait_exponential):
wait_exponential strategy (which uses a fixed interval) may be preferable.
"""

@_wait_dunder_call_accept_old_params
@_compat.wait_dunder_call_accept_old_params
def __call__(self, retry_state):
high = super(wait_random_exponential, self).__call__(
retry_state=retry_state)
return random.uniform(0, high)


def _func_takes_last_result(waiter):
if not six.callable(waiter):
return False
if isinstance(waiter, wait_base):
waiter = waiter.__call__
waiter_spec = _utils.getargspec(waiter)
return 'last_result' in waiter_spec.args


def _wait_func_accept_retry_state(wait_func):
if not six.callable(wait_func):
return wait_func

takes_retry_state = _utils._func_takes_retry_state(wait_func)
if takes_retry_state:
return wait_func

takes_last_result = _func_takes_last_result(wait_func)
if takes_last_result:
@six.wraps(wait_func)
def wrapped_wait_func(retry_state):
return wait_func(
retry_state.attempt_number,
retry_state.seconds_since_start,
last_result=retry_state.outcome,
)
else:
@six.wraps(wait_func)
def wrapped_wait_func(retry_state):
return wait_func(
retry_state.attempt_number,
retry_state.seconds_since_start,
)
return wrapped_wait_func

0 comments on commit 52e0181

Please sign in to comment.