Skip to content
Merged
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
7 changes: 7 additions & 0 deletions tenacity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,13 @@ def next_action(rs: "RetryCallState") -> None:
self._add_action_func(lambda rs: DoSleep(rs.upcoming_sleep))

def __iter__(self) -> t.Generator[AttemptManager, None, None]:
if not self.enabled:
retry_state = RetryCallState(self, fn=None, args=(), kwargs={})
yield AttemptManager(retry_state=retry_state)
if retry_state.outcome is not None and retry_state.outcome.failed:
raise retry_state.outcome.exception() # type: ignore[misc]
return

self.begin()

retry_state = RetryCallState(self, fn=None, args=(), kwargs={})
Expand Down
14 changes: 14 additions & 0 deletions tenacity/asyncio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,25 @@ def __iter__(self) -> t.Generator[AttemptManager, None, None]:
raise TypeError("AsyncRetrying object is not iterable")

def __aiter__(self) -> "AsyncRetrying":
if not self.enabled:
self._retry_state = RetryCallState(self, fn=None, args=(), kwargs={})
self._disabled_iter_done = False
return self

self.begin()
self._retry_state = RetryCallState(self, fn=None, args=(), kwargs={})
return self

async def __anext__(self) -> AttemptManager:
if not self.enabled:
if self._disabled_iter_done:
outcome = self._retry_state.outcome
if outcome is not None and outcome.failed:
raise outcome.exception() # type: ignore[misc]
raise StopAsyncIteration
self._disabled_iter_done = True
return AttemptManager(retry_state=self._retry_state)

while True:
do = await self.iter(retry_state=self._retry_state)
if do is None:
Expand Down
29 changes: 29 additions & 0 deletions tests/test_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,35 @@ async def always_fails() -> None:
await always_fails()
assert call_count == 1

@asynctest
async def test_enabled_false_aiter_raises_original_exception(self) -> None:
"""When enabled=False, the async iterator raises the original exception,
not a RetryError, and the body executes exactly once."""
call_count = 0
retrying = AsyncRetrying(
enabled=False,
stop=stop_after_attempt(5),
)
with pytest.raises(ValueError, match="fail"):
async for attempt in retrying:
with attempt:
call_count += 1
raise ValueError("fail")
assert call_count == 1

@asynctest
async def test_enabled_false_aiter_succeeds_on_first_attempt(self) -> None:
"""When enabled=False, the async iterator runs the body once and stops."""
call_count = 0
retrying = AsyncRetrying(
enabled=False,
stop=stop_after_attempt(5),
)
async for attempt in retrying:
with attempt:
call_count += 1
assert call_count == 1


@unittest.skipIf(not have_trio, "trio not installed")
class TestTrio(unittest.TestCase):
Expand Down
29 changes: 29 additions & 0 deletions tests/test_tenacity.py
Original file line number Diff line number Diff line change
Expand Up @@ -1561,6 +1561,35 @@ def fails_twice() -> bool:
assert fails_twice() is True
assert call_count == 3

def test_enabled_false_iter_raises_original_exception(self) -> None:
"""When enabled=False, the iterator protocol raises the original exception,
not a RetryError, and the body executes exactly once."""
call_count = 0
retrying = Retrying(
enabled=False,
stop=tenacity.stop_after_attempt(5),
wait=tenacity.wait_none(),
)
with pytest.raises(ValueError, match="fail"):
for attempt in retrying:
with attempt:
call_count += 1
raise ValueError("fail")
assert call_count == 1

def test_enabled_false_iter_succeeds_on_first_attempt(self) -> None:
"""When enabled=False, the iterator protocol runs the body once and stops."""
call_count = 0
retrying = Retrying(
enabled=False,
stop=tenacity.stop_after_attempt(5),
wait=tenacity.wait_none(),
)
for attempt in retrying:
with attempt:
call_count += 1
assert call_count == 1


class TestRetryWith:
def test_redefine_wait(self) -> None:
Expand Down
Loading