python-backoff 2.4.0__tar.gz → 3.1.0__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.
- {python_backoff-2.4.0 → python_backoff-3.1.0}/.gitignore +2 -0
- {python_backoff-2.4.0 → python_backoff-3.1.0}/PKG-INFO +12 -7
- {python_backoff-2.4.0 → python_backoff-3.1.0}/README.rst +6 -2
- {python_backoff-2.4.0 → python_backoff-3.1.0}/backoff/__init__.py +8 -5
- {python_backoff-2.4.0 → python_backoff-3.1.0}/backoff/_async.py +29 -20
- {python_backoff-2.4.0 → python_backoff-3.1.0}/backoff/_common.py +24 -49
- {python_backoff-2.4.0 → python_backoff-3.1.0}/backoff/_decorator.py +117 -63
- {python_backoff-2.4.0 → python_backoff-3.1.0}/backoff/_sync.py +16 -14
- {python_backoff-2.4.0 → python_backoff-3.1.0}/backoff/_typing.py +13 -23
- python_backoff-3.1.0/backoff/_wait_gen.py +187 -0
- python_backoff-3.1.0/backoff/types.py +6 -0
- python_backoff-3.1.0/docs/advanced/combining-decorators.md +123 -0
- python_backoff-3.1.0/docs/advanced/custom-strategies.md +144 -0
- python_backoff-3.1.0/docs/advanced/runtime-config.md +102 -0
- python_backoff-3.1.0/docs/api/backoff/index.md +1 -0
- python_backoff-3.1.0/docs/api/summary.md +1 -0
- python_backoff-3.1.0/docs/changelog.md +1 -0
- python_backoff-3.1.0/docs/examples.md +492 -0
- python_backoff-3.1.0/docs/faq.md +454 -0
- python_backoff-3.1.0/docs/getting-started.md +206 -0
- python_backoff-3.1.0/docs/index.md +73 -0
- python_backoff-3.1.0/docs/user-guide/async.md +194 -0
- python_backoff-3.1.0/docs/user-guide/configuration.md +397 -0
- python_backoff-3.1.0/docs/user-guide/context-manager.md +63 -0
- python_backoff-3.1.0/docs/user-guide/decorators.md +248 -0
- python_backoff-3.1.0/docs/user-guide/event-handlers.md +435 -0
- python_backoff-3.1.0/docs/user-guide/logging.md +234 -0
- python_backoff-3.1.0/docs/user-guide/wait-strategies.md +333 -0
- {python_backoff-2.4.0 → python_backoff-3.1.0}/pyproject.toml +37 -12
- python_backoff-3.1.0/tests/common.py +43 -0
- python_backoff-3.1.0/tests/conftest.py +10 -0
- {python_backoff-2.4.0 → python_backoff-3.1.0}/tests/test_backoff.py +361 -308
- {python_backoff-2.4.0 → python_backoff-3.1.0}/tests/test_backoff_async.py +345 -272
- {python_backoff-2.4.0 → python_backoff-3.1.0}/tests/test_integration.py +16 -13
- {python_backoff-2.4.0 → python_backoff-3.1.0}/tests/test_retry_context.py +77 -8
- python_backoff-3.1.0/tests/test_types.py +4 -0
- {python_backoff-2.4.0 → python_backoff-3.1.0}/tests/test_wait_gen.py +39 -0
- python_backoff-3.1.0/tests/typing/__init__.py +0 -0
- python_backoff-2.4.0/tests/test_typing.py → python_backoff-3.1.0/tests/typing/basic.py +3 -3
- python_backoff-3.1.0/tests/typing/decorators.py +54 -0
- python_backoff-3.1.0/tests/typing/pyrefly.py +21 -0
- python_backoff-2.4.0/backoff/_wait_gen.py +0 -113
- python_backoff-2.4.0/backoff/types.py +0 -5
- python_backoff-2.4.0/tests/common.py +0 -45
- python_backoff-2.4.0/tests/test_types.py +0 -3
- {python_backoff-2.4.0 → python_backoff-3.1.0}/LICENSE +0 -0
- {python_backoff-2.4.0 → python_backoff-3.1.0}/backoff/_jitter.py +0 -0
- {python_backoff-2.4.0 → python_backoff-3.1.0}/backoff/py.typed +0 -0
- {python_backoff-2.4.0 → python_backoff-3.1.0}/tests/__init__.py +0 -0
- {python_backoff-2.4.0 → python_backoff-3.1.0}/tests/test_jitter.py +0 -0
- {python_backoff-2.4.0 → python_backoff-3.1.0}/tests/test_package.py +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: python-backoff
|
|
3
|
-
Version:
|
|
4
|
-
|
|
3
|
+
Version: 3.1.0
|
|
4
|
+
Import-Name: backoff
|
|
5
|
+
Summary: Function decoration and context managers for backoff and retry
|
|
5
6
|
Project-URL: Documentation, https://python-backoff.github.io/backoff
|
|
6
7
|
Project-URL: Repository, https://github.com/python-backoff/backoff
|
|
7
8
|
Author-email: Bob Green <rgreen@aquent.com>
|
|
@@ -15,17 +16,17 @@ Classifier: Natural Language :: English
|
|
|
15
16
|
Classifier: Operating System :: OS Independent
|
|
16
17
|
Classifier: Programming Language :: Python
|
|
17
18
|
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.14
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.15
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.16
|
|
25
26
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
26
27
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
28
|
Classifier: Topic :: Utilities
|
|
28
|
-
Requires-Python: >=3.
|
|
29
|
+
Requires-Python: >=3.10
|
|
29
30
|
Description-Content-Type: text/x-rst
|
|
30
31
|
|
|
31
32
|
backoff
|
|
@@ -40,9 +41,11 @@ backoff
|
|
|
40
41
|
.. image:: https://img.shields.io/github/license/python-backoff/backoff
|
|
41
42
|
:target: https://github.com/python-backoff/backoff/blob/main/LICENSE
|
|
42
43
|
|
|
44
|
+
*Note: this is an actively maintained fork of https://github.com/litl/backoff.*
|
|
45
|
+
|
|
43
46
|
**Function decoration for backoff and retry**
|
|
44
47
|
|
|
45
|
-
This
|
|
48
|
+
This library provides function decorators which can be used to wrap a
|
|
46
49
|
function such that it will be retried until some condition is met. It
|
|
47
50
|
is meant to be of use when accessing unreliable resources with the
|
|
48
51
|
potential for intermittent failures i.e. network resources and external
|
|
@@ -53,6 +56,8 @@ Decorators support both regular functions for synchronous code and
|
|
|
53
56
|
`asyncio <https://docs.python.org/3/library/asyncio.html>`__'s coroutines
|
|
54
57
|
for asynchronous code.
|
|
55
58
|
|
|
59
|
+
Context managers are also available to enable retries of arbitrary code blocks.
|
|
60
|
+
|
|
56
61
|
Examples
|
|
57
62
|
========
|
|
58
63
|
|
|
@@ -183,7 +188,7 @@ The ``on_predicate`` decorator is used to retry when a particular
|
|
|
183
188
|
condition is true of the return value of the target function. This may
|
|
184
189
|
be useful when polling a resource for externally generated content.
|
|
185
190
|
|
|
186
|
-
Here's an example which uses a
|
|
191
|
+
Here's an example which uses a Fibonacci sequence backoff when the
|
|
187
192
|
return value of the target function is the empty list:
|
|
188
193
|
|
|
189
194
|
.. code-block:: python
|
|
@@ -10,9 +10,11 @@ backoff
|
|
|
10
10
|
.. image:: https://img.shields.io/github/license/python-backoff/backoff
|
|
11
11
|
:target: https://github.com/python-backoff/backoff/blob/main/LICENSE
|
|
12
12
|
|
|
13
|
+
*Note: this is an actively maintained fork of https://github.com/litl/backoff.*
|
|
14
|
+
|
|
13
15
|
**Function decoration for backoff and retry**
|
|
14
16
|
|
|
15
|
-
This
|
|
17
|
+
This library provides function decorators which can be used to wrap a
|
|
16
18
|
function such that it will be retried until some condition is met. It
|
|
17
19
|
is meant to be of use when accessing unreliable resources with the
|
|
18
20
|
potential for intermittent failures i.e. network resources and external
|
|
@@ -23,6 +25,8 @@ Decorators support both regular functions for synchronous code and
|
|
|
23
25
|
`asyncio <https://docs.python.org/3/library/asyncio.html>`__'s coroutines
|
|
24
26
|
for asynchronous code.
|
|
25
27
|
|
|
28
|
+
Context managers are also available to enable retries of arbitrary code blocks.
|
|
29
|
+
|
|
26
30
|
Examples
|
|
27
31
|
========
|
|
28
32
|
|
|
@@ -153,7 +157,7 @@ The ``on_predicate`` decorator is used to retry when a particular
|
|
|
153
157
|
condition is true of the return value of the target function. This may
|
|
154
158
|
be useful when polling a resource for externally generated content.
|
|
155
159
|
|
|
156
|
-
Here's an example which uses a
|
|
160
|
+
Here's an example which uses a Fibonacci sequence backoff when the
|
|
157
161
|
return value of the target function is the empty list:
|
|
158
162
|
|
|
159
163
|
.. code-block:: python
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Function decoration for backoff and retry
|
|
2
|
+
Function decoration for backoff and retry.
|
|
3
3
|
|
|
4
4
|
This module provides function decorators which can be used to wrap a
|
|
5
5
|
function such that it will be retried until some condition is met. It
|
|
@@ -8,16 +8,19 @@ potential for intermittent failures i.e. network resources and external
|
|
|
8
8
|
APIs. Somewhat more generally, it may also be of use for dynamically
|
|
9
9
|
polling resources for externally generated content.
|
|
10
10
|
|
|
11
|
-
For examples and full documentation see
|
|
12
|
-
https://
|
|
11
|
+
For examples and full documentation, see
|
|
12
|
+
[the official documentation](https://backoff.readthedocs.io/en/latest/).
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
+
from backoff._common import Attempt
|
|
15
16
|
from backoff._decorator import aretry_context, on_exception, on_predicate, retry_context
|
|
16
17
|
from backoff._jitter import full_jitter, random_jitter
|
|
17
|
-
from backoff._wait_gen import constant, decay, expo, fibo, runtime
|
|
18
|
+
from backoff._wait_gen import capped, constant, decay, expo, fibo, runtime
|
|
18
19
|
|
|
19
20
|
__all__ = [
|
|
21
|
+
"Attempt",
|
|
20
22
|
"aretry_context",
|
|
23
|
+
"capped",
|
|
21
24
|
"constant",
|
|
22
25
|
"decay",
|
|
23
26
|
"expo",
|
|
@@ -30,4 +33,4 @@ __all__ = [
|
|
|
30
33
|
"runtime",
|
|
31
34
|
]
|
|
32
35
|
|
|
33
|
-
__version__ = "
|
|
36
|
+
__version__ = "3.1.0"
|
|
@@ -3,13 +3,15 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import functools
|
|
5
5
|
import inspect
|
|
6
|
-
from
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
7
8
|
|
|
8
|
-
from backoff._common import
|
|
9
|
+
from backoff._common import Attempt, _RetryState
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
11
12
|
import sys
|
|
12
13
|
from collections.abc import AsyncGenerator, Coroutine, Iterable
|
|
14
|
+
from typing import ParamSpec
|
|
13
15
|
|
|
14
16
|
from backoff._typing import (
|
|
15
17
|
ContextDetails,
|
|
@@ -25,11 +27,6 @@ if TYPE_CHECKING:
|
|
|
25
27
|
_WaitGenerator,
|
|
26
28
|
)
|
|
27
29
|
|
|
28
|
-
if sys.version_info >= (3, 10):
|
|
29
|
-
from typing import ParamSpec
|
|
30
|
-
else:
|
|
31
|
-
from typing_extensions import ParamSpec
|
|
32
|
-
|
|
33
30
|
if sys.version_info >= (3, 11):
|
|
34
31
|
from typing import Unpack
|
|
35
32
|
else:
|
|
@@ -76,26 +73,27 @@ async def _call_handlers(
|
|
|
76
73
|
"kwargs": kwargs,
|
|
77
74
|
"tries": tries,
|
|
78
75
|
"elapsed": elapsed,
|
|
76
|
+
**extra,
|
|
79
77
|
}
|
|
80
|
-
# pyrefly: ignore [no-matching-overload]
|
|
81
|
-
details.update(extra)
|
|
82
78
|
for handler in handlers:
|
|
83
79
|
await handler(details)
|
|
84
80
|
|
|
85
81
|
|
|
86
82
|
def retry_predicate(
|
|
87
|
-
target: Callable[P, T],
|
|
83
|
+
target: Callable[P, Coroutine[object, object, T]],
|
|
88
84
|
wait_gen: _WaitGenerator,
|
|
89
85
|
predicate: _Predicate[T],
|
|
90
86
|
*,
|
|
91
87
|
max_tries: _MaybeCallable[int] | None,
|
|
92
88
|
max_time: _MaybeCallable[float] | None,
|
|
93
89
|
jitter: _Jitterer | None,
|
|
90
|
+
on_try: Iterable[_Handler],
|
|
94
91
|
on_success: Iterable[_Handler],
|
|
95
92
|
on_backoff: Iterable[_Handler],
|
|
96
93
|
on_giveup: Iterable[_Handler],
|
|
97
94
|
wait_gen_kwargs: dict[str, Any],
|
|
98
|
-
) -> Callable[P, T]:
|
|
95
|
+
) -> Callable[P, Coroutine[object, object, T]]:
|
|
96
|
+
on_try = _ensure_coroutines(on_try)
|
|
99
97
|
on_success = _ensure_coroutines(on_success)
|
|
100
98
|
on_backoff = _ensure_coroutines(on_backoff)
|
|
101
99
|
on_giveup = _ensure_coroutines(on_giveup)
|
|
@@ -116,14 +114,16 @@ def retry_predicate(
|
|
|
116
114
|
)
|
|
117
115
|
while True:
|
|
118
116
|
state.start_attempt()
|
|
119
|
-
ret = await target(*args, **kwargs)
|
|
120
117
|
details: _BaseDetails = {
|
|
121
118
|
"target": target,
|
|
122
119
|
"args": args,
|
|
123
120
|
"kwargs": kwargs,
|
|
124
121
|
"tries": state.tries,
|
|
125
|
-
"elapsed": state.
|
|
122
|
+
"elapsed": state.elapsed,
|
|
126
123
|
}
|
|
124
|
+
await _call_handlers(on_try, **details)
|
|
125
|
+
ret = await target(*args, **kwargs)
|
|
126
|
+
details["elapsed"] = state.record_elapsed()
|
|
127
127
|
|
|
128
128
|
if predicate(ret):
|
|
129
129
|
if state.exhausted():
|
|
@@ -154,7 +154,7 @@ def retry_predicate(
|
|
|
154
154
|
|
|
155
155
|
return ret
|
|
156
156
|
|
|
157
|
-
return retry
|
|
157
|
+
return retry
|
|
158
158
|
|
|
159
159
|
|
|
160
160
|
def _adapt_context_handlers(
|
|
@@ -177,7 +177,7 @@ def _adapt_context_handlers(
|
|
|
177
177
|
|
|
178
178
|
|
|
179
179
|
def retry_exception(
|
|
180
|
-
target: Callable[P, T],
|
|
180
|
+
target: Callable[P, Coroutine[object, object, T]],
|
|
181
181
|
wait_gen: _WaitGenerator,
|
|
182
182
|
exception: _MaybeTuple[type[Exception]],
|
|
183
183
|
*,
|
|
@@ -185,12 +185,14 @@ def retry_exception(
|
|
|
185
185
|
max_time: _MaybeCallable[float] | None,
|
|
186
186
|
jitter: _Jitterer | None,
|
|
187
187
|
giveup: _Predicate[Exception],
|
|
188
|
+
on_try: Iterable[_Handler],
|
|
188
189
|
on_success: Iterable[_Handler],
|
|
189
190
|
on_backoff: Iterable[_Handler],
|
|
190
191
|
on_giveup: Iterable[_Handler],
|
|
191
192
|
raise_on_giveup: bool,
|
|
192
193
|
wait_gen_kwargs: dict[str, Any],
|
|
193
|
-
) -> Callable[P, T]:
|
|
194
|
+
) -> Callable[P, Coroutine[object, object, T]]:
|
|
195
|
+
on_try = _ensure_coroutines(on_try)
|
|
194
196
|
on_success = _ensure_coroutines(on_success)
|
|
195
197
|
on_backoff = _ensure_coroutines(on_backoff)
|
|
196
198
|
on_giveup = _ensure_coroutines(on_giveup)
|
|
@@ -214,6 +216,7 @@ def retry_exception(
|
|
|
214
216
|
max_time=max_time,
|
|
215
217
|
jitter=jitter,
|
|
216
218
|
giveup=giveup,
|
|
219
|
+
on_try=_adapt_context_handlers(on_try, target, args, kwargs),
|
|
217
220
|
on_success=_adapt_context_handlers(on_success, target, args, kwargs),
|
|
218
221
|
on_backoff=_adapt_context_handlers(on_backoff, target, args, kwargs),
|
|
219
222
|
on_giveup=_adapt_context_handlers(on_giveup, target, args, kwargs),
|
|
@@ -221,11 +224,11 @@ def retry_exception(
|
|
|
221
224
|
wait_gen_kwargs=wait_gen_kwargs,
|
|
222
225
|
):
|
|
223
226
|
with attempt:
|
|
224
|
-
ret = await target(*args, **kwargs)
|
|
227
|
+
ret = await target(*args, **kwargs)
|
|
225
228
|
|
|
226
229
|
return ret
|
|
227
230
|
|
|
228
|
-
return retry
|
|
231
|
+
return retry
|
|
229
232
|
|
|
230
233
|
|
|
231
234
|
async def _dispatch_handlers(
|
|
@@ -243,12 +246,13 @@ async def aretry_context(
|
|
|
243
246
|
max_time: _MaybeCallable[float] | None,
|
|
244
247
|
jitter: _Jitterer | None,
|
|
245
248
|
giveup: _Predicate[BaseException],
|
|
249
|
+
on_try: Iterable[_ContextHandler],
|
|
246
250
|
on_success: Iterable[_ContextHandler],
|
|
247
251
|
on_backoff: Iterable[_ContextHandler],
|
|
248
252
|
on_giveup: Iterable[_ContextHandler],
|
|
249
253
|
raise_on_giveup: bool,
|
|
250
254
|
wait_gen_kwargs: dict[str, Any],
|
|
251
|
-
) -> AsyncGenerator[
|
|
255
|
+
) -> AsyncGenerator[Attempt, None]:
|
|
252
256
|
giveup = _ensure_coroutine(giveup)
|
|
253
257
|
|
|
254
258
|
state = _RetryState(
|
|
@@ -259,7 +263,12 @@ async def aretry_context(
|
|
|
259
263
|
)
|
|
260
264
|
while True:
|
|
261
265
|
state.start_attempt()
|
|
262
|
-
attempt =
|
|
266
|
+
attempt = Attempt(exception)
|
|
267
|
+
await _dispatch_handlers(
|
|
268
|
+
handlers=on_try,
|
|
269
|
+
tries=state.tries,
|
|
270
|
+
elapsed=state.elapsed,
|
|
271
|
+
)
|
|
263
272
|
yield attempt
|
|
264
273
|
elapsed = state.record_elapsed()
|
|
265
274
|
|
|
@@ -4,16 +4,18 @@ import functools
|
|
|
4
4
|
import logging
|
|
5
5
|
import time
|
|
6
6
|
import traceback
|
|
7
|
-
import
|
|
8
|
-
from typing import TYPE_CHECKING, Any,
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
import sys
|
|
12
|
-
from collections.abc import Generator, Iterable
|
|
12
|
+
from collections.abc import Callable, Generator, Iterable
|
|
13
13
|
|
|
14
14
|
from backoff._typing import (
|
|
15
15
|
ContextDetails,
|
|
16
16
|
Details,
|
|
17
|
+
_AnyLogger,
|
|
18
|
+
_AnyLoggerOrName,
|
|
17
19
|
_ContextHandler,
|
|
18
20
|
_Jitterer,
|
|
19
21
|
_MaybeCallable,
|
|
@@ -25,6 +27,8 @@ if TYPE_CHECKING:
|
|
|
25
27
|
else:
|
|
26
28
|
from typing_extensions import Self
|
|
27
29
|
|
|
30
|
+
ExceptionTypes: TypeAlias = type[Exception] | tuple[type[Exception], ...]
|
|
31
|
+
|
|
28
32
|
|
|
29
33
|
# Use module-specific logger with a default null handler.
|
|
30
34
|
_logger = logging.getLogger("backoff")
|
|
@@ -39,7 +43,7 @@ _HandlerT = TypeVar("_HandlerT")
|
|
|
39
43
|
def _maybe_call(f: _MaybeCallable[T] | None, *args: Any, **kwargs: Any) -> T | None:
|
|
40
44
|
if callable(f):
|
|
41
45
|
try:
|
|
42
|
-
return f(*args, **kwargs) # ty:ignore[
|
|
46
|
+
return f(*args, **kwargs) # ty:ignore[invalid-return-type]
|
|
43
47
|
except TypeError:
|
|
44
48
|
return f # type: ignore[return-value] # ty:ignore[invalid-return-type]
|
|
45
49
|
else:
|
|
@@ -64,18 +68,7 @@ def _next_wait(
|
|
|
64
68
|
max_time: float | None,
|
|
65
69
|
) -> float:
|
|
66
70
|
value = wait.send(send_value)
|
|
67
|
-
|
|
68
|
-
seconds = jitter(value) if jitter is not None else value
|
|
69
|
-
except TypeError:
|
|
70
|
-
warnings.warn(
|
|
71
|
-
"Nullary jitter function signature is deprecated. Use "
|
|
72
|
-
"unary signature accepting a wait value in seconds and "
|
|
73
|
-
"returning a jittered version of it.",
|
|
74
|
-
DeprecationWarning,
|
|
75
|
-
stacklevel=2,
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
seconds = value + jitter() # type: ignore[call-arg, misc] # ty:ignore[missing-argument, call-non-callable]
|
|
71
|
+
seconds = jitter(value) if jitter is not None else value
|
|
79
72
|
|
|
80
73
|
# don't sleep longer than remaining allotted max_time
|
|
81
74
|
if max_time is not None:
|
|
@@ -132,7 +125,8 @@ class _RetryState:
|
|
|
132
125
|
return _next_wait(self.wait, send_value, jitter, self.elapsed, self.max_time)
|
|
133
126
|
|
|
134
127
|
|
|
135
|
-
|
|
128
|
+
@dataclass(slots=True)
|
|
129
|
+
class Attempt:
|
|
136
130
|
"""A single attempt yielded by `retry_context`/`aretry_context`.
|
|
137
131
|
|
|
138
132
|
Used as `with attempt: ...`. Exceptions matching `exception_types` are
|
|
@@ -141,16 +135,10 @@ class _Attempt:
|
|
|
141
135
|
exception at all, is left for the `with` block's normal exit behavior.
|
|
142
136
|
"""
|
|
143
137
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
"exception_types",
|
|
147
|
-
)
|
|
138
|
+
exception_types: ExceptionTypes
|
|
139
|
+
exception: BaseException | None = field(init=False)
|
|
148
140
|
|
|
149
|
-
def
|
|
150
|
-
self,
|
|
151
|
-
exception_types: type[Exception] | tuple[type[Exception], ...],
|
|
152
|
-
) -> None:
|
|
153
|
-
self.exception_types = exception_types
|
|
141
|
+
def __post_init__(self) -> None:
|
|
154
142
|
self.exception: BaseException | None = None
|
|
155
143
|
|
|
156
144
|
def __enter__(self) -> Self:
|
|
@@ -163,7 +151,6 @@ class _Attempt:
|
|
|
163
151
|
tb: object,
|
|
164
152
|
) -> bool:
|
|
165
153
|
if exc_type is None:
|
|
166
|
-
# self.outcome = _Success()
|
|
167
154
|
return False
|
|
168
155
|
|
|
169
156
|
if not issubclass(exc_type, self.exception_types):
|
|
@@ -178,9 +165,7 @@ def _dispatch_handlers(handlers: Iterable[_ContextHandler], **details: Any) -> N
|
|
|
178
165
|
hdlr(details) # type: ignore[arg-type] # ty:ignore[invalid-argument-type]
|
|
179
166
|
|
|
180
167
|
|
|
181
|
-
def _prepare_logger(
|
|
182
|
-
logger: str | logging.Logger | logging.LoggerAdapter | None,
|
|
183
|
-
) -> logging.Logger | logging.LoggerAdapter | None:
|
|
168
|
+
def _prepare_logger(logger: _AnyLoggerOrName | None) -> _AnyLogger | None:
|
|
184
169
|
if isinstance(logger, str):
|
|
185
170
|
logger = logging.getLogger(logger)
|
|
186
171
|
return logger
|
|
@@ -192,7 +177,7 @@ def _config_handlers(
|
|
|
192
177
|
user_handlers: _HandlerT | Iterable[_HandlerT] | None,
|
|
193
178
|
*,
|
|
194
179
|
default_handler: Callable[..., None] | None = None,
|
|
195
|
-
logger:
|
|
180
|
+
logger: _AnyLogger | None = None,
|
|
196
181
|
log_level: int | None = None,
|
|
197
182
|
) -> list[_HandlerT]:
|
|
198
183
|
handlers: list[_HandlerT] = []
|
|
@@ -225,16 +210,11 @@ def _config_handlers(
|
|
|
225
210
|
|
|
226
211
|
|
|
227
212
|
# Default backoff handler
|
|
228
|
-
def _log_backoff(
|
|
229
|
-
details: Details,
|
|
230
|
-
logger: logging.Logger | logging.LoggerAdapter,
|
|
231
|
-
log_level: int,
|
|
232
|
-
) -> None:
|
|
213
|
+
def _log_backoff(details: Details, logger: _AnyLogger, log_level: int) -> None:
|
|
233
214
|
msg = "Backing off %s(...) for %.1fs (%s)"
|
|
234
|
-
log_args = [details["target"].__name__, details["wait"]] # ty:ignore[
|
|
215
|
+
log_args = [details["target"].__name__, details["wait"]] # ty:ignore[missing-attribute]
|
|
235
216
|
|
|
236
|
-
exc
|
|
237
|
-
if exc is not None:
|
|
217
|
+
if (exc := details.get("exception")) is not None:
|
|
238
218
|
exc_fmt = traceback.format_exception_only(type(exc), exc)[-1]
|
|
239
219
|
log_args.append(exc_fmt.rstrip("\n"))
|
|
240
220
|
else:
|
|
@@ -243,16 +223,11 @@ def _log_backoff(
|
|
|
243
223
|
|
|
244
224
|
|
|
245
225
|
# Default giveup handler
|
|
246
|
-
def _log_giveup(
|
|
247
|
-
details: Details,
|
|
248
|
-
logger: logging.Logger | logging.LoggerAdapter,
|
|
249
|
-
log_level: int,
|
|
250
|
-
) -> None:
|
|
226
|
+
def _log_giveup(details: Details, logger: _AnyLogger, log_level: int) -> None:
|
|
251
227
|
msg = "Giving up %s(...) after %d tries (%s)"
|
|
252
|
-
log_args = [details["target"].__name__, details["tries"]] # ty:ignore[
|
|
228
|
+
log_args = [details["target"].__name__, details["tries"]] # ty:ignore[missing-attribute]
|
|
253
229
|
|
|
254
|
-
exc
|
|
255
|
-
if exc is not None:
|
|
230
|
+
if (exc := details.get("exception")) is not None:
|
|
256
231
|
exc_fmt = traceback.format_exception_only(type(exc), exc)[-1]
|
|
257
232
|
log_args.append(exc_fmt.rstrip("\n"))
|
|
258
233
|
else:
|
|
@@ -266,7 +241,7 @@ def _log_giveup(
|
|
|
266
241
|
# directly since it's no longer the active exception by this point).
|
|
267
242
|
def _log_backoff_context(
|
|
268
243
|
details: ContextDetails,
|
|
269
|
-
logger:
|
|
244
|
+
logger: _AnyLogger,
|
|
270
245
|
log_level: int,
|
|
271
246
|
) -> None:
|
|
272
247
|
logger.log(
|
|
@@ -280,7 +255,7 @@ def _log_backoff_context(
|
|
|
280
255
|
# Default giveup handler for retry_context/aretry_context.
|
|
281
256
|
def _log_giveup_context(
|
|
282
257
|
details: ContextDetails,
|
|
283
|
-
logger: logging.Logger | logging.LoggerAdapter,
|
|
258
|
+
logger: logging.Logger | logging.LoggerAdapter[Any],
|
|
284
259
|
log_level: int,
|
|
285
260
|
) -> None:
|
|
286
261
|
logger.log(
|