Hello community, here is the log from the commit of package python-tenacity for openSUSE:Factory checked in at 2018-10-22 11:23:51 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-tenacity (Old) and /work/SRC/openSUSE:Factory/.python-tenacity.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-tenacity" Mon Oct 22 11:23:51 2018 rev:5 rq:643129 version:4.12.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-tenacity/python-tenacity.changes 2018-01-13 21:47:40.198011952 +0100 +++ /work/SRC/openSUSE:Factory/.python-tenacity.new/python-tenacity.changes 2018-10-22 11:23:55.863120521 +0200 @@ -1,0 +2,27 @@ +Thu Oct 18 21:56:38 UTC 2018 - Jan Engelhardt <[email protected]> + +- Use noun phrase in summary. + +------------------------------------------------------------------- +Thu Oct 11 11:56:31 UTC 2018 - Dirk Mueller <[email protected]> + +- update to 4.12.0: + * add retry\_error\_callback param + * Fix Mergify conf + * Enable mergify + * Implement before\_sleep logging hook + * Rename tenacity.async to tenacity.\_asyncio + * Remove useless install of nose + * Switch to pytest + * Fix codeblock formatting + * Document how to use Trio/curio + * Catch BaseException rather than just Exception + * Fix pep8 errors + * Only install monotonic on Python 2 + * Stop using pbr to build documentation + * Add \`license\` key to \`setup.cfg\` + * Avoid inspect.getargspec deprecation warning + * Don't fall over if an old version of tornado is installed + * Allow to specify RetryError + +------------------------------------------------------------------- Old: ---- tenacity-4.8.0.tar.gz New: ---- tenacity-4.12.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-tenacity.spec ++++++ --- /var/tmp/diff_new_pack.2uQ4CS/_old 2018-10-22 11:23:56.327120057 +0200 +++ /var/tmp/diff_new_pack.2uQ4CS/_new 2018-10-22 11:23:56.331120053 +0200 @@ -12,16 +12,16 @@ # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. -# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# Please submit bugfixes or comments via https://bugs.opensuse.org/ # %{?!python_module:%define python_module() python-%{**} python3-%{**}} %bcond_without test Name: python-tenacity -Version: 4.8.0 +Version: 4.12.0 Release: 0 -Summary: Retry code until it succeeeds +Summary: Python module for retrying code until it succeeeds License: Apache-2.0 Group: Development/Languages/Python Url: https://github.com/jd/tenacity @@ -29,6 +29,7 @@ BuildRequires: %{python_module devel} BuildRequires: %{python_module monotonic >= 0.6} BuildRequires: %{python_module pbr} +BuildRequires: %{python_module pytest} BuildRequires: %{python_module setuptools} BuildRequires: %{python_module six >= 1.7.0} BuildRequires: fdupes @@ -72,12 +73,13 @@ %if %{with test} %check -%python_exec setup.py nosetests --ignore-files '.*async.py' +%python_exec setup.py nosetests --ignore-files '.*asyncio.py' %endif %files %{python_files} %defattr(-,root,root,-) -%doc AUTHORS ChangeLog LICENSE README.rst +%license LICENSE +%doc ChangeLog README.rst %{python_sitelib}/* %changelog ++++++ tenacity-4.8.0.tar.gz -> tenacity-4.12.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/.mergify.yml new/tenacity-4.12.0/.mergify.yml --- old/tenacity-4.8.0/.mergify.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/tenacity-4.12.0/.mergify.yml 2018-05-01 12:18:58.000000000 +0200 @@ -0,0 +1,9 @@ +rules: + default: + protection: + required_status_checks: + strict: True + contexts: + - continuous-integration/travis-ci + required_pull_request_reviews: + required_approving_review_count: 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/.travis.yml new/tenacity-4.12.0/.travis.yml --- old/tenacity-4.8.0/.travis.yml 2017-12-14 09:53:01.000000000 +0100 +++ new/tenacity-4.12.0/.travis.yml 2018-05-01 12:18:58.000000000 +0200 @@ -6,7 +6,7 @@ - 3.6 - pypy -install: pip install tox tox-travis nose +install: pip install tox tox-travis script: tox diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/AUTHORS new/tenacity-4.12.0/AUTHORS --- old/tenacity-4.8.0/AUTHORS 2017-12-14 09:53:45.000000000 +0100 +++ new/tenacity-4.12.0/AUTHORS 2018-05-01 12:19:48.000000000 +0200 @@ -1,10 +1,15 @@ Brian Williams <[email protected]> Brian-Williams <[email protected]> +Daniel Bennett <[email protected]> Elisey Zanko <[email protected]> +Hannes Gräuler <[email protected]> +Jaye Doepke <[email protected]> Joshua Harlow <[email protected]> Julien Danjou <[email protected]> +Martin Larralde <[email protected]> +Michael Elsdörfer <[email protected]> Michael Evans <[email protected]> +Tim Burke <[email protected]> Victor Yap <[email protected]> William Silversmith <[email protected]> Zane Bitter <[email protected]> -Étienne BERSAC (bersace) <[email protected]> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/ChangeLog new/tenacity-4.12.0/ChangeLog --- old/tenacity-4.8.0/ChangeLog 2017-12-14 09:53:45.000000000 +0100 +++ new/tenacity-4.12.0/ChangeLog 2018-05-01 12:19:47.000000000 +0200 @@ -1,6 +1,39 @@ CHANGES ======= +4.12.0 +------ + +* add retry\_error\_callback param +* Fix Mergify conf +* Enable mergify + +4.11.0 +------ + +* Implement before\_sleep logging hook +* Rename tenacity.async to tenacity.\_asyncio +* Remove useless install of nose +* Switch to pytest +* Fix codeblock formatting +* Document how to use Trio/curio + +4.10.0 +------ + +* Catch BaseException rather than just Exception +* Fix pep8 errors +* Only install monotonic on Python 2 +* Stop using pbr to build documentation +* Add \`license\` key to \`setup.cfg\` + +4.9.0 +----- + +* Avoid inspect.getargspec deprecation warning +* Don't fall over if an old version of tornado is installed +* Allow to specify RetryError + 4.8.0 ----- @@ -83,40 +116,3 @@ ----- * Add \_\_call\_\_ on BaseRetrying class -* Document before and after keywords -* Remove useless MANIFEST -* Remove non-working PyPI download image -* Bump hacking to 0.13 -* Use Python 3 for pep8 tox target -* Remove deprecated wait\_jitter - -3.7.1 ------ - -* Fix pep8 errors - -3.7.0 ------ - -* Correctly set the exception if we TryAgain for ever - -3.6.0 ------ - -* Retry on coroutines -* Run flake8 only with latest python -* Deduplicate retry decorator logic -* Extract controller IOs in subclass - -3.5.0 ------ - -* Allow to combine stop conditions -* Add SayThanks -* retry: implement bitwise operators on retry strategies -* retry: add retry\_all - -3.4.0 ------ - -* Deprecate wait\_jitter for wait\_random diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/PKG-INFO new/tenacity-4.12.0/PKG-INFO --- old/tenacity-4.8.0/PKG-INFO 2017-12-14 09:53:45.000000000 +0100 +++ new/tenacity-4.12.0/PKG-INFO 2018-05-01 12:19:48.000000000 +0200 @@ -1,12 +1,11 @@ Metadata-Version: 1.1 Name: tenacity -Version: 4.8.0 +Version: 4.12.0 Summary: Retry code until it succeeeds Home-page: https://github.com/jd/tenacity Author: Julien Danjou Author-email: [email protected] -License: UNKNOWN -Description-Content-Type: UNKNOWN +License: Apache 2.0 Description: Tenacity ======== .. image:: https://img.shields.io/pypi/v/tenacity.svg @@ -263,6 +262,25 @@ def raise_my_exception(): raise MyException("Fail") + Similarly, you can call a custom callback function after all retries failed, without raising an exception (or you can re-raise or do anything really) + + .. testcode:: + + def return_last_value(last_attempt): + """return the result of the last call attempt""" + return last_attempt.result() + + def is_false(value): + """Return True if value is False""" + return value is False + + # will return False after trying 3 times to get a different result + @retry(stop=stop_after_attempt(3), + retry_error_callback=return_last_value, + retry=retry_if_result(is_false)) + def eventually_return_false(): + return False + You can access the statistics about the retry made over a function by using the `retry` attribute attached to the function and its `statistics` attribute: @@ -320,6 +338,14 @@ @tornado.gen.coroutine def my_async_function(http_client, url): yield http_client.fetch(url) + + You can even use alternative event loops such as `curio` or `Trio` by passing the correct sleep function: + + .. code-block:: python + + @retry(sleep=trio.sleep) + async def my_async_function(loop): + await asks.get('https://example.org') Contribute ---------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/README.rst new/tenacity-4.12.0/README.rst --- old/tenacity-4.8.0/README.rst 2017-12-14 09:53:01.000000000 +0100 +++ new/tenacity-4.12.0/README.rst 2018-05-01 12:18:58.000000000 +0200 @@ -254,6 +254,25 @@ def raise_my_exception(): raise MyException("Fail") +Similarly, you can call a custom callback function after all retries failed, without raising an exception (or you can re-raise or do anything really) + +.. testcode:: + + def return_last_value(last_attempt): + """return the result of the last call attempt""" + return last_attempt.result() + + def is_false(value): + """Return True if value is False""" + return value is False + + # will return False after trying 3 times to get a different result + @retry(stop=stop_after_attempt(3), + retry_error_callback=return_last_value, + retry=retry_if_result(is_false)) + def eventually_return_false(): + return False + You can access the statistics about the retry made over a function by using the `retry` attribute attached to the function and its `statistics` attribute: @@ -311,6 +330,14 @@ @tornado.gen.coroutine def my_async_function(http_client, url): yield http_client.fetch(url) + +You can even use alternative event loops such as `curio` or `Trio` by passing the correct sleep function: + +.. code-block:: python + + @retry(sleep=trio.sleep) + async def my_async_function(loop): + await asks.get('https://example.org') Contribute ---------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/doc/source/index.rst new/tenacity-4.12.0/doc/source/index.rst --- old/tenacity-4.8.0/doc/source/index.rst 2017-12-14 09:53:01.000000000 +0100 +++ new/tenacity-4.12.0/doc/source/index.rst 2018-05-01 12:18:58.000000000 +0200 @@ -254,6 +254,25 @@ def raise_my_exception(): raise MyException("Fail") +Similarly, you can call a custom callback function after all retries failed, without raising an exception (or you can re-raise or do anything really) + +.. testcode:: + + def return_last_value(last_attempt): + """return the result of the last call attempt""" + return last_attempt.result() + + def is_false(value): + """Return True if value is False""" + return value is False + + # will return False after trying 3 times to get a different result + @retry(stop=stop_after_attempt(3), + retry_error_callback=return_last_value, + retry=retry_if_result(is_false)) + def eventually_return_false(): + return False + You can access the statistics about the retry made over a function by using the `retry` attribute attached to the function and its `statistics` attribute: @@ -311,6 +330,14 @@ @tornado.gen.coroutine def my_async_function(http_client, url): yield http_client.fetch(url) + +You can even use alternative event loops such as `curio` or `Trio` by passing the correct sleep function: + +.. code-block:: python + + @retry(sleep=trio.sleep) + async def my_async_function(loop): + await asks.get('https://example.org') Contribute ---------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/requirements.txt new/tenacity-4.12.0/requirements.txt --- old/tenacity-4.8.0/requirements.txt 2017-12-14 09:53:01.000000000 +0100 +++ new/tenacity-4.12.0/requirements.txt 2018-05-01 12:18:58.000000000 +0200 @@ -1,3 +1,3 @@ six>=1.9.0 futures>=3.0;python_version=='2.7' -monotonic>=0.6 # Apache-2.0 +monotonic>=0.6;python_version=='2.7' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/setup.cfg new/tenacity-4.12.0/setup.cfg --- old/tenacity-4.8.0/setup.cfg 2017-12-14 09:53:45.000000000 +0100 +++ new/tenacity-4.12.0/setup.cfg 2018-05-01 12:19:48.000000000 +0200 @@ -1,5 +1,6 @@ [metadata] name = tenacity +license = Apache 2.0 url = https://github.com/jd/tenacity summary = Retry code until it succeeeds description-file = @@ -26,11 +27,6 @@ [wheel] universal = 1 -[build_sphinx] -source-dir = doc/source -build-dir = doc/_build -builder = doctest,html - [egg_info] tag_build = tag_date = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/tenacity/__init__.py new/tenacity-4.12.0/tenacity/__init__.py --- old/tenacity-4.8.0/tenacity/__init__.py 2017-12-14 09:53:01.000000000 +0100 +++ new/tenacity-4.12.0/tenacity/__init__.py 2018-05-01 12:18:58.000000000 +0200 @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- +# Copyright 2016-2018 Julien Danjou # Copyright 2017 Elisey Zanko # Copyright 2016 Étienne Bersac -# Copyright 2016 Julien Danjou # Copyright 2016 Joshua Harlow # Copyright 2013-2014 Ray Holder # @@ -27,13 +27,10 @@ except ImportError: tornado = None -import inspect import sys import threading from concurrent import futures -from monotonic import monotonic as now - import six from tenacity import _utils @@ -81,6 +78,10 @@ from .after import after_log # noqa from .after import after_nothing # noqa +# Import all built-in after strategies for easier usage. +from .before_sleep import before_sleep_log # noqa +from .before_sleep import before_sleep_nothing # noqa + def retry(*dargs, **dkw): """Wrap a function with a new `Retrying` object. @@ -95,7 +96,8 @@ def wrap(f): if asyncio and asyncio.iscoroutinefunction(f): r = AsyncRetrying(*dargs, **dkw) - elif tornado and tornado.gen.is_coroutine_function(f): + elif tornado and hasattr(tornado.gen, 'is_coroutine_function') \ + and tornado.gen.is_coroutine_function(f): r = TornadoRetrying(*dargs, **dkw) else: r = Retrying(*dargs, **dkw) @@ -123,6 +125,21 @@ _unset = object() +class RetryError(Exception): + """Encapsulates the last attempt instance right before giving up.""" + + def __init__(self, last_attempt): + self.last_attempt = last_attempt + + def reraise(self): + if self.last_attempt.failed: + raise self.last_attempt.result() + raise self + + def __str__(self): + return "{0}[{1}]".format(self.__class__.__name__, self.last_attempt) + + class BaseRetrying(object): def __init__(self, @@ -131,23 +148,32 @@ retry=retry_if_exception_type(), before=before_nothing, after=after_nothing, - reraise=False): + before_sleep=before_sleep_nothing, + reraise=False, + retry_error_cls=RetryError, + retry_error_callback=None): self.sleep = sleep self.stop = stop self.wait = wait self.retry = retry self.before = before self.after = after + self.before_sleep = before_sleep self.reraise = reraise self._local = threading.local() # This will allow for passing in the result and handling # the older versions of these functions that do not take # the prior result. self._wait_takes_result = self._waiter_takes_last_result(wait) + self.retry_error_cls = retry_error_cls + self.retry_error_callback = retry_error_callback def copy(self, sleep=_unset, stop=_unset, wait=_unset, - retry=_unset, before=_unset, after=_unset, reraise=_unset): + retry=_unset, before=_unset, after=_unset, before_sleep=_unset, + reraise=_unset): """Copy this object with some parameters changed if needed.""" + if before_sleep is _unset: + before_sleep = self.before_sleep return self.__class__( sleep=self.sleep if sleep is _unset else sleep, stop=self.stop if stop is _unset else stop, @@ -155,6 +181,7 @@ retry=self.retry if retry is _unset else retry, before=self.before if before is _unset else before, after=self.after if after is _unset else after, + before_sleep=before_sleep, reraise=self.reraise if after is _unset else reraise, ) @@ -164,7 +191,7 @@ return False if isinstance(waiter, _wait.wait_base): waiter = waiter.__call__ - waiter_spec = inspect.getargspec(waiter) + waiter_spec = _utils.getargspec(waiter) return 'last_result' in waiter_spec.args def __repr__(self): @@ -224,18 +251,18 @@ def begin(self, fn): self.fn = fn self.statistics.clear() - self.statistics['start_time'] = now() + self.statistics['start_time'] = _utils.now() self.statistics['attempt_number'] = 1 self.statistics['idle_for'] = 0 - def iter(self, result, exc_info, start_time): + def iter(self, result, exc_info, start_time): # noqa fut = Future(self.statistics['attempt_number']) if result is not NO_RESULT: - trial_end_time = now() + trial_end_time = _utils.now() fut.set_result(result) retry = self.retry(fut) elif exc_info: - trial_end_time = now() + trial_end_time = _utils.now() t, e, tb = exc_info _utils.capture(fut, exc_info) if isinstance(e, TryAgain): @@ -256,14 +283,19 @@ self.after(self.fn, self.statistics['attempt_number'], trial_time_taken) - delay_since_first_attempt = now() - self.statistics['start_time'] + delay_since_first_attempt = ( + _utils.now() - self.statistics['start_time'] + ) self.statistics['delay_since_first_attempt'] = \ delay_since_first_attempt if self.stop(self.statistics['attempt_number'], delay_since_first_attempt): + if self.retry_error_callback: + return self.retry_error_callback(fut) + retry_exc = self.retry_error_cls(fut) if self.reraise: - raise RetryError(fut).reraise() - six.raise_from(RetryError(fut), fut.exception()) + raise retry_exc.reraise() + six.raise_from(retry_exc, fut.exception()) if self.wait: if self._wait_takes_result: @@ -277,6 +309,9 @@ self.statistics['idle_for'] += sleep self.statistics['attempt_number'] += 1 + if self.before_sleep is not None: + self.before_sleep(self, sleep=sleep, last_result=fut) + return DoSleep(sleep) @@ -288,7 +323,7 @@ result = NO_RESULT exc_info = None - start_time = now() + start_time = _utils.now() while True: do = self.iter(result=result, exc_info=exc_info, @@ -297,7 +332,7 @@ try: result = fn(*args, **kwargs) continue - except Exception: + except BaseException: exc_info = sys.exc_info() continue elif isinstance(do, DoSleep): @@ -333,23 +368,8 @@ return fut -class RetryError(Exception): - """Encapsulates the last attempt instance right before giving up.""" - - def __init__(self, last_attempt): - self.last_attempt = last_attempt - - def reraise(self): - if self.last_attempt.failed: - raise self.last_attempt.result() - raise self - - def __str__(self): - return "RetryError[{0}]".format(self.last_attempt) - - if asyncio: - from tenacity.async import AsyncRetrying + from tenacity._asyncio import AsyncRetrying if tornado: from tenacity.tornadoweb import TornadoRetrying diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/tenacity/_asyncio.py new/tenacity-4.12.0/tenacity/_asyncio.py --- old/tenacity-4.8.0/tenacity/_asyncio.py 1970-01-01 01:00:00.000000000 +0100 +++ new/tenacity-4.12.0/tenacity/_asyncio.py 2018-05-01 12:18:58.000000000 +0200 @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# Copyright 2016 Étienne Bersac +# Copyright 2016 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 asyncio +import sys + +from tenacity import BaseRetrying +from tenacity import DoAttempt +from tenacity import DoSleep +from tenacity import NO_RESULT +from tenacity import _utils + + +class AsyncRetrying(BaseRetrying): + + def __init__(self, + sleep=asyncio.sleep, + **kwargs): + super(AsyncRetrying, self).__init__(**kwargs) + self.sleep = sleep + + @asyncio.coroutine + def call(self, fn, *args, **kwargs): + self.begin(fn) + + result = NO_RESULT + exc_info = None + start_time = _utils.now() + + while True: + do = self.iter(result=result, exc_info=exc_info, + start_time=start_time) + if isinstance(do, DoAttempt): + try: + result = yield from fn(*args, **kwargs) + exc_info = None + continue + except BaseException: + result = NO_RESULT + exc_info = sys.exc_info() + continue + elif isinstance(do, DoSleep): + result = NO_RESULT + exc_info = None + yield from self.sleep(do) + else: + return do diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/tenacity/_utils.py new/tenacity-4.12.0/tenacity/_utils.py --- old/tenacity-4.8.0/tenacity/_utils.py 2017-12-14 09:53:01.000000000 +0100 +++ new/tenacity-4.12.0/tenacity/_utils.py 2018-05-01 12:18:58.000000000 +0200 @@ -16,6 +16,7 @@ import inspect import sys +import time import six @@ -31,10 +32,17 @@ # TODO(harlowja): delete this in future, since its # has to repeatedly calculate this crap. fut.set_exception_info(tb[1], tb[2]) + + def getargspec(func): + # This was deprecated in Python 3. + return inspect.getargspec(func) else: def capture(fut, tb): fut.set_exception(tb[1]) + def getargspec(func): + return inspect.getfullargspec(func) + def visible_attrs(obj, attrs=None): if attrs is None: @@ -97,3 +105,9 @@ except AttributeError: pass return ".".join(segments) + + +try: + now = time.monotonic # noqa +except AttributeError: + from monotonic import monotonic as now # noqa diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/tenacity/async.py new/tenacity-4.12.0/tenacity/async.py --- old/tenacity-4.8.0/tenacity/async.py 2017-12-14 09:53:01.000000000 +0100 +++ new/tenacity-4.12.0/tenacity/async.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2016 Étienne Bersac -# Copyright 2016 Julien Danjou -# Copyright 2016 Joshua Harlow -# Copyright 2013-2014 Ray Holder -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 asyncio -import sys - -from monotonic import monotonic as now - -from tenacity import BaseRetrying -from tenacity import DoAttempt -from tenacity import DoSleep -from tenacity import NO_RESULT - - -class AsyncRetrying(BaseRetrying): - - def __init__(self, - sleep=asyncio.sleep, - **kwargs): - super(AsyncRetrying, self).__init__(**kwargs) - self.sleep = sleep - - @asyncio.coroutine - def call(self, fn, *args, **kwargs): - self.begin(fn) - - result = NO_RESULT - exc_info = None - start_time = now() - - while True: - do = self.iter(result=result, exc_info=exc_info, - start_time=start_time) - if isinstance(do, DoAttempt): - try: - result = yield from fn(*args, **kwargs) - exc_info = None - continue - except Exception: - result = NO_RESULT - exc_info = sys.exc_info() - continue - elif isinstance(do, DoSleep): - result = NO_RESULT - exc_info = None - yield from self.sleep(do) - else: - return do diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/tenacity/before_sleep.py new/tenacity-4.12.0/tenacity/before_sleep.py --- old/tenacity-4.8.0/tenacity/before_sleep.py 1970-01-01 01:00:00.000000000 +0100 +++ new/tenacity-4.12.0/tenacity/before_sleep.py 2018-05-01 12:18:58.000000000 +0200 @@ -0,0 +1,33 @@ +# Copyright 2016 Julien Danjou +# Copyright 2016 Joshua Harlow +# Copyright 2013-2014 Ray Holder +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 tenacity import _utils + + +def before_sleep_nothing(retry_obj, sleep, last_result): + """Before call strategy that does nothing.""" + + +def before_sleep_log(logger, log_level): + """Before call strategy that logs to some logger the attempt.""" + def log_it(retry_obj, sleep, last_result): + logger.log(log_level, + "Retrying %s in %d seconds as it raised %s.", + _utils.get_callback_name(retry_obj.fn), + sleep, + last_result.exception()) + + return log_it diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/tenacity/tests/test_async.py new/tenacity-4.12.0/tenacity/tests/test_async.py --- old/tenacity-4.8.0/tenacity/tests/test_async.py 2017-12-14 09:53:01.000000000 +0100 +++ new/tenacity-4.12.0/tenacity/tests/test_async.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,57 +0,0 @@ -# coding: utf-8 -# Copyright 2016 Étienne Bersac -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 asyncio -import unittest - -import six - -from tenacity import async -from tenacity import retry -from tenacity.tests.test_tenacity import NoIOErrorAfterCount - - -def asynctest(callable_): - callable_ = asyncio.coroutine(callable_) - - @six.wraps(callable_) - def wrapper(*a, **kw): - loop = asyncio.get_event_loop() - return loop.run_until_complete(callable_(*a, **kw)) - - return wrapper - - -@retry [email protected] -def _retryable_coroutine(thing): - yield from asyncio.sleep(0.00001) - thing.go() - - -class TestAsync(unittest.TestCase): - @asynctest - def test_retry(self): - assert asyncio.iscoroutinefunction(_retryable_coroutine) - thing = NoIOErrorAfterCount(5) - yield from _retryable_coroutine(thing) - assert thing.counter == thing.count - - def test_repr(self): - repr(async.AsyncRetrying()) - - -if __name__ == '__main__': - unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/tenacity/tests/test_asyncio.py new/tenacity-4.12.0/tenacity/tests/test_asyncio.py --- old/tenacity-4.8.0/tenacity/tests/test_asyncio.py 1970-01-01 01:00:00.000000000 +0100 +++ new/tenacity-4.12.0/tenacity/tests/test_asyncio.py 2018-05-01 12:18:58.000000000 +0200 @@ -0,0 +1,57 @@ +# coding: utf-8 +# Copyright 2016 Étienne Bersac +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 asyncio +import unittest + +import six + +from tenacity import _asyncio as tasyncio +from tenacity import retry +from tenacity.tests.test_tenacity import NoIOErrorAfterCount + + +def asynctest(callable_): + callable_ = asyncio.coroutine(callable_) + + @six.wraps(callable_) + def wrapper(*a, **kw): + loop = asyncio.get_event_loop() + return loop.run_until_complete(callable_(*a, **kw)) + + return wrapper + + +@retry [email protected] +def _retryable_coroutine(thing): + yield from asyncio.sleep(0.00001) + thing.go() + + +class TestAsync(unittest.TestCase): + @asynctest + def test_retry(self): + assert asyncio.iscoroutinefunction(_retryable_coroutine) + thing = NoIOErrorAfterCount(5) + yield from _retryable_coroutine(thing) + assert thing.counter == thing.count + + def test_repr(self): + repr(tasyncio.AsyncRetrying()) + + +if __name__ == '__main__': + unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/tenacity/tests/test_tenacity.py new/tenacity-4.12.0/tenacity/tests/test_tenacity.py --- old/tenacity-4.8.0/tenacity/tests/test_tenacity.py 2017-12-14 09:53:01.000000000 +0100 +++ new/tenacity-4.12.0/tenacity/tests/test_tenacity.py 2018-05-01 12:18:58.000000000 +0200 @@ -720,6 +720,27 @@ self.assertTrue(TestBeforeAfterAttempts._attempt_number is 2) + def test_before_sleep(self): + TestBeforeAfterAttempts._attempt_number = 0 + + def _before_sleep(retry_obj, sleep, last_result): + self.assertGreater(sleep, 0) + TestBeforeAfterAttempts._attempt_number = \ + retry_obj.statistics['attempt_number'] + + @retry(wait=tenacity.wait_fixed(0.1), + stop=tenacity.stop_after_attempt(3), + before_sleep=_before_sleep) + def _test_before_sleep(): + if TestBeforeAfterAttempts._attempt_number < 2: + raise Exception("testing before_sleep_attempts handler") + else: + pass + + _test_before_sleep() + + self.assertTrue(TestBeforeAfterAttempts._attempt_number is 2) + class TestReraiseExceptions(unittest.TestCase): @@ -810,5 +831,31 @@ self.assertEqual(2, _foobar.retry.statistics['attempt_number']) +class TestRetryErrorCallback(unittest.TestCase): + + def setUp(self): + self._attempt_number = 0 + self._callback_called = False + + def _callback(self, fut): + self._callback_called = True + return fut + + def test_retry_error_callback(self): + num_attempts = 3 + + @retry(stop=tenacity.stop_after_attempt(num_attempts), + retry_error_callback=self._callback) + def _foobar(): + self._attempt_number += 1 + raise Exception("This exception should not be raised") + + result = _foobar() + + self.assertTrue(self._callback_called) + self.assertEqual(num_attempts, self._attempt_number) + self.assertIsInstance(result, tenacity.Future) + + if __name__ == '__main__': unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/tenacity/tests/test_tornado.py new/tenacity-4.12.0/tenacity/tests/test_tornado.py --- old/tenacity-4.8.0/tenacity/tests/test_tornado.py 2017-12-14 09:53:01.000000000 +0100 +++ new/tenacity-4.12.0/tenacity/tests/test_tornado.py 2018-05-01 12:18:58.000000000 +0200 @@ -41,6 +41,19 @@ def test_repr(self): repr(tornadoweb.TornadoRetrying()) + def test_old_tornado(self): + old_attr = gen.is_coroutine_function + try: + del gen.is_coroutine_function + + # is_coroutine_function was introduced in tornado 4.5; + # verify that we don't *completely* fall over on old versions + @retry + def retryable(thing): + pass + finally: + gen.is_coroutine_function = old_attr + if __name__ == '__main__': unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/tenacity/tornadoweb.py new/tenacity-4.12.0/tenacity/tornadoweb.py --- old/tenacity-4.8.0/tenacity/tornadoweb.py 2017-12-14 09:53:01.000000000 +0100 +++ new/tenacity-4.12.0/tenacity/tornadoweb.py 2018-05-01 12:18:58.000000000 +0200 @@ -15,12 +15,11 @@ import sys -from monotonic import monotonic as now - from tenacity import BaseRetrying from tenacity import DoAttempt from tenacity import DoSleep from tenacity import NO_RESULT +from tenacity import _utils from tornado import gen @@ -39,7 +38,7 @@ result = NO_RESULT exc_info = None - start_time = now() + start_time = _utils.now() while True: do = self.iter(result=result, exc_info=exc_info, @@ -49,7 +48,7 @@ result = yield fn(*args, **kwargs) exc_info = None continue - except Exception: + except BaseException: result = NO_RESULT exc_info = sys.exc_info() continue diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/tenacity.egg-info/PKG-INFO new/tenacity-4.12.0/tenacity.egg-info/PKG-INFO --- old/tenacity-4.8.0/tenacity.egg-info/PKG-INFO 2017-12-14 09:53:45.000000000 +0100 +++ new/tenacity-4.12.0/tenacity.egg-info/PKG-INFO 2018-05-01 12:19:48.000000000 +0200 @@ -1,12 +1,11 @@ Metadata-Version: 1.1 Name: tenacity -Version: 4.8.0 +Version: 4.12.0 Summary: Retry code until it succeeeds Home-page: https://github.com/jd/tenacity Author: Julien Danjou Author-email: [email protected] -License: UNKNOWN -Description-Content-Type: UNKNOWN +License: Apache 2.0 Description: Tenacity ======== .. image:: https://img.shields.io/pypi/v/tenacity.svg @@ -263,6 +262,25 @@ def raise_my_exception(): raise MyException("Fail") + Similarly, you can call a custom callback function after all retries failed, without raising an exception (or you can re-raise or do anything really) + + .. testcode:: + + def return_last_value(last_attempt): + """return the result of the last call attempt""" + return last_attempt.result() + + def is_false(value): + """Return True if value is False""" + return value is False + + # will return False after trying 3 times to get a different result + @retry(stop=stop_after_attempt(3), + retry_error_callback=return_last_value, + retry=retry_if_result(is_false)) + def eventually_return_false(): + return False + You can access the statistics about the retry made over a function by using the `retry` attribute attached to the function and its `statistics` attribute: @@ -320,6 +338,14 @@ @tornado.gen.coroutine def my_async_function(http_client, url): yield http_client.fetch(url) + + You can even use alternative event loops such as `curio` or `Trio` by passing the correct sleep function: + + .. code-block:: python + + @retry(sleep=trio.sleep) + async def my_async_function(loop): + await asks.get('https://example.org') Contribute ---------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/tenacity.egg-info/SOURCES.txt new/tenacity-4.12.0/tenacity.egg-info/SOURCES.txt --- old/tenacity-4.8.0/tenacity.egg-info/SOURCES.txt 2017-12-14 09:53:45.000000000 +0100 +++ new/tenacity-4.12.0/tenacity.egg-info/SOURCES.txt 2018-05-01 12:19:48.000000000 +0200 @@ -1,3 +1,4 @@ +.mergify.yml .travis.yml AUTHORS ChangeLog @@ -10,10 +11,11 @@ doc/source/conf.py doc/source/index.rst tenacity/__init__.py +tenacity/_asyncio.py tenacity/_utils.py tenacity/after.py -tenacity/async.py tenacity/before.py +tenacity/before_sleep.py tenacity/nap.py tenacity/retry.py tenacity/stop.py @@ -27,6 +29,6 @@ tenacity.egg-info/requires.txt tenacity.egg-info/top_level.txt tenacity/tests/__init__.py -tenacity/tests/test_async.py +tenacity/tests/test_asyncio.py tenacity/tests/test_tenacity.py tenacity/tests/test_tornado.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/tenacity.egg-info/pbr.json new/tenacity-4.12.0/tenacity.egg-info/pbr.json --- old/tenacity-4.8.0/tenacity.egg-info/pbr.json 2017-12-14 09:53:45.000000000 +0100 +++ new/tenacity-4.12.0/tenacity.egg-info/pbr.json 2018-05-01 12:19:48.000000000 +0200 @@ -1 +1 @@ -{"git_version": "69cf4a7", "is_release": true} \ No newline at end of file +{"git_version": "723ba6a", "is_release": true} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/tenacity.egg-info/requires.txt new/tenacity-4.12.0/tenacity.egg-info/requires.txt --- old/tenacity-4.8.0/tenacity.egg-info/requires.txt 2017-12-14 09:53:45.000000000 +0100 +++ new/tenacity-4.12.0/tenacity.egg-info/requires.txt 2018-05-01 12:19:48.000000000 +0200 @@ -1,5 +1,5 @@ six>=1.9.0 -monotonic>=0.6 [:(python_version=='2.7')] futures>=3.0 +monotonic>=0.6 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-4.8.0/tox.ini new/tenacity-4.12.0/tox.ini --- old/tenacity-4.8.0/tox.ini 2017-12-14 09:53:01.000000000 +0100 +++ new/tenacity-4.12.0/tox.ini 2018-05-01 12:18:58.000000000 +0200 @@ -11,13 +11,14 @@ usedevelop = True sitepackages = False deps = - nose + pytest sphinx - tornado + tornado>=4.5 commands = - py{27,py}: python setup.py nosetests --ignore-files '.*async.py' - py3{5,6}: python setup.py nosetests - python setup.py build_sphinx + py{27,py}: pytest --ignore='tenacity/tests/test_asyncio.py' '{posargs}' + py3{5,6}: pytest '{posargs}' + sphinx-build -a -E -W -b doctest doc/source doc/build + sphinx-build -a -E -W -b html doc/source doc/build [testenv:pep8] basepython = python3
