tenacity 8.4.1__tar.gz → 8.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. {tenacity-8.4.1/tenacity.egg-info → tenacity-8.4.2}/PKG-INFO +1 -1
  2. tenacity-8.4.2/releasenotes/notes/fix-local-context-overwrite-94190ba06a481631.yaml +4 -0
  3. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity/__init__.py +8 -2
  4. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity/asyncio/__init__.py +9 -4
  5. {tenacity-8.4.1 → tenacity-8.4.2/tenacity.egg-info}/PKG-INFO +1 -1
  6. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity.egg-info/SOURCES.txt +2 -0
  7. tenacity-8.4.2/tests/test_issue_478.py +118 -0
  8. {tenacity-8.4.1 → tenacity-8.4.2}/.editorconfig +0 -0
  9. {tenacity-8.4.1 → tenacity-8.4.2}/.github/dependabot.yml +0 -0
  10. {tenacity-8.4.1 → tenacity-8.4.2}/.github/workflows/ci.yaml +0 -0
  11. {tenacity-8.4.1 → tenacity-8.4.2}/.github/workflows/deploy.yaml +0 -0
  12. {tenacity-8.4.1 → tenacity-8.4.2}/.gitignore +0 -0
  13. {tenacity-8.4.1 → tenacity-8.4.2}/.mergify.yml +0 -0
  14. {tenacity-8.4.1 → tenacity-8.4.2}/.readthedocs.yml +0 -0
  15. {tenacity-8.4.1 → tenacity-8.4.2}/LICENSE +0 -0
  16. {tenacity-8.4.1 → tenacity-8.4.2}/README.rst +0 -0
  17. {tenacity-8.4.1 → tenacity-8.4.2}/doc/source/api.rst +0 -0
  18. {tenacity-8.4.1 → tenacity-8.4.2}/doc/source/changelog.rst +0 -0
  19. {tenacity-8.4.1 → tenacity-8.4.2}/doc/source/conf.py +0 -0
  20. {tenacity-8.4.1 → tenacity-8.4.2}/doc/source/index.rst +0 -0
  21. {tenacity-8.4.1 → tenacity-8.4.2}/pyproject.toml +0 -0
  22. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/Fix-tests-for-typeguard-3.x-6eebfea546b6207e.yaml +0 -0
  23. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/Use--for-formatting-and-validate-using-black-39ec9d57d4691778.yaml +0 -0
  24. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/add-async-actions-b249c527d99723bb.yaml +0 -0
  25. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/add-reno-d1ab5710f272650a.yaml +0 -0
  26. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/add-retry_except_exception_type-31b31da1924d55f4.yaml +0 -0
  27. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/add-stop-before-delay-a775f88ac872c923.yaml +0 -0
  28. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/add-test-extra-55e869261b03e56d.yaml +0 -0
  29. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/add_omitted_modules_to_import_all-2ab282f20a2c22f7.yaml +0 -0
  30. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/add_retry_if_exception_cause_type-d16b918ace4ae0ad.yaml +0 -0
  31. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/added_a_link_to_documentation-eefaf8f074b539f8.yaml +0 -0
  32. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/after_log-50f4d73b24ce9203.yaml +0 -0
  33. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/allow-mocking-of-nap-sleep-6679c50e702446f1.yaml +0 -0
  34. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/annotate_code-197b93130df14042.yaml +0 -0
  35. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/before_sleep_log-improvements-d8149274dfb37d7c.yaml +0 -0
  36. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/clarify-reraise-option-6829667eacf4f599.yaml +0 -0
  37. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/dependabot-for-github-actions-4d2464f3c0928463.yaml +0 -0
  38. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/do_not_package_tests-fe5ac61940b0a5ed.yaml +0 -0
  39. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/drop-deprecated-python-versions-69a05cb2e0f1034c.yaml +0 -0
  40. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/drop_deprecated-7ea90b212509b082.yaml +0 -0
  41. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/export-convenience-symbols-981d9611c8b754f3.yaml +0 -0
  42. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/fix-async-loop-with-result-f68e913ccb425aca.yaml +0 -0
  43. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/fix-setuptools-config-3af71aa3592b6948.yaml +0 -0
  44. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/fix-wait-typing-b26eecdb6cc0a1de.yaml +0 -0
  45. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/fix_async-52b6594c8e75c4bc.yaml +0 -0
  46. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/make-logger-more-compatible-5da1ddf1bab77047.yaml +0 -0
  47. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/no-async-iter-6132a42e52348a75.yaml +0 -0
  48. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/pr320-py3-only-wheel-tag.yaml +0 -0
  49. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/py36_plus-c425fb3aa17c6682.yaml +0 -0
  50. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/remove-py36-876c0416cf279d15.yaml +0 -0
  51. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/retrycallstate-repr-94947f7b00ee15e1.yaml +0 -0
  52. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/some-slug-for-preserve-defaults-86682846dfa18005.yaml +0 -0
  53. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/sphinx_define_error-642c9cd5c165d39a.yaml +0 -0
  54. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/support-timedelta-wait-unit-type-5ba1e9fc0fe45523.yaml +0 -0
  55. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/timedelta-for-stop-ef6bf71b88ce9988.yaml +0 -0
  56. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/trio-support-retry-22bd544800cd1f36.yaml +0 -0
  57. {tenacity-8.4.1 → tenacity-8.4.2}/releasenotes/notes/wait_exponential_jitter-6ffc81dddcbaa6d3.yaml +0 -0
  58. {tenacity-8.4.1 → tenacity-8.4.2}/reno.yaml +0 -0
  59. {tenacity-8.4.1 → tenacity-8.4.2}/setup.cfg +0 -0
  60. {tenacity-8.4.1 → tenacity-8.4.2}/setup.py +0 -0
  61. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity/_utils.py +0 -0
  62. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity/after.py +0 -0
  63. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity/asyncio/retry.py +0 -0
  64. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity/before.py +0 -0
  65. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity/before_sleep.py +0 -0
  66. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity/nap.py +0 -0
  67. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity/py.typed +0 -0
  68. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity/retry.py +0 -0
  69. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity/stop.py +0 -0
  70. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity/tornadoweb.py +0 -0
  71. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity/wait.py +0 -0
  72. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity.egg-info/dependency_links.txt +0 -0
  73. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity.egg-info/requires.txt +0 -0
  74. {tenacity-8.4.1 → tenacity-8.4.2}/tenacity.egg-info/top_level.txt +0 -0
  75. {tenacity-8.4.1 → tenacity-8.4.2}/tests/__init__.py +0 -0
  76. {tenacity-8.4.1 → tenacity-8.4.2}/tests/test_after.py +0 -0
  77. {tenacity-8.4.1 → tenacity-8.4.2}/tests/test_asyncio.py +0 -0
  78. {tenacity-8.4.1 → tenacity-8.4.2}/tests/test_tenacity.py +0 -0
  79. {tenacity-8.4.1 → tenacity-8.4.2}/tests/test_tornado.py +0 -0
  80. {tenacity-8.4.1 → tenacity-8.4.2}/tests/test_utils.py +0 -0
  81. {tenacity-8.4.1 → tenacity-8.4.2}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tenacity
3
- Version: 8.4.1
3
+ Version: 8.4.2
4
4
  Summary: Retry code until it succeeds
5
5
  Home-page: https://github.com/jd/tenacity
6
6
  Author: Julien Danjou
@@ -0,0 +1,4 @@
1
+ ---
2
+ fixes:
3
+ - |
4
+ Avoid overwriting local contexts when applying the retry decorator.
@@ -329,13 +329,19 @@ class BaseRetrying(ABC):
329
329
  f, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__")
330
330
  )
331
331
  def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any:
332
- return self(f, *args, **kw)
332
+ # Always create a copy to prevent overwriting the local contexts when
333
+ # calling the same wrapped functions multiple times in the same stack
334
+ copy = self.copy()
335
+ wrapped_f.statistics = copy.statistics # type: ignore[attr-defined]
336
+ return copy(f, *args, **kw)
333
337
 
334
338
  def retry_with(*args: t.Any, **kwargs: t.Any) -> WrappedFn:
335
339
  return self.copy(*args, **kwargs).wraps(f)
336
340
 
337
- wrapped_f.retry = self # type: ignore[attr-defined]
341
+ # Preserve attributes
342
+ wrapped_f.retry = wrapped_f # type: ignore[attr-defined]
338
343
  wrapped_f.retry_with = retry_with # type: ignore[attr-defined]
344
+ wrapped_f.statistics = {} # type: ignore[attr-defined]
339
345
 
340
346
  return wrapped_f # type: ignore[return-value]
341
347
 
@@ -175,18 +175,23 @@ class AsyncRetrying(BaseRetrying):
175
175
  raise StopAsyncIteration
176
176
 
177
177
  def wraps(self, fn: WrappedFn) -> WrappedFn:
178
- fn = super().wraps(fn)
178
+ wrapped = super().wraps(fn)
179
179
  # Ensure wrapper is recognized as a coroutine function.
180
180
 
181
181
  @functools.wraps(
182
182
  fn, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__")
183
183
  )
184
184
  async def async_wrapped(*args: t.Any, **kwargs: t.Any) -> t.Any:
185
- return await fn(*args, **kwargs)
185
+ # Always create a copy to prevent overwriting the local contexts when
186
+ # calling the same wrapped functions multiple times in the same stack
187
+ copy = self.copy()
188
+ async_wrapped.statistics = copy.statistics # type: ignore[attr-defined]
189
+ return await copy(fn, *args, **kwargs)
186
190
 
187
191
  # Preserve attributes
188
- async_wrapped.retry = fn.retry # type: ignore[attr-defined]
189
- async_wrapped.retry_with = fn.retry_with # type: ignore[attr-defined]
192
+ async_wrapped.retry = async_wrapped # type: ignore[attr-defined]
193
+ async_wrapped.retry_with = wrapped.retry_with # type: ignore[attr-defined]
194
+ async_wrapped.statistics = {} # type: ignore[attr-defined]
190
195
 
191
196
  return async_wrapped # type: ignore[return-value]
192
197
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tenacity
3
- Version: 8.4.1
3
+ Version: 8.4.2
4
4
  Summary: Retry code until it succeeds
5
5
  Home-page: https://github.com/jd/tenacity
6
6
  Author: Julien Danjou
@@ -37,6 +37,7 @@ releasenotes/notes/drop-deprecated-python-versions-69a05cb2e0f1034c.yaml
37
37
  releasenotes/notes/drop_deprecated-7ea90b212509b082.yaml
38
38
  releasenotes/notes/export-convenience-symbols-981d9611c8b754f3.yaml
39
39
  releasenotes/notes/fix-async-loop-with-result-f68e913ccb425aca.yaml
40
+ releasenotes/notes/fix-local-context-overwrite-94190ba06a481631.yaml
40
41
  releasenotes/notes/fix-setuptools-config-3af71aa3592b6948.yaml
41
42
  releasenotes/notes/fix-wait-typing-b26eecdb6cc0a1de.yaml
42
43
  releasenotes/notes/fix_async-52b6594c8e75c4bc.yaml
@@ -73,6 +74,7 @@ tenacity/asyncio/retry.py
73
74
  tests/__init__.py
74
75
  tests/test_after.py
75
76
  tests/test_asyncio.py
77
+ tests/test_issue_478.py
76
78
  tests/test_tenacity.py
77
79
  tests/test_tornado.py
78
80
  tests/test_utils.py
@@ -0,0 +1,118 @@
1
+ import asyncio
2
+ import typing
3
+ import unittest
4
+
5
+ from functools import wraps
6
+
7
+ from tenacity import RetryCallState, retry
8
+
9
+
10
+ def asynctest(
11
+ callable_: typing.Callable[..., typing.Any],
12
+ ) -> typing.Callable[..., typing.Any]:
13
+ @wraps(callable_)
14
+ def wrapper(*a: typing.Any, **kw: typing.Any) -> typing.Any:
15
+ loop = asyncio.get_event_loop()
16
+ return loop.run_until_complete(callable_(*a, **kw))
17
+
18
+ return wrapper
19
+
20
+
21
+ MAX_RETRY_FIX_ATTEMPTS = 2
22
+
23
+
24
+ class TestIssue478(unittest.TestCase):
25
+ def test_issue(self) -> None:
26
+ results = []
27
+
28
+ def do_retry(retry_state: RetryCallState) -> bool:
29
+ outcome = retry_state.outcome
30
+ assert outcome
31
+ ex = outcome.exception()
32
+ _subject_: str = retry_state.args[0]
33
+
34
+ if _subject_ == "Fix": # no retry on fix failure
35
+ return False
36
+
37
+ if retry_state.attempt_number >= MAX_RETRY_FIX_ATTEMPTS:
38
+ return False
39
+
40
+ if ex:
41
+ do_fix_work()
42
+ return True
43
+
44
+ return False
45
+
46
+ @retry(reraise=True, retry=do_retry)
47
+ def _do_work(subject: str) -> None:
48
+ if subject == "Error":
49
+ results.append(f"{subject} is not working")
50
+ raise Exception(f"{subject} is not working")
51
+ results.append(f"{subject} is working")
52
+
53
+ def do_any_work(subject: str) -> None:
54
+ _do_work(subject)
55
+
56
+ def do_fix_work() -> None:
57
+ _do_work("Fix")
58
+
59
+ try:
60
+ do_any_work("Error")
61
+ except Exception as exc:
62
+ assert str(exc) == "Error is not working"
63
+ else:
64
+ assert False, "No exception caught"
65
+
66
+ assert results == [
67
+ "Error is not working",
68
+ "Fix is working",
69
+ "Error is not working",
70
+ ]
71
+
72
+ @asynctest
73
+ async def test_async(self) -> None:
74
+ results = []
75
+
76
+ async def do_retry(retry_state: RetryCallState) -> bool:
77
+ outcome = retry_state.outcome
78
+ assert outcome
79
+ ex = outcome.exception()
80
+ _subject_: str = retry_state.args[0]
81
+
82
+ if _subject_ == "Fix": # no retry on fix failure
83
+ return False
84
+
85
+ if retry_state.attempt_number >= MAX_RETRY_FIX_ATTEMPTS:
86
+ return False
87
+
88
+ if ex:
89
+ await do_fix_work()
90
+ return True
91
+
92
+ return False
93
+
94
+ @retry(reraise=True, retry=do_retry)
95
+ async def _do_work(subject: str) -> None:
96
+ if subject == "Error":
97
+ results.append(f"{subject} is not working")
98
+ raise Exception(f"{subject} is not working")
99
+ results.append(f"{subject} is working")
100
+
101
+ async def do_any_work(subject: str) -> None:
102
+ await _do_work(subject)
103
+
104
+ async def do_fix_work() -> None:
105
+ await _do_work("Fix")
106
+
107
+ try:
108
+ await do_any_work("Error")
109
+ except Exception as exc:
110
+ assert str(exc) == "Error is not working"
111
+ else:
112
+ assert False, "No exception caught"
113
+
114
+ assert results == [
115
+ "Error is not working",
116
+ "Fix is working",
117
+ "Error is not working",
118
+ ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes