From 04ea6f44505e236bb2e13864af7e1575366ef9a2 Mon Sep 17 00:00:00 2001 From: jakkdl Date: Thu, 31 Oct 2024 12:18:14 +0100 Subject: [PATCH 1/3] add exceptiongroup support to retry_if_exception --- setup.cfg | 1 + tenacity/retry.py | 6 ++++++ tests/test_tenacity.py | 46 ++++++++++++++++++++++++++++++++++++++++++ tox.ini | 2 +- 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index ccb824a2..176e0e13 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,6 +22,7 @@ classifier = [options] install_requires = + exceptiongroup; python_version < "3.11" python_requires = >=3.8 packages = find: diff --git a/tenacity/retry.py b/tenacity/retry.py index 9211631b..f7924595 100644 --- a/tenacity/retry.py +++ b/tenacity/retry.py @@ -16,11 +16,14 @@ import abc import re +import sys import typing if typing.TYPE_CHECKING: from tenacity import RetryCallState +if sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup class retry_base(abc.ABC): """Abstract base class for retry strategies.""" @@ -79,6 +82,9 @@ def __call__(self, retry_state: "RetryCallState") -> bool: exception = retry_state.outcome.exception() if exception is None: raise RuntimeError("outcome failed but the exception is None") + if isinstance(exception, BaseExceptionGroup): + # look for any exceptions not matching the predicate + return exception.split(self.predicate)[1] is None return self.predicate(exception) else: return False diff --git a/tests/test_tenacity.py b/tests/test_tenacity.py index b76fec2c..d31e11a2 100644 --- a/tests/test_tenacity.py +++ b/tests/test_tenacity.py @@ -32,6 +32,9 @@ import tenacity from tenacity import RetryCallState, RetryError, Retrying, retry +if sys.version_info < (3, 11): + from exceptiongroup import ExceptionGroup + _unset = object() @@ -733,6 +736,24 @@ def go(self): return True +class NoExceptionGroupAfterCount: + def __init__(self, count: int, exceptions: tuple[Exception]): + self.counter = 0 + self.count = count + self.exceptions = exceptions + + def go(self): + """Raise an ExceptionGroup until after count threshold has been crossed. + + Then return True. + """ + if self.counter < self.count: + self.counter += 1 + raise ExceptionGroup("tenacity test group", self.exceptions) + + return True + + class NoNameErrorAfterCount: """Holds counter state for invoking a method several times in a row.""" @@ -1014,6 +1035,7 @@ def _retryable_test_with_exception_type_custom(thing): retry=tenacity.retry_if_exception_type(CustomError), ) def _retryable_test_with_exception_type_custom_attempt_limit(thing): + # this is not used?? return thing.go() @@ -1064,6 +1086,30 @@ def test_retry_if_exception_of_type(self): self.assertTrue(isinstance(n, NameError)) print(n) + def test_retry_if_exception_of_type_exceptiongroup(self): + self.assertTrue( + _retryable_test_with_exception_type_io( + NoExceptionGroupAfterCount(5, exceptions=(IOError(),)) + ) + ) + with pytest.raises(ExceptionGroup): + self.assertTrue( + _retryable_test_with_exception_type_io( + NoExceptionGroupAfterCount(5, exceptions=(IOError(),ValueError())) + ) + ) + # not supported + with pytest.raises(ExceptionGroup): + e = IOError() + e.__cause__ = NameError() + self.assertTrue( + _retryable_test_with_exception_cause_type( + NoExceptionGroupAfterCount(5, exceptions=(e,)) + ) + ) + + + def test_retry_except_exception_of_type(self): self.assertTrue( _retryable_test_if_not_exception_type_io(NoNameErrorAfterCount(5)) diff --git a/tox.ini b/tox.ini index 14f8ae00..42f1b7d6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py3{8,9,10,11,12,12-trio}, pep8, pypy3 +envlist = py3{8,9,10,11,12,13,13-trio}, pep8, pypy3 skip_missing_interpreters = True [testenv] From 7034b62895f784f4684c16d99512da819eb10a4f Mon Sep 17 00:00:00 2001 From: jakkdl Date: Thu, 31 Oct 2024 12:40:42 +0100 Subject: [PATCH 2/3] ruff format --- tenacity/retry.py | 1 + tests/test_tenacity.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tenacity/retry.py b/tenacity/retry.py index f7924595..d5f82cd9 100644 --- a/tenacity/retry.py +++ b/tenacity/retry.py @@ -25,6 +25,7 @@ if sys.version_info < (3, 11): from exceptiongroup import BaseExceptionGroup + class retry_base(abc.ABC): """Abstract base class for retry strategies.""" diff --git a/tests/test_tenacity.py b/tests/test_tenacity.py index d31e11a2..88547e4c 100644 --- a/tests/test_tenacity.py +++ b/tests/test_tenacity.py @@ -1095,7 +1095,7 @@ def test_retry_if_exception_of_type_exceptiongroup(self): with pytest.raises(ExceptionGroup): self.assertTrue( _retryable_test_with_exception_type_io( - NoExceptionGroupAfterCount(5, exceptions=(IOError(),ValueError())) + NoExceptionGroupAfterCount(5, exceptions=(IOError(), ValueError())) ) ) # not supported @@ -1108,8 +1108,6 @@ def test_retry_if_exception_of_type_exceptiongroup(self): ) ) - - def test_retry_except_exception_of_type(self): self.assertTrue( _retryable_test_if_not_exception_type_io(NoNameErrorAfterCount(5)) From 12cc12d22e272f066f4a31905cc6d73183c36b65 Mon Sep 17 00:00:00 2001 From: jakkdl Date: Thu, 31 Oct 2024 12:43:02 +0100 Subject: [PATCH 3/3] tuple -> typing.Tuple ... though 3.8 is out anyway... --- .github/workflows/ci.yaml | 4 +++- tests/test_tenacity.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2b9b4f92..9b9c5d76 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,7 +27,9 @@ jobs: - python: "3.11" tox: py311 - python: "3.12" - tox: py312,py312-trio + tox: py312 + - python: "3.13" + tox: py313,py313-trio - python: "3.12" tox: pep8 - python: "3.11" diff --git a/tests/test_tenacity.py b/tests/test_tenacity.py index 88547e4c..db2fd877 100644 --- a/tests/test_tenacity.py +++ b/tests/test_tenacity.py @@ -737,7 +737,7 @@ def go(self): class NoExceptionGroupAfterCount: - def __init__(self, count: int, exceptions: tuple[Exception]): + def __init__(self, count: int, exceptions: typing.Tuple[Exception]): self.counter = 0 self.count = count self.exceptions = exceptions