diff --git a/tenacity/__init__.py b/tenacity/__init__.py index d6656046..ed82cf55 100644 --- a/tenacity/__init__.py +++ b/tenacity/__init__.py @@ -30,8 +30,11 @@ import sys import threading import typing as t +import warnings +from abc import ABCMeta, abstractmethod from concurrent import futures + import six from tenacity import _utils @@ -209,6 +212,7 @@ def __exit__(self, exc_type, exc_value, traceback): class BaseRetrying(object): + __metaclass__ = ABCMeta def __init__(self, sleep=sleep, @@ -326,7 +330,7 @@ def wraps(self, f): """ @_utils.wraps(f) def wrapped_f(*args, **kw): - return self.call(f, *args, **kw) + return self(f, *args, **kw) def retry_with(*args, **kwargs): return self.copy(*args, **kwargs).wraps(f) @@ -396,11 +400,15 @@ def __iter__(self): else: break + @abstractmethod + def __call__(self, *args, **kwargs): + pass + class Retrying(BaseRetrying): """Retrying controller.""" - def call(self, fn, *args, **kwargs): + def __call__(self, fn, *args, **kwargs): self.begin(fn) retry_state = RetryCallState( @@ -420,7 +428,11 @@ def call(self, fn, *args, **kwargs): else: return do - __call__ = call + def call(self, *args, **kwargs): + """Use ``__call__`` instead because this method is deprecated.""" + warnings.warn("'Retrying.call()' method is deprecated. " + + "Use 'Retrying.__call__()' instead") + self.__call__(self, *args, **kwargs) class Future(futures.Future): diff --git a/tenacity/_asyncio.py b/tenacity/_asyncio.py index 0f48511e..6b24b2e1 100644 --- a/tenacity/_asyncio.py +++ b/tenacity/_asyncio.py @@ -34,7 +34,7 @@ def __init__(self, super(AsyncRetrying, self).__init__(**kwargs) self.sleep = sleep - async def call(self, fn, *args, **kwargs): + async def __call__(self, fn, *args, **kwargs): self.begin(fn) retry_state = RetryCallState( diff --git a/tenacity/tests/test_asyncio.py b/tenacity/tests/test_asyncio.py index baa06115..617a0ef5 100644 --- a/tenacity/tests/test_asyncio.py +++ b/tenacity/tests/test_asyncio.py @@ -18,7 +18,7 @@ import six -from tenacity import RetryError +from tenacity import AsyncRetrying, RetryError from tenacity import _asyncio as tasyncio from tenacity import retry, stop_after_attempt from tenacity.tests.test_tenacity import NoIOErrorAfterCount, current_time_ms @@ -34,6 +34,11 @@ def wrapper(*a, **kw): return wrapper +async def _async_function(thing): + await asyncio.sleep(0.00001) + return thing.go() + + @retry async def _retryable_coroutine(thing): await asyncio.sleep(0.00001) @@ -53,6 +58,13 @@ async def test_retry(self): await _retryable_coroutine(thing) assert thing.counter == thing.count + @asynctest + async def test_retry_using_async_retying(self): + thing = NoIOErrorAfterCount(5) + retrying = AsyncRetrying() + await retrying(_async_function, thing) + assert thing.counter == thing.count + @asynctest async def test_stop_after_attempt(self): thing = NoIOErrorAfterCount(2) diff --git a/tenacity/tests/test_tenacity.py b/tenacity/tests/test_tenacity.py index 4fbed6e6..568a5a8c 100644 --- a/tenacity/tests/test_tenacity.py +++ b/tenacity/tests/test_tenacity.py @@ -34,8 +34,13 @@ class TestBase(unittest.TestCase): + def test_repr(self): - repr(tenacity.BaseRetrying()) + class ConcreteRetrying(tenacity.BaseRetrying): + def __call__(self): + pass + + repr(ConcreteRetrying()) class TestStopConditions(unittest.TestCase): @@ -135,7 +140,7 @@ def __call__(self, attempt_number, seconds_since_start): def failing(): raise NotImplementedError() with pytest.raises(RetryError): - retrying.call(failing) + retrying(failing) def test_stop_func_with_retry_state(self): def stop_func(retry_state): @@ -283,7 +288,7 @@ def dying(): stop=tenacity.stop_after_attempt(r_attempts), reraise=True) with reports_deprecation_warning(): - self.assertRaises(Exception, r.call, dying) + self.assertRaises(Exception, r, dying) self.assertEqual(r_attempts - 1, len(captures)) self.assertTrue(all([r.failed for r in captures])) @@ -451,14 +456,14 @@ def dying(): retrying1 = Retrying(wait=wait1, stop=tenacity.stop_after_attempt(4)) with reports_deprecation_warning(): - self.assertRaises(Exception, lambda: retrying1.call(dying)) + self.assertRaises(Exception, lambda: retrying1(dying)) self.assertEqual([t[0] for t in wait1.calls], [1, 2, 3]) # This assumes that 3 iterations complete within 1 second. self.assertTrue(all(t[1] < 1 for t in wait1.calls)) retrying2 = Retrying(wait=wait2, stop=tenacity.stop_after_attempt(4)) with reports_deprecation_warning(): - self.assertRaises(Exception, lambda: retrying2.call(dying)) + self.assertRaises(Exception, lambda: retrying2(dying)) self.assertEqual([t[0] for t in wait2.calls], [1, 2, 3]) # This assumes that 3 iterations complete within 1 second. self.assertTrue(all(t[1] < 1 for t in wait2.calls)) @@ -494,7 +499,7 @@ def waitfunc(retry_state): def returnval(): return 123 try: - retrying.call(returnval) + retrying(returnval) except ExtractCallState as err: retry_state = err.args[0] self.assertIs(retry_state.fn, returnval) @@ -508,7 +513,7 @@ def returnval(): def dying(): raise Exception("Broken") try: - retrying.call(dying) + retrying(dying) except ExtractCallState as err: retry_state = err.args[0] self.assertIs(retry_state.fn, dying) @@ -598,7 +603,7 @@ def _raise_try_again(self): def test_retry_try_again(self): self._attempts = 0 Retrying(stop=tenacity.stop_after_attempt(5), - retry=tenacity.retry_never).call(self._raise_try_again) + retry=tenacity.retry_never)(self._raise_try_again) self.assertEqual(3, self._attempts) def test_retry_try_again_forever(self): @@ -608,7 +613,7 @@ def _r(): r = Retrying(stop=tenacity.stop_after_attempt(5), retry=tenacity.retry_never) self.assertRaises(tenacity.RetryError, - r.call, + r, _r) self.assertEqual(5, r.statistics['attempt_number']) @@ -1048,7 +1053,7 @@ def __call__(self, attempt): def failing(): raise NotImplementedError() with pytest.raises(RetryError): - retrying.call(failing) + retrying(failing) class TestBeforeAfterAttempts(unittest.TestCase): @@ -1143,7 +1148,7 @@ def _test_before_sleep(): self.assertEqual(self.slept, 2) - def test_before_sleep_log_raises(self): + def _before_sleep_log_raises(self, get_call_fn): thing = NoIOErrorAfterCount(2) logger = logging.getLogger(self.id()) logger.propagate = False @@ -1155,7 +1160,7 @@ def test_before_sleep_log_raises(self): retrying = Retrying(wait=tenacity.wait_fixed(0.01), stop=tenacity.stop_after_attempt(3), before_sleep=_before_sleep) - retrying.call(thing.go) + get_call_fn(retrying)(thing.go) finally: logger.removeHandler(handler) @@ -1166,6 +1171,12 @@ def test_before_sleep_log_raises(self): self.assertRegexpMatches(fmt(handler.records[0]), etalon_re) self.assertRegexpMatches(fmt(handler.records[1]), etalon_re) + def test_before_sleep_log_raises(self): + self._before_sleep_log_raises(lambda x: x) + + def test_before_sleep_log_raises_deprecated_call(self): + self._before_sleep_log_raises(lambda x: x.call) + def test_before_sleep_log_raises_with_exc_info(self): thing = NoIOErrorAfterCount(2) logger = logging.getLogger(self.id()) @@ -1180,7 +1191,7 @@ def test_before_sleep_log_raises_with_exc_info(self): retrying = Retrying(wait=tenacity.wait_fixed(0.01), stop=tenacity.stop_after_attempt(3), before_sleep=_before_sleep) - retrying.call(thing.go) + retrying(thing.go) finally: logger.removeHandler(handler) @@ -1209,7 +1220,7 @@ def test_before_sleep_log_returns(self, exc_info=False): retrying = Retrying(wait=tenacity.wait_fixed(0.01), stop=tenacity.stop_after_attempt(3), retry=_retry, before_sleep=_before_sleep) - retrying.call(thing.go) + retrying(thing.go) finally: logger.removeHandler(handler) diff --git a/tenacity/tornadoweb.py b/tenacity/tornadoweb.py index 43b4caf9..27dd349a 100644 --- a/tenacity/tornadoweb.py +++ b/tenacity/tornadoweb.py @@ -32,7 +32,7 @@ def __init__(self, self.sleep = sleep @gen.coroutine - def call(self, fn, *args, **kwargs): + def __call__(self, fn, *args, **kwargs): self.begin(fn) retry_state = RetryCallState(