-
-
Notifications
You must be signed in to change notification settings - Fork 291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for an async defined retry and callback_error_retry #289
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
--- | ||
prelude: > | ||
Example use cases: | ||
- if we get logged out from the server | ||
- if authentication tokens expire | ||
We want to be able to automatically refresh our session by calling a specific | ||
function. This function can be asynchronously defined. | ||
features: | ||
- | | ||
Asynchronous defined retry callbacks: | ||
- retry | ||
- retry_error_callback | ||
issues: | ||
- | | ||
#249 | ||
|
||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,10 +15,17 @@ | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
try: | ||
from inspect import iscoroutinefunction | ||
except ImportError: | ||
iscoroutinefunction = None | ||
|
||
import sys | ||
from asyncio import sleep | ||
|
||
from tenacity import AttemptManager | ||
import six | ||
|
||
from tenacity import AttemptManager, RetryAction, TryAgain | ||
from tenacity import BaseRetrying | ||
from tenacity import DoAttempt | ||
from tenacity import DoSleep | ||
|
@@ -30,12 +37,58 @@ def __init__(self, sleep=sleep, **kwargs): | |
super(AsyncRetrying, self).__init__(**kwargs) | ||
self.sleep = sleep | ||
|
||
async def iter(self, retry_state): # noqa | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why the I'm ok on the idea, but the implementation has too much copy pasting from the parent |
||
fut = retry_state.outcome | ||
if fut is None: | ||
if self.before is not None: | ||
self.before(retry_state) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you also add async support for before and after callbacks? |
||
return DoAttempt() | ||
|
||
is_explicit_retry = retry_state.outcome.failed and isinstance( | ||
retry_state.outcome.exception(), TryAgain | ||
) | ||
if iscoroutinefunction(self.retry): | ||
should_retry = await self.retry(retry_state=retry_state) | ||
else: | ||
should_retry = self.retry(retry_state=retry_state) | ||
if not (is_explicit_retry or should_retry): | ||
return fut.result() | ||
|
||
if self.after is not None: | ||
self.after(retry_state=retry_state) | ||
|
||
self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start | ||
if self.stop(retry_state=retry_state): | ||
if self.retry_error_callback: | ||
if iscoroutinefunction(self.retry_error_callback): | ||
return await self.retry_error_callback(retry_state=retry_state) | ||
else: | ||
return self.retry_error_callback(retry_state=retry_state) | ||
retry_exc = self.retry_error_cls(fut) | ||
if self.reraise: | ||
raise retry_exc.reraise() | ||
six.raise_from(retry_exc, fut.exception()) | ||
|
||
if self.wait: | ||
iteration_sleep = self.wait(retry_state=retry_state) | ||
else: | ||
iteration_sleep = 0.0 | ||
retry_state.next_action = RetryAction(iteration_sleep) | ||
retry_state.idle_for += iteration_sleep | ||
self.statistics["idle_for"] += iteration_sleep | ||
self.statistics["attempt_number"] += 1 | ||
|
||
if self.before_sleep is not None: | ||
self.before_sleep(retry_state=retry_state) | ||
|
||
return DoSleep(iteration_sleep) | ||
|
||
async def __call__(self, fn, *args, **kwargs): | ||
self.begin(fn) | ||
|
||
retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs) | ||
while True: | ||
do = self.iter(retry_state=retry_state) | ||
do = await self.iter(retry_state=retry_state) | ||
if isinstance(do, DoAttempt): | ||
try: | ||
result = await fn(*args, **kwargs) | ||
|
@@ -56,7 +109,7 @@ def __aiter__(self): | |
|
||
async def __anext__(self): | ||
while True: | ||
do = self.iter(retry_state=self._retry_state) | ||
do = await self.iter(retry_state=self._retry_state) | ||
if do is None: | ||
raise StopAsyncIteration | ||
elif isinstance(do, DoAttempt): | ||
|
@@ -69,6 +122,7 @@ async def __anext__(self): | |
|
||
def wraps(self, fn): | ||
fn = super().wraps(fn) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unrelated 😁 |
||
# Ensure wrapper is recognized as a coroutine function. | ||
|
||
async def async_wrapped(*args, **kwargs): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.