tenacity
Advanced tools
| --- | ||
| fixes: | ||
| - | | ||
| Avoid overwriting local contexts when applying the retry decorator. |
| 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", | ||
| ] |
+1
-1
| Metadata-Version: 2.1 | ||
| Name: tenacity | ||
| Version: 8.4.1 | ||
| Version: 8.4.2 | ||
| Summary: Retry code until it succeeds | ||
@@ -5,0 +5,0 @@ Home-page: https://github.com/jd/tenacity |
| Metadata-Version: 2.1 | ||
| Name: tenacity | ||
| Version: 8.4.1 | ||
| Version: 8.4.2 | ||
| Summary: Retry code until it succeeds | ||
@@ -5,0 +5,0 @@ Home-page: https://github.com/jd/tenacity |
@@ -40,2 +40,3 @@ .editorconfig | ||
| releasenotes/notes/fix-async-loop-with-result-f68e913ccb425aca.yaml | ||
| releasenotes/notes/fix-local-context-overwrite-94190ba06a481631.yaml | ||
| releasenotes/notes/fix-setuptools-config-3af71aa3592b6948.yaml | ||
@@ -77,4 +78,5 @@ releasenotes/notes/fix-wait-typing-b26eecdb6cc0a1de.yaml | ||
| tests/test_asyncio.py | ||
| tests/test_issue_478.py | ||
| tests/test_tenacity.py | ||
| tests/test_tornado.py | ||
| tests/test_utils.py |
@@ -332,3 +332,7 @@ # Copyright 2016-2018 Julien Danjou | ||
| def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any: | ||
| return self(f, *args, **kw) | ||
| # Always create a copy to prevent overwriting the local contexts when | ||
| # calling the same wrapped functions multiple times in the same stack | ||
| copy = self.copy() | ||
| wrapped_f.statistics = copy.statistics # type: ignore[attr-defined] | ||
| return copy(f, *args, **kw) | ||
@@ -338,4 +342,6 @@ def retry_with(*args: t.Any, **kwargs: t.Any) -> WrappedFn: | ||
| wrapped_f.retry = self # type: ignore[attr-defined] | ||
| # Preserve attributes | ||
| wrapped_f.retry = wrapped_f # type: ignore[attr-defined] | ||
| wrapped_f.retry_with = retry_with # type: ignore[attr-defined] | ||
| wrapped_f.statistics = {} # type: ignore[attr-defined] | ||
@@ -342,0 +348,0 @@ return wrapped_f # type: ignore[return-value] |
@@ -178,3 +178,3 @@ # Copyright 2016 Étienne Bersac | ||
| def wraps(self, fn: WrappedFn) -> WrappedFn: | ||
| fn = super().wraps(fn) | ||
| wrapped = super().wraps(fn) | ||
| # Ensure wrapper is recognized as a coroutine function. | ||
@@ -186,7 +186,12 @@ | ||
| async def async_wrapped(*args: t.Any, **kwargs: t.Any) -> t.Any: | ||
| return await fn(*args, **kwargs) | ||
| # Always create a copy to prevent overwriting the local contexts when | ||
| # calling the same wrapped functions multiple times in the same stack | ||
| copy = self.copy() | ||
| async_wrapped.statistics = copy.statistics # type: ignore[attr-defined] | ||
| return await copy(fn, *args, **kwargs) | ||
| # Preserve attributes | ||
| async_wrapped.retry = fn.retry # type: ignore[attr-defined] | ||
| async_wrapped.retry_with = fn.retry_with # type: ignore[attr-defined] | ||
| async_wrapped.retry = async_wrapped # type: ignore[attr-defined] | ||
| async_wrapped.retry_with = wrapped.retry_with # type: ignore[attr-defined] | ||
| async_wrapped.statistics = {} # type: ignore[attr-defined] | ||
@@ -193,0 +198,0 @@ return async_wrapped # type: ignore[return-value] |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
214451
1.99%81
2.53%3643
2.79%