Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-tenacity for openSUSE:Factory checked in at 2024-09-09 14:44:34 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-tenacity (Old) and /work/SRC/openSUSE:Factory/.python-tenacity.new.10096 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-tenacity" Mon Sep 9 14:44:34 2024 rev:25 rq:1199461 version:9.0.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-tenacity/python-tenacity.changes 2024-09-05 15:48:25.967173540 +0200 +++ /work/SRC/openSUSE:Factory/.python-tenacity.new.10096/python-tenacity.changes 2024-09-09 14:45:27.721239409 +0200 @@ -1,0 +2,11 @@ +Sun Sep 8 13:40:43 UTC 2024 - Dirk Müller <dmuel...@suse.com> + +- update to 9.0.0: + * Respects `min` argument for `wait_random_exponential` + * Bump major version to warn API breakage on statistics + attribute +- update to 8.5.0: + * fix: Restore contents of retry attribute for wrapped + functions + +------------------------------------------------------------------- Old: ---- tenacity-8.4.2.tar.gz New: ---- tenacity-9.0.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-tenacity.spec ++++++ --- /var/tmp/diff_new_pack.lncpun/_old 2024-09-09 14:45:28.361266034 +0200 +++ /var/tmp/diff_new_pack.lncpun/_new 2024-09-09 14:45:28.361266034 +0200 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-tenacity -Version: 8.4.2 +Version: 9.0.0 Release: 0 Summary: Python module for retrying code until it succeeeds License: Apache-2.0 ++++++ tenacity-8.4.2.tar.gz -> tenacity-9.0.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-8.4.2/.github/workflows/ci.yaml new/tenacity-9.0.0/.github/workflows/ci.yaml --- old/tenacity-8.4.2/.github/workflows/ci.yaml 2024-06-24 16:59:58.000000000 +0200 +++ new/tenacity-9.0.0/.github/workflows/ci.yaml 2024-07-29 14:12:16.000000000 +0200 @@ -34,7 +34,7 @@ tox: mypy steps: - name: Checkout ðï¸ - uses: actions/checkout@v4.1.6 + uses: actions/checkout@v4.1.7 with: fetch-depth: 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-8.4.2/.github/workflows/deploy.yaml new/tenacity-9.0.0/.github/workflows/deploy.yaml --- old/tenacity-8.4.2/.github/workflows/deploy.yaml 2024-06-24 16:59:58.000000000 +0200 +++ new/tenacity-9.0.0/.github/workflows/deploy.yaml 2024-07-29 14:12:16.000000000 +0200 @@ -11,7 +11,7 @@ runs-on: ubuntu-latest steps: - name: Checkout ðï¸ - uses: actions/checkout@v4.1.6 + uses: actions/checkout@v4.1.7 with: fetch-depth: 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-8.4.2/PKG-INFO new/tenacity-9.0.0/PKG-INFO --- old/tenacity-8.4.2/PKG-INFO 2024-06-24 17:00:08.379039000 +0200 +++ new/tenacity-9.0.0/PKG-INFO 2024-07-29 14:12:25.123712800 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: tenacity -Version: 8.4.2 +Version: 9.0.0 Summary: Retry code until it succeeds Home-page: https://github.com/jd/tenacity Author: Julien Danjou diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-8.4.2/README.rst new/tenacity-9.0.0/README.rst --- old/tenacity-8.4.2/README.rst 2024-06-24 16:59:58.000000000 +0200 +++ new/tenacity-9.0.0/README.rst 2024-07-29 14:12:16.000000000 +0200 @@ -124,8 +124,8 @@ print("Stopping after 10 seconds") raise Exception -If you're on a tight deadline, and exceeding your delay time isn't ok, -then you can give up on retries one attempt before you would exceed the delay. +If you're on a tight deadline, and exceeding your delay time isn't ok, +then you can give up on retries one attempt before you would exceed the delay. .. testcode:: @@ -362,7 +362,7 @@ ~~~~~~~~~~ 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: +`statistics` attribute attached to the function: .. testcode:: @@ -375,7 +375,7 @@ except Exception: pass - print(raise_my_exception.retry.statistics) + print(raise_my_exception.statistics) .. testoutput:: :hide: @@ -495,7 +495,7 @@ except Exception: pass - print(raise_my_exception.retry.statistics) + print(raise_my_exception.statistics) .. testoutput:: :hide: @@ -514,6 +514,32 @@ retryer = Retrying(stop=stop_after_attempt(max_attempts), reraise=True) retryer(never_good_enough, 'I really do try') +You may also want to change the behaviour of a decorated function temporarily, +like in tests to avoid unnecessary wait times. You can modify/patch the `retry` +attribute attached to the function. Bear in mind this is a write-only attribute, +statistics should be read from the function `statistics` attribute. + +.. testcode:: + + @retry(stop=stop_after_attempt(3), wait=wait_fixed(3)) + def raise_my_exception(): + raise MyException("Fail") + + from unittest import mock + + with mock.patch.object(raise_my_exception.retry, "wait", wait_fixed(0)): + try: + raise_my_exception() + except Exception: + pass + + print(raise_my_exception.statistics) + +.. testoutput:: + :hide: + + ... + Retrying code block ~~~~~~~~~~~~~~~~~~~ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-8.4.2/doc/source/index.rst new/tenacity-9.0.0/doc/source/index.rst --- old/tenacity-8.4.2/doc/source/index.rst 2024-06-24 16:59:58.000000000 +0200 +++ new/tenacity-9.0.0/doc/source/index.rst 2024-07-29 14:12:16.000000000 +0200 @@ -124,8 +124,8 @@ print("Stopping after 10 seconds") raise Exception -If you're on a tight deadline, and exceeding your delay time isn't ok, -then you can give up on retries one attempt before you would exceed the delay. +If you're on a tight deadline, and exceeding your delay time isn't ok, +then you can give up on retries one attempt before you would exceed the delay. .. testcode:: @@ -362,7 +362,7 @@ ~~~~~~~~~~ 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: +`statistics` attribute attached to the function: .. testcode:: @@ -375,7 +375,7 @@ except Exception: pass - print(raise_my_exception.retry.statistics) + print(raise_my_exception.statistics) .. testoutput:: :hide: @@ -495,7 +495,7 @@ except Exception: pass - print(raise_my_exception.retry.statistics) + print(raise_my_exception.statistics) .. testoutput:: :hide: @@ -514,6 +514,32 @@ retryer = Retrying(stop=stop_after_attempt(max_attempts), reraise=True) retryer(never_good_enough, 'I really do try') +You may also want to change the behaviour of a decorated function temporarily, +like in tests to avoid unnecessary wait times. You can modify/patch the `retry` +attribute attached to the function. Bear in mind this is a write-only attribute, +statistics should be read from the function `statistics` attribute. + +.. testcode:: + + @retry(stop=stop_after_attempt(3), wait=wait_fixed(3)) + def raise_my_exception(): + raise MyException("Fail") + + from unittest import mock + + with mock.patch.object(raise_my_exception.retry, "wait", wait_fixed(0)): + try: + raise_my_exception() + except Exception: + pass + + print(raise_my_exception.statistics) + +.. testoutput:: + :hide: + + ... + Retrying code block ~~~~~~~~~~~~~~~~~~~ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-8.4.2/releasenotes/notes/fix-retry-wrapper-attributes-f7a3a45b8e90f257.yaml new/tenacity-9.0.0/releasenotes/notes/fix-retry-wrapper-attributes-f7a3a45b8e90f257.yaml --- old/tenacity-8.4.2/releasenotes/notes/fix-retry-wrapper-attributes-f7a3a45b8e90f257.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/tenacity-9.0.0/releasenotes/notes/fix-retry-wrapper-attributes-f7a3a45b8e90f257.yaml 2024-07-29 14:12:16.000000000 +0200 @@ -0,0 +1,6 @@ +--- +fixes: + - | + Restore the value of the `retry` attribute for wrapped functions. Also, + clarify that those attributes are write-only and statistics should be + read from the function attribute directly. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-8.4.2/releasenotes/notes/wait-random-exponential-min-2a4b7eed9f002436.yaml new/tenacity-9.0.0/releasenotes/notes/wait-random-exponential-min-2a4b7eed9f002436.yaml --- old/tenacity-8.4.2/releasenotes/notes/wait-random-exponential-min-2a4b7eed9f002436.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/tenacity-9.0.0/releasenotes/notes/wait-random-exponential-min-2a4b7eed9f002436.yaml 2024-07-29 14:12:16.000000000 +0200 @@ -0,0 +1,4 @@ +--- +fixes: + - | + Respects `min` arg for `wait_random_exponential` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-8.4.2/tenacity/__init__.py new/tenacity-9.0.0/tenacity/__init__.py --- old/tenacity-8.4.2/tenacity/__init__.py 2024-06-24 16:59:58.000000000 +0200 +++ new/tenacity-9.0.0/tenacity/__init__.py 2024-07-29 14:12:16.000000000 +0200 @@ -339,7 +339,7 @@ return self.copy(*args, **kwargs).wraps(f) # Preserve attributes - wrapped_f.retry = wrapped_f # type: ignore[attr-defined] + wrapped_f.retry = self # type: ignore[attr-defined] wrapped_f.retry_with = retry_with # type: ignore[attr-defined] wrapped_f.statistics = {} # type: ignore[attr-defined] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-8.4.2/tenacity/asyncio/__init__.py new/tenacity-9.0.0/tenacity/asyncio/__init__.py --- old/tenacity-8.4.2/tenacity/asyncio/__init__.py 2024-06-24 16:59:58.000000000 +0200 +++ new/tenacity-9.0.0/tenacity/asyncio/__init__.py 2024-07-29 14:12:16.000000000 +0200 @@ -189,7 +189,7 @@ return await copy(fn, *args, **kwargs) # Preserve attributes - async_wrapped.retry = async_wrapped # type: ignore[attr-defined] + async_wrapped.retry = self # type: ignore[attr-defined] async_wrapped.retry_with = wrapped.retry_with # type: ignore[attr-defined] async_wrapped.statistics = {} # type: ignore[attr-defined] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-8.4.2/tenacity/wait.py new/tenacity-9.0.0/tenacity/wait.py --- old/tenacity-8.4.2/tenacity/wait.py 2024-06-24 16:59:58.000000000 +0200 +++ new/tenacity-9.0.0/tenacity/wait.py 2024-07-29 14:12:16.000000000 +0200 @@ -197,7 +197,7 @@ def __call__(self, retry_state: "RetryCallState") -> float: high = super().__call__(retry_state=retry_state) - return random.uniform(0, high) + return random.uniform(self.min, high) class wait_exponential_jitter(wait_base): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-8.4.2/tenacity.egg-info/PKG-INFO new/tenacity-9.0.0/tenacity.egg-info/PKG-INFO --- old/tenacity-8.4.2/tenacity.egg-info/PKG-INFO 2024-06-24 17:00:08.000000000 +0200 +++ new/tenacity-9.0.0/tenacity.egg-info/PKG-INFO 2024-07-29 14:12:25.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: tenacity -Version: 8.4.2 +Version: 9.0.0 Summary: Retry code until it succeeds Home-page: https://github.com/jd/tenacity Author: Julien Danjou diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-8.4.2/tenacity.egg-info/SOURCES.txt new/tenacity-9.0.0/tenacity.egg-info/SOURCES.txt --- old/tenacity-8.4.2/tenacity.egg-info/SOURCES.txt 2024-06-24 17:00:08.000000000 +0200 +++ new/tenacity-9.0.0/tenacity.egg-info/SOURCES.txt 2024-07-29 14:12:25.000000000 +0200 @@ -38,6 +38,7 @@ releasenotes/notes/export-convenience-symbols-981d9611c8b754f3.yaml releasenotes/notes/fix-async-loop-with-result-f68e913ccb425aca.yaml releasenotes/notes/fix-local-context-overwrite-94190ba06a481631.yaml +releasenotes/notes/fix-retry-wrapper-attributes-f7a3a45b8e90f257.yaml releasenotes/notes/fix-setuptools-config-3af71aa3592b6948.yaml releasenotes/notes/fix-wait-typing-b26eecdb6cc0a1de.yaml releasenotes/notes/fix_async-52b6594c8e75c4bc.yaml @@ -52,6 +53,7 @@ releasenotes/notes/support-timedelta-wait-unit-type-5ba1e9fc0fe45523.yaml releasenotes/notes/timedelta-for-stop-ef6bf71b88ce9988.yaml releasenotes/notes/trio-support-retry-22bd544800cd1f36.yaml +releasenotes/notes/wait-random-exponential-min-2a4b7eed9f002436.yaml releasenotes/notes/wait_exponential_jitter-6ffc81dddcbaa6d3.yaml tenacity/__init__.py tenacity/_utils.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-8.4.2/tests/test_asyncio.py new/tenacity-9.0.0/tests/test_asyncio.py --- old/tenacity-8.4.2/tests/test_asyncio.py 2024-06-24 16:59:58.000000000 +0200 +++ new/tenacity-9.0.0/tests/test_asyncio.py 2024-07-29 14:12:16.000000000 +0200 @@ -17,6 +17,7 @@ import inspect import unittest from functools import wraps +from unittest import mock try: import trio @@ -59,7 +60,7 @@ @retry(stop=stop_after_attempt(2)) async def _retryable_coroutine_with_2_attempts(thing): await asyncio.sleep(0.00001) - thing.go() + return thing.go() class TestAsyncio(unittest.TestCase): @@ -394,6 +395,64 @@ await _async_function(thing) +class TestDecoratorWrapper(unittest.TestCase): + @asynctest + async def test_retry_function_attributes(self): + """Test that the wrapped function attributes are exposed as intended. + + - statistics contains the value for the latest function run + - retry object can be modified to change its behaviour (useful to patch in tests) + - retry object statistics do not contain valid information + """ + + self.assertTrue( + await _retryable_coroutine_with_2_attempts(NoIOErrorAfterCount(1)) + ) + + expected_stats = { + "attempt_number": 2, + "delay_since_first_attempt": mock.ANY, + "idle_for": mock.ANY, + "start_time": mock.ANY, + } + self.assertEqual( + _retryable_coroutine_with_2_attempts.statistics, # type: ignore[attr-defined] + expected_stats, + ) + self.assertEqual( + _retryable_coroutine_with_2_attempts.retry.statistics, # type: ignore[attr-defined] + {}, + ) + + with mock.patch.object( + _retryable_coroutine_with_2_attempts.retry, # type: ignore[attr-defined] + "stop", + tenacity.stop_after_attempt(1), + ): + try: + self.assertTrue( + await _retryable_coroutine_with_2_attempts(NoIOErrorAfterCount(2)) + ) + except RetryError as exc: + expected_stats = { + "attempt_number": 1, + "delay_since_first_attempt": mock.ANY, + "idle_for": mock.ANY, + "start_time": mock.ANY, + } + self.assertEqual( + _retryable_coroutine_with_2_attempts.statistics, # type: ignore[attr-defined] + expected_stats, + ) + self.assertEqual(exc.last_attempt.attempt_number, 1) + self.assertEqual( + _retryable_coroutine_with_2_attempts.retry.statistics, # type: ignore[attr-defined] + {}, + ) + else: + self.fail("RetryError should have been raised after 1 attempt") + + # make sure mypy accepts passing an async sleep function # https://github.com/jd/tenacity/issues/399 async def my_async_sleep(x: float) -> None: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tenacity-8.4.2/tests/test_tenacity.py new/tenacity-9.0.0/tests/test_tenacity.py --- old/tenacity-8.4.2/tests/test_tenacity.py 2024-06-24 16:59:58.000000000 +0200 +++ new/tenacity-9.0.0/tests/test_tenacity.py 2024-07-29 14:12:16.000000000 +0200 @@ -25,6 +25,7 @@ from contextlib import contextmanager from copy import copy from fractions import Fraction +from unittest import mock import pytest @@ -471,9 +472,17 @@ self._assert_inclusive_range(fn(make_retry_state(8, 0)), 0, 60.0) self._assert_inclusive_range(fn(make_retry_state(9, 0)), 0, 60.0) - fn = tenacity.wait_random_exponential(10, 5) + # max wait + max_wait = 5 + fn = tenacity.wait_random_exponential(10, max_wait) for _ in range(1000): - self._assert_inclusive_range(fn(make_retry_state(1, 0)), 0.00, 5.00) + self._assert_inclusive_range(fn(make_retry_state(1, 0)), 0.00, max_wait) + + # min wait + min_wait = 5 + fn = tenacity.wait_random_exponential(min=min_wait) + for _ in range(1000): + self._assert_inclusive_range(fn(make_retry_state(1, 0)), min_wait, 5) # Default arguments exist fn = tenacity.wait_random_exponential() @@ -1073,7 +1082,7 @@ _retryable_test_with_unless_exception_type_name(NameErrorUntilCount(5)) ) except NameError as e: - s = _retryable_test_with_unless_exception_type_name.retry.statistics + s = _retryable_test_with_unless_exception_type_name.statistics self.assertTrue(s["attempt_number"] == 6) print(e) else: @@ -1088,7 +1097,7 @@ ) ) except NameError as e: - s = _retryable_test_with_unless_exception_type_no_input.retry.statistics + s = _retryable_test_with_unless_exception_type_no_input.statistics self.assertTrue(s["attempt_number"] == 6) print(e) else: @@ -1111,7 +1120,7 @@ _retryable_test_if_exception_message_message(NoCustomErrorAfterCount(3)) ) except CustomError: - print(_retryable_test_if_exception_message_message.retry.statistics) + print(_retryable_test_if_exception_message_message.statistics) self.fail("CustomError should've been retried from errormessage") def test_retry_if_not_exception_message(self): @@ -1122,7 +1131,7 @@ ) ) except CustomError: - s = _retryable_test_if_not_exception_message_message.retry.statistics + s = _retryable_test_if_not_exception_message_message.statistics self.assertTrue(s["attempt_number"] == 1) def test_retry_if_not_exception_message_delay(self): @@ -1131,7 +1140,7 @@ _retryable_test_not_exception_message_delay(NameErrorUntilCount(3)) ) except NameError: - s = _retryable_test_not_exception_message_delay.retry.statistics + s = _retryable_test_not_exception_message_delay.statistics print(s["attempt_number"]) self.assertTrue(s["attempt_number"] == 4) @@ -1151,7 +1160,7 @@ ) ) except CustomError: - s = _retryable_test_if_not_exception_message_message.retry.statistics + s = _retryable_test_if_not_exception_message_message.statistics self.assertTrue(s["attempt_number"] == 1) def test_retry_if_exception_cause_type(self): @@ -1209,6 +1218,43 @@ h = retrying.wraps(Hello()) self.assertEqual(h(), "Hello") + def test_retry_function_attributes(self): + """Test that the wrapped function attributes are exposed as intended. + + - statistics contains the value for the latest function run + - retry object can be modified to change its behaviour (useful to patch in tests) + - retry object statistics do not contain valid information + """ + + self.assertTrue(_retryable_test_with_stop(NoneReturnUntilAfterCount(2))) + + expected_stats = { + "attempt_number": 3, + "delay_since_first_attempt": mock.ANY, + "idle_for": mock.ANY, + "start_time": mock.ANY, + } + self.assertEqual(_retryable_test_with_stop.statistics, expected_stats) + self.assertEqual(_retryable_test_with_stop.retry.statistics, {}) + + with mock.patch.object( + _retryable_test_with_stop.retry, "stop", tenacity.stop_after_attempt(1) + ): + try: + self.assertTrue(_retryable_test_with_stop(NoneReturnUntilAfterCount(2))) + except RetryError as exc: + expected_stats = { + "attempt_number": 1, + "delay_since_first_attempt": mock.ANY, + "idle_for": mock.ANY, + "start_time": mock.ANY, + } + self.assertEqual(_retryable_test_with_stop.statistics, expected_stats) + self.assertEqual(exc.last_attempt.attempt_number, 1) + self.assertEqual(_retryable_test_with_stop.retry.statistics, {}) + else: + self.fail("RetryError should have been raised after 1 attempt") + class TestRetryWith: def test_redefine_wait(self): @@ -1479,21 +1525,21 @@ def _foobar(): return 42 - self.assertEqual({}, _foobar.retry.statistics) + self.assertEqual({}, _foobar.statistics) _foobar() - self.assertEqual(1, _foobar.retry.statistics["attempt_number"]) + self.assertEqual(1, _foobar.statistics["attempt_number"]) def test_stats_failing(self): @retry(stop=tenacity.stop_after_attempt(2)) def _foobar(): raise ValueError(42) - self.assertEqual({}, _foobar.retry.statistics) + self.assertEqual({}, _foobar.statistics) try: _foobar() except Exception: # noqa: B902 pass - self.assertEqual(2, _foobar.retry.statistics["attempt_number"]) + self.assertEqual(2, _foobar.statistics["attempt_number"]) class TestRetryErrorCallback(unittest.TestCase):