From a863c9047ac65d5462288c8f61600c3d32bb6b87 Mon Sep 17 00:00:00 2001 From: Julien Danjou Date: Tue, 24 Feb 2026 11:17:14 +0100 Subject: [PATCH] feat: add async context manager support to AttemptManager AttemptManager now supports `async with` in addition to `with`, allowing cleaner usage inside `async for` loops. Closes #469 Co-Authored-By: Claude Opus 4.6 Change-Id: If6d24d58fbb4a0eaba5b62b20f6821d2ccd073a9 --- tenacity/__init__.py | 11 +++++++++++ tests/test_asyncio.py | 15 +++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/tenacity/__init__.py b/tenacity/__init__.py index 079740f6..57af68a4 100644 --- a/tenacity/__init__.py +++ b/tenacity/__init__.py @@ -214,6 +214,17 @@ def __exit__( self.retry_state.set_result(None) return None + async def __aenter__(self) -> None: + pass + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: t.Optional["types.TracebackType"], + ) -> bool | None: + return self.__exit__(exc_type, exc_value, traceback) + class BaseRetrying(ABC): def __init__( diff --git a/tests/test_asyncio.py b/tests/test_asyncio.py index 599c4f26..0cdb8ba6 100644 --- a/tests/test_asyncio.py +++ b/tests/test_asyncio.py @@ -190,6 +190,21 @@ async def test_do_max_attempts(self) -> None: assert attempts == 3 + @asynctest + async def test_async_with_attempt_manager(self) -> None: + """AttemptManager supports async with for use inside async for.""" + attempts = 0 + retrying = tasyncio.AsyncRetrying(stop=stop_after_attempt(3)) + try: + async for attempt in retrying: + async with attempt: + attempts += 1 + raise Exception + except RetryError: + pass + + assert attempts == 3 + @asynctest async def test_reraise(self) -> None: class CustomError(Exception):