From 09946ec02eba45c674929e6a0474a9ed4f12bc3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20de=20Prado?= Date: Mon, 14 Sep 2020 14:58:16 +0100 Subject: [PATCH 1/7] Update _asyncio.py Not entirely sure, but I would say that this line should be added so that it follows the same structure that `Retrying` class. --- tenacity/_asyncio.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tenacity/_asyncio.py b/tenacity/_asyncio.py index 0f48511e..b7493880 100644 --- a/tenacity/_asyncio.py +++ b/tenacity/_asyncio.py @@ -71,3 +71,5 @@ async def __anext__(self): await self.sleep(do) else: return do + + __call__ = call From 7c48e6dfb22c7e23fa70f2e2a145f5c5a154932d Mon Sep 17 00:00:00 2001 From: scrapinghub-ci Date: Tue, 15 Sep 2020 16:36:20 +0100 Subject: [PATCH 2/7] __call__ must be implemented by all BaseRetrying classes --- tenacity/__init__.py | 13 ++++++++----- tenacity/_asyncio.py | 4 +--- tenacity/tests/test_asyncio.py | 14 +++++++++++++- tenacity/tests/test_tenacity.py | 31 ++++++++++++++++++------------- tenacity/tornadoweb.py | 2 +- 5 files changed, 41 insertions(+), 23 deletions(-) diff --git a/tenacity/__init__.py b/tenacity/__init__.py index d6656046..f0783610 100644 --- a/tenacity/__init__.py +++ b/tenacity/__init__.py @@ -16,6 +16,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from abc import abstractmethod, ABC try: from inspect import iscoroutinefunction @@ -208,7 +209,7 @@ def __exit__(self, exc_type, exc_value, traceback): self.retry_state.set_result(None) -class BaseRetrying(object): +class BaseRetrying(ABC): def __init__(self, sleep=sleep, @@ -326,7 +327,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 +397,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,8 +425,6 @@ def call(self, fn, *args, **kwargs): else: return do - __call__ = call - class Future(futures.Future): """Encapsulates a (future or past) attempted call to a target function.""" diff --git a/tenacity/_asyncio.py b/tenacity/_asyncio.py index b7493880..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( @@ -71,5 +71,3 @@ async def __anext__(self): await self.sleep(do) else: return do - - __call__ = call diff --git a/tenacity/tests/test_asyncio.py b/tenacity/tests/test_asyncio.py index baa06115..1c253b48 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 RetryError, AsyncRetrying 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..635e8080 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): @@ -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) + retrying(thing.go) finally: logger.removeHandler(handler) @@ -1180,7 +1185,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 +1214,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( From 3cbf85e1443fbf67f7b49c8fc60c4f05ca417c25 Mon Sep 17 00:00:00 2001 From: scrapinghub-ci Date: Tue, 15 Sep 2020 16:42:57 +0100 Subject: [PATCH 3/7] Fix import order --- tenacity/tests/test_asyncio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tenacity/tests/test_asyncio.py b/tenacity/tests/test_asyncio.py index 1c253b48..617a0ef5 100644 --- a/tenacity/tests/test_asyncio.py +++ b/tenacity/tests/test_asyncio.py @@ -18,7 +18,7 @@ import six -from tenacity import RetryError, AsyncRetrying +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 From 207802db06f973ed828c0685393af41b2e48c77b Mon Sep 17 00:00:00 2001 From: scrapinghub-ci Date: Tue, 15 Sep 2020 16:45:57 +0100 Subject: [PATCH 4/7] Python 2.7 abstract classes compatibility --- tenacity/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tenacity/__init__.py b/tenacity/__init__.py index f0783610..4a4fe2bc 100644 --- a/tenacity/__init__.py +++ b/tenacity/__init__.py @@ -16,7 +16,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from abc import abstractmethod, ABC +from abc import abstractmethod, ABCMeta try: from inspect import iscoroutinefunction @@ -209,7 +209,8 @@ def __exit__(self, exc_type, exc_value, traceback): self.retry_state.set_result(None) -class BaseRetrying(ABC): +class BaseRetrying(object): + __metaclass__ = ABCMeta def __init__(self, sleep=sleep, From 48da2655a6c6e64d467ec8ae7f40aa984e7a22a3 Mon Sep 17 00:00:00 2001 From: scrapinghub-ci Date: Tue, 15 Sep 2020 16:49:12 +0100 Subject: [PATCH 5/7] Making pep8 happy again --- tenacity/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tenacity/__init__.py b/tenacity/__init__.py index 4a4fe2bc..c87bf80f 100644 --- a/tenacity/__init__.py +++ b/tenacity/__init__.py @@ -16,7 +16,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from abc import abstractmethod, ABCMeta try: from inspect import iscoroutinefunction @@ -31,6 +30,7 @@ import sys import threading import typing as t +from abc import ABCMeta, abstractmethod from concurrent import futures import six From a5830a952a93db79acc7a4394f881bec12f2e316 Mon Sep 17 00:00:00 2001 From: scrapinghub-ci Date: Wed, 16 Sep 2020 16:44:25 +0100 Subject: [PATCH 6/7] Keep call method in Retying for backwards compatibility --- tenacity/__init__.py | 6 ++++++ tenacity/tests/test_tenacity.py | 10 ++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/tenacity/__init__.py b/tenacity/__init__.py index c87bf80f..792410af 100644 --- a/tenacity/__init__.py +++ b/tenacity/__init__.py @@ -16,6 +16,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import warnings try: from inspect import iscoroutinefunction @@ -426,6 +427,11 @@ def __call__(self, fn, *args, **kwargs): else: return do + def call(self, *args, **kwargs): + """Deprecated. Use ``__call__`` instead""" + warnings.warn("'Retrying.call()' method is deprecated. Use 'Retrying.__call__()' instead") + self.__call__(self, *args, **kwargs) + class Future(futures.Future): """Encapsulates a (future or past) attempted call to a target function.""" diff --git a/tenacity/tests/test_tenacity.py b/tenacity/tests/test_tenacity.py index 635e8080..568a5a8c 100644 --- a/tenacity/tests/test_tenacity.py +++ b/tenacity/tests/test_tenacity.py @@ -1148,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 @@ -1160,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(thing.go) + get_call_fn(retrying)(thing.go) finally: logger.removeHandler(handler) @@ -1171,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()) From 5245c747e7e865a1ecc3e8319193e417dc7da4d1 Mon Sep 17 00:00:00 2001 From: scrapinghub-ci Date: Wed, 16 Sep 2020 16:48:51 +0100 Subject: [PATCH 7/7] Making pep8 happy again --- tenacity/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tenacity/__init__.py b/tenacity/__init__.py index 792410af..ed82cf55 100644 --- a/tenacity/__init__.py +++ b/tenacity/__init__.py @@ -16,7 +16,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import warnings try: from inspect import iscoroutinefunction @@ -31,9 +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 @@ -428,8 +429,9 @@ def __call__(self, fn, *args, **kwargs): return do def call(self, *args, **kwargs): - """Deprecated. Use ``__call__`` instead""" - warnings.warn("'Retrying.call()' method is deprecated. Use 'Retrying.__call__()' instead") + """Use ``__call__`` instead because this method is deprecated.""" + warnings.warn("'Retrying.call()' method is deprecated. " + + "Use 'Retrying.__call__()' instead") self.__call__(self, *args, **kwargs)