Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
fixes:
- |
When ``reraise=True`` surfaces the exception that triggered a ``TryAgain``,
that exception's own ``__cause__`` and ``__context__`` are now kept, so the
traceback still shows what led to it. Previously they were overwritten as
if it had been raised with ``from None``.
5 changes: 4 additions & 1 deletion tenacity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,10 @@ def reraise(self) -> t.NoReturn:
if isinstance(exc, TryAgain):
cause = exc.__cause__ or exc.__context__
if cause is not None:
raise cause.with_traceback(cause.__traceback__) from None
# No "from None": that would overwrite the exception's own
# __cause__ and suppress its __context__, hiding the
# chain that led to it.
raise cause.with_traceback(cause.__traceback__)
raise self.last_attempt.result()
raise self

Expand Down
19 changes: 19 additions & 0 deletions tests/test_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,25 @@ async def _test() -> None:
await retrying(_test)
self.assertEqual(2, retrying.statistics["attempt_number"])

@asynctest
async def test_reraise_try_again_keeps_exception_chain(self) -> None:
root = OSError("disk unavailable")

async def _test() -> None:
try:
try:
raise root
except OSError as e:
raise KeyError("config") from e
except KeyError:
raise tenacity.TryAgain # noqa: B904

retrying = tasyncio.AsyncRetrying(stop=stop_after_attempt(2), reraise=True)
with pytest.raises(KeyError) as raised:
await retrying(_test)
assert raised.value.__cause__ is root
assert raised.value.__context__ is root

@asynctest
async def test_sleeps(self) -> None:
start = current_time_ms()
Expand Down
36 changes: 36 additions & 0 deletions tests/test_tenacity.py
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,42 @@ def _r() -> None:
self.assertRaises(UnderlyingError, r, _r)
self.assertEqual(5, r.statistics["attempt_number"])

def test_retry_try_again_reraise_keeps_exception_chain(self) -> None:
# Unwrapping TryAgain must not rewrite the chain of the exception it
# surfaces: its own cause and context still have to be reported.
root = OSError("disk unavailable")

def _r() -> None:
try:
try:
raise root
except OSError as e:
raise KeyError("config") from e
except KeyError:
raise tenacity.TryAgain # noqa: B904

r = Retrying(stop=tenacity.stop_after_attempt(2), reraise=True)
with self.assertRaises(KeyError) as raised:
r(_r)
self.assertIs(raised.exception.__cause__, root)
self.assertIs(raised.exception.__context__, root)

def test_retry_try_again_reraise_keeps_implicit_context(self) -> None:
def _r() -> None:
try:
try:
raise OSError("disk unavailable")
except OSError:
raise KeyError("config") # noqa: B904
except KeyError:
raise tenacity.TryAgain # noqa: B904

r = Retrying(stop=tenacity.stop_after_attempt(2), reraise=True)
with self.assertRaises(KeyError) as raised:
r(_r)
self.assertIsInstance(raised.exception.__context__, OSError)
self.assertFalse(raised.exception.__suppress_context__)

def test_retry_try_again_forever_reraise(self) -> None:
def _r() -> None:
raise tenacity.TryAgain
Expand Down
Loading