-
-
Notifications
You must be signed in to change notification settings - Fork 283
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Avoid overwriting local contexts with retry decorator (#479)
* Avoid overwriting local contexts with retry decorator * Add reno release note
- Loading branch information
Showing
4 changed files
with
139 additions
and
6 deletions.
There are no files selected for viewing
4 changes: 4 additions & 0 deletions
4
releasenotes/notes/fix-local-context-overwrite-94190ba06a481631.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
--- | ||
fixes: | ||
- | | ||
Avoid overwriting local contexts when applying the retry decorator. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import asyncio | ||
import typing | ||
import unittest | ||
|
||
from functools import wraps | ||
|
||
from tenacity import RetryCallState, retry | ||
|
||
|
||
def asynctest( | ||
callable_: typing.Callable[..., typing.Any], | ||
) -> typing.Callable[..., typing.Any]: | ||
@wraps(callable_) | ||
def wrapper(*a: typing.Any, **kw: typing.Any) -> typing.Any: | ||
loop = asyncio.get_event_loop() | ||
return loop.run_until_complete(callable_(*a, **kw)) | ||
|
||
return wrapper | ||
|
||
|
||
MAX_RETRY_FIX_ATTEMPTS = 2 | ||
|
||
|
||
class TestIssue478(unittest.TestCase): | ||
def test_issue(self) -> None: | ||
results = [] | ||
|
||
def do_retry(retry_state: RetryCallState) -> bool: | ||
outcome = retry_state.outcome | ||
assert outcome | ||
ex = outcome.exception() | ||
_subject_: str = retry_state.args[0] | ||
|
||
if _subject_ == "Fix": # no retry on fix failure | ||
return False | ||
|
||
if retry_state.attempt_number >= MAX_RETRY_FIX_ATTEMPTS: | ||
return False | ||
|
||
if ex: | ||
do_fix_work() | ||
return True | ||
|
||
return False | ||
|
||
@retry(reraise=True, retry=do_retry) | ||
def _do_work(subject: str) -> None: | ||
if subject == "Error": | ||
results.append(f"{subject} is not working") | ||
raise Exception(f"{subject} is not working") | ||
results.append(f"{subject} is working") | ||
|
||
def do_any_work(subject: str) -> None: | ||
_do_work(subject) | ||
|
||
def do_fix_work() -> None: | ||
_do_work("Fix") | ||
|
||
try: | ||
do_any_work("Error") | ||
except Exception as exc: | ||
assert str(exc) == "Error is not working" | ||
else: | ||
assert False, "No exception caught" | ||
|
||
assert results == [ | ||
"Error is not working", | ||
"Fix is working", | ||
"Error is not working", | ||
] | ||
|
||
@asynctest | ||
async def test_async(self) -> None: | ||
results = [] | ||
|
||
async def do_retry(retry_state: RetryCallState) -> bool: | ||
outcome = retry_state.outcome | ||
assert outcome | ||
ex = outcome.exception() | ||
_subject_: str = retry_state.args[0] | ||
|
||
if _subject_ == "Fix": # no retry on fix failure | ||
return False | ||
|
||
if retry_state.attempt_number >= MAX_RETRY_FIX_ATTEMPTS: | ||
return False | ||
|
||
if ex: | ||
await do_fix_work() | ||
return True | ||
|
||
return False | ||
|
||
@retry(reraise=True, retry=do_retry) | ||
async def _do_work(subject: str) -> None: | ||
if subject == "Error": | ||
results.append(f"{subject} is not working") | ||
raise Exception(f"{subject} is not working") | ||
results.append(f"{subject} is working") | ||
|
||
async def do_any_work(subject: str) -> None: | ||
await _do_work(subject) | ||
|
||
async def do_fix_work() -> None: | ||
await _do_work("Fix") | ||
|
||
try: | ||
await do_any_work("Error") | ||
except Exception as exc: | ||
assert str(exc) == "Error is not working" | ||
else: | ||
assert False, "No exception caught" | ||
|
||
assert results == [ | ||
"Error is not working", | ||
"Fix is working", | ||
"Error is not working", | ||
] |
a15fa64
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.
6