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