python-backoff 2.2.2rc2__py3-none-any.whl → 2.3.1__py3-none-any.whl
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.
- backoff/__init__.py +11 -11
- backoff/_async.py +56 -44
- backoff/_common.py +15 -14
- backoff/_decorator.py +39 -34
- backoff/_jitter.py +0 -2
- backoff/_sync.py +51 -39
- backoff/_typing.py +15 -17
- backoff/_wait_gen.py +7 -11
- backoff/types.py +1 -2
- {python_backoff-2.2.2rc2.dist-info → python_backoff-2.3.1.dist-info}/METADATA +109 -59
- python_backoff-2.3.1.dist-info/RECORD +14 -0
- {python_backoff-2.2.2rc2.dist-info → python_backoff-2.3.1.dist-info}/WHEEL +1 -1
- python_backoff-2.2.2rc2.dist-info/RECORD +0 -14
- {python_backoff-2.2.2rc2.dist-info → python_backoff-2.3.1.dist-info}/licenses/LICENSE +0 -0
backoff/__init__.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# coding:utf-8
|
|
2
1
|
"""
|
|
3
2
|
Function decoration for backoff and retry
|
|
4
3
|
|
|
@@ -12,20 +11,21 @@ polling resources for externally generated content.
|
|
|
12
11
|
For examples and full documentation see the README at
|
|
13
12
|
https://github.com/python-backoff/backoff
|
|
14
13
|
"""
|
|
14
|
+
|
|
15
15
|
from backoff._decorator import on_exception, on_predicate
|
|
16
16
|
from backoff._jitter import full_jitter, random_jitter
|
|
17
17
|
from backoff._wait_gen import constant, expo, fibo, runtime, decay
|
|
18
18
|
|
|
19
19
|
__all__ = [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
"on_predicate",
|
|
21
|
+
"on_exception",
|
|
22
|
+
"constant",
|
|
23
|
+
"expo",
|
|
24
|
+
"decay",
|
|
25
|
+
"fibo",
|
|
26
|
+
"runtime",
|
|
27
|
+
"full_jitter",
|
|
28
|
+
"random_jitter",
|
|
29
29
|
]
|
|
30
30
|
|
|
31
|
-
__version__ = "2.
|
|
31
|
+
__version__ = "2.3.1"
|
backoff/_async.py
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
# coding:utf-8
|
|
2
|
-
import datetime
|
|
3
|
-
import functools
|
|
4
1
|
import asyncio
|
|
2
|
+
import functools
|
|
5
3
|
import inspect
|
|
6
|
-
|
|
4
|
+
import time
|
|
7
5
|
|
|
8
|
-
from backoff._common import
|
|
6
|
+
from backoff._common import _init_wait_gen, _maybe_call, _next_wait
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
def _ensure_coroutine(coro_or_func):
|
|
12
10
|
if inspect.iscoroutinefunction(coro_or_func):
|
|
13
11
|
return coro_or_func
|
|
14
12
|
else:
|
|
13
|
+
|
|
15
14
|
@functools.wraps(coro_or_func)
|
|
16
15
|
async def f(*args, **kwargs):
|
|
17
16
|
return coro_or_func(*args, **kwargs)
|
|
17
|
+
|
|
18
18
|
return f
|
|
19
19
|
|
|
20
20
|
|
|
@@ -22,27 +22,32 @@ def _ensure_coroutines(coros_or_funcs):
|
|
|
22
22
|
return [_ensure_coroutine(f) for f in coros_or_funcs]
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
async def _call_handlers(handlers,
|
|
26
|
-
*,
|
|
27
|
-
target, args, kwargs, tries, elapsed,
|
|
28
|
-
**extra):
|
|
25
|
+
async def _call_handlers(handlers, *, target, args, kwargs, tries, elapsed, **extra):
|
|
29
26
|
details = {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
"target": target,
|
|
28
|
+
"args": args,
|
|
29
|
+
"kwargs": kwargs,
|
|
30
|
+
"tries": tries,
|
|
31
|
+
"elapsed": elapsed,
|
|
35
32
|
}
|
|
36
33
|
details.update(extra)
|
|
37
34
|
for handler in handlers:
|
|
38
35
|
await handler(details)
|
|
39
36
|
|
|
40
37
|
|
|
41
|
-
def retry_predicate(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
def retry_predicate(
|
|
39
|
+
target,
|
|
40
|
+
wait_gen,
|
|
41
|
+
predicate,
|
|
42
|
+
*,
|
|
43
|
+
max_tries,
|
|
44
|
+
max_time,
|
|
45
|
+
jitter,
|
|
46
|
+
on_success,
|
|
47
|
+
on_backoff,
|
|
48
|
+
on_giveup,
|
|
49
|
+
wait_gen_kwargs,
|
|
50
|
+
):
|
|
46
51
|
on_success = _ensure_coroutines(on_success)
|
|
47
52
|
on_backoff = _ensure_coroutines(on_backoff)
|
|
48
53
|
on_giveup = _ensure_coroutines(on_giveup)
|
|
@@ -55,17 +60,16 @@ def retry_predicate(target, wait_gen, predicate,
|
|
|
55
60
|
|
|
56
61
|
@functools.wraps(target)
|
|
57
62
|
async def retry(*args, **kwargs):
|
|
58
|
-
|
|
59
63
|
# update variables from outer function args
|
|
60
64
|
max_tries_value = _maybe_call(max_tries)
|
|
61
65
|
max_time_value = _maybe_call(max_time)
|
|
62
66
|
|
|
63
67
|
tries = 0
|
|
64
|
-
start =
|
|
68
|
+
start = time.monotonic()
|
|
65
69
|
wait = _init_wait_gen(wait_gen, wait_gen_kwargs)
|
|
66
70
|
while True:
|
|
67
71
|
tries += 1
|
|
68
|
-
elapsed =
|
|
72
|
+
elapsed = time.monotonic() - start
|
|
69
73
|
details = {
|
|
70
74
|
"target": target,
|
|
71
75
|
"args": args,
|
|
@@ -76,23 +80,22 @@ def retry_predicate(target, wait_gen, predicate,
|
|
|
76
80
|
|
|
77
81
|
ret = await target(*args, **kwargs)
|
|
78
82
|
if predicate(ret):
|
|
79
|
-
max_tries_exceeded =
|
|
80
|
-
max_time_exceeded = (
|
|
81
|
-
|
|
83
|
+
max_tries_exceeded = tries == max_tries_value
|
|
84
|
+
max_time_exceeded = (
|
|
85
|
+
max_time_value is not None and elapsed >= max_time_value
|
|
86
|
+
)
|
|
82
87
|
|
|
83
88
|
if max_tries_exceeded or max_time_exceeded:
|
|
84
89
|
await _call_handlers(on_giveup, **details, value=ret)
|
|
85
90
|
break
|
|
86
91
|
|
|
87
92
|
try:
|
|
88
|
-
seconds = _next_wait(wait, ret, jitter, elapsed,
|
|
89
|
-
max_time_value)
|
|
93
|
+
seconds = _next_wait(wait, ret, jitter, elapsed, max_time_value)
|
|
90
94
|
except StopIteration:
|
|
91
95
|
await _call_handlers(on_giveup, **details, value=ret)
|
|
92
96
|
break
|
|
93
97
|
|
|
94
|
-
await _call_handlers(on_backoff, **details, value=ret,
|
|
95
|
-
wait=seconds)
|
|
98
|
+
await _call_handlers(on_backoff, **details, value=ret, wait=seconds)
|
|
96
99
|
|
|
97
100
|
# Note: there is no convenient way to pass explicit event
|
|
98
101
|
# loop to decorator, so here we assume that either default
|
|
@@ -114,11 +117,21 @@ def retry_predicate(target, wait_gen, predicate,
|
|
|
114
117
|
return retry
|
|
115
118
|
|
|
116
119
|
|
|
117
|
-
def retry_exception(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
def retry_exception(
|
|
121
|
+
target,
|
|
122
|
+
wait_gen,
|
|
123
|
+
exception,
|
|
124
|
+
*,
|
|
125
|
+
max_tries,
|
|
126
|
+
max_time,
|
|
127
|
+
jitter,
|
|
128
|
+
giveup,
|
|
129
|
+
on_success,
|
|
130
|
+
on_backoff,
|
|
131
|
+
on_giveup,
|
|
132
|
+
raise_on_giveup,
|
|
133
|
+
wait_gen_kwargs,
|
|
134
|
+
):
|
|
122
135
|
on_success = _ensure_coroutines(on_success)
|
|
123
136
|
on_backoff = _ensure_coroutines(on_backoff)
|
|
124
137
|
on_giveup = _ensure_coroutines(on_giveup)
|
|
@@ -130,16 +143,15 @@ def retry_exception(target, wait_gen, exception,
|
|
|
130
143
|
|
|
131
144
|
@functools.wraps(target)
|
|
132
145
|
async def retry(*args, **kwargs):
|
|
133
|
-
|
|
134
146
|
max_tries_value = _maybe_call(max_tries)
|
|
135
147
|
max_time_value = _maybe_call(max_time)
|
|
136
148
|
|
|
137
149
|
tries = 0
|
|
138
|
-
start =
|
|
150
|
+
start = time.monotonic()
|
|
139
151
|
wait = _init_wait_gen(wait_gen, wait_gen_kwargs)
|
|
140
152
|
while True:
|
|
141
153
|
tries += 1
|
|
142
|
-
elapsed =
|
|
154
|
+
elapsed = time.monotonic() - start
|
|
143
155
|
details = {
|
|
144
156
|
"target": target,
|
|
145
157
|
"args": args,
|
|
@@ -152,9 +164,10 @@ def retry_exception(target, wait_gen, exception,
|
|
|
152
164
|
ret = await target(*args, **kwargs)
|
|
153
165
|
except exception as e:
|
|
154
166
|
giveup_result = await giveup(e)
|
|
155
|
-
max_tries_exceeded =
|
|
156
|
-
max_time_exceeded = (
|
|
157
|
-
|
|
167
|
+
max_tries_exceeded = tries == max_tries_value
|
|
168
|
+
max_time_exceeded = (
|
|
169
|
+
max_time_value is not None and elapsed >= max_time_value
|
|
170
|
+
)
|
|
158
171
|
|
|
159
172
|
if giveup_result or max_tries_exceeded or max_time_exceeded:
|
|
160
173
|
await _call_handlers(on_giveup, **details, exception=e)
|
|
@@ -163,14 +176,12 @@ def retry_exception(target, wait_gen, exception,
|
|
|
163
176
|
return None
|
|
164
177
|
|
|
165
178
|
try:
|
|
166
|
-
seconds = _next_wait(wait, e, jitter, elapsed,
|
|
167
|
-
max_time_value)
|
|
179
|
+
seconds = _next_wait(wait, e, jitter, elapsed, max_time_value)
|
|
168
180
|
except StopIteration:
|
|
169
181
|
await _call_handlers(on_giveup, **details, exception=e)
|
|
170
182
|
raise e
|
|
171
183
|
|
|
172
|
-
await _call_handlers(on_backoff, **details, wait=seconds,
|
|
173
|
-
exception=e)
|
|
184
|
+
await _call_handlers(on_backoff, **details, wait=seconds, exception=e)
|
|
174
185
|
|
|
175
186
|
# Note: there is no convenient way to pass explicit event
|
|
176
187
|
# loop to decorator, so here we assume that either default
|
|
@@ -186,4 +197,5 @@ def retry_exception(target, wait_gen, exception,
|
|
|
186
197
|
await _call_handlers(on_success, **details)
|
|
187
198
|
|
|
188
199
|
return ret
|
|
200
|
+
|
|
189
201
|
return retry
|
backoff/_common.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
# coding:utf-8
|
|
2
|
-
|
|
3
1
|
import functools
|
|
4
2
|
import logging
|
|
5
3
|
import sys
|
|
@@ -8,7 +6,7 @@ import warnings
|
|
|
8
6
|
|
|
9
7
|
|
|
10
8
|
# Use module-specific logger with a default null handler.
|
|
11
|
-
_logger = logging.getLogger(
|
|
9
|
+
_logger = logging.getLogger("backoff")
|
|
12
10
|
_logger.addHandler(logging.NullHandler()) # pragma: no cover
|
|
13
11
|
_logger.setLevel(logging.INFO)
|
|
14
12
|
|
|
@@ -34,10 +32,7 @@ def _init_wait_gen(wait_gen, wait_gen_kwargs):
|
|
|
34
32
|
def _next_wait(wait, send_value, jitter, elapsed, max_time):
|
|
35
33
|
value = wait.send(send_value)
|
|
36
34
|
try:
|
|
37
|
-
if jitter is not None
|
|
38
|
-
seconds = jitter(value)
|
|
39
|
-
else:
|
|
40
|
-
seconds = value
|
|
35
|
+
seconds = jitter(value) if jitter is not None else value
|
|
41
36
|
except TypeError:
|
|
42
37
|
warnings.warn(
|
|
43
38
|
"Nullary jitter function signature is deprecated. Use "
|
|
@@ -65,14 +60,20 @@ def _prepare_logger(logger):
|
|
|
65
60
|
# Configure handler list with user specified handler and optionally
|
|
66
61
|
# with a default handler bound to the specified logger.
|
|
67
62
|
def _config_handlers(
|
|
68
|
-
user_handlers,
|
|
63
|
+
user_handlers,
|
|
64
|
+
*,
|
|
65
|
+
default_handler=None,
|
|
66
|
+
logger=None,
|
|
67
|
+
log_level=None,
|
|
69
68
|
):
|
|
70
69
|
handlers = []
|
|
71
70
|
if logger is not None:
|
|
72
71
|
assert log_level is not None, "Log level is not specified"
|
|
73
72
|
# bind the specified logger to the default log handler
|
|
74
73
|
log_handler = functools.partial(
|
|
75
|
-
default_handler,
|
|
74
|
+
default_handler,
|
|
75
|
+
logger=logger,
|
|
76
|
+
log_level=log_level,
|
|
76
77
|
)
|
|
77
78
|
handlers.append(log_handler)
|
|
78
79
|
|
|
@@ -81,7 +82,7 @@ def _config_handlers(
|
|
|
81
82
|
|
|
82
83
|
# user specified handlers can either be an iterable of handlers
|
|
83
84
|
# or a single handler. either way append them to the list.
|
|
84
|
-
if hasattr(user_handlers,
|
|
85
|
+
if hasattr(user_handlers, "__iter__"):
|
|
85
86
|
# add all handlers in the iterable
|
|
86
87
|
handlers += list(user_handlers)
|
|
87
88
|
else:
|
|
@@ -94,27 +95,27 @@ def _config_handlers(
|
|
|
94
95
|
# Default backoff handler
|
|
95
96
|
def _log_backoff(details, logger, log_level):
|
|
96
97
|
msg = "Backing off %s(...) for %.1fs (%s)"
|
|
97
|
-
log_args = [details[
|
|
98
|
+
log_args = [details["target"].__name__, details["wait"]]
|
|
98
99
|
|
|
99
100
|
exc_typ, exc, _ = sys.exc_info()
|
|
100
101
|
if exc is not None:
|
|
101
102
|
exc_fmt = traceback.format_exception_only(exc_typ, exc)[-1]
|
|
102
103
|
log_args.append(exc_fmt.rstrip("\n"))
|
|
103
104
|
else:
|
|
104
|
-
log_args.append(details[
|
|
105
|
+
log_args.append(details["value"])
|
|
105
106
|
logger.log(log_level, msg, *log_args)
|
|
106
107
|
|
|
107
108
|
|
|
108
109
|
# Default giveup handler
|
|
109
110
|
def _log_giveup(details, logger, log_level):
|
|
110
111
|
msg = "Giving up %s(...) after %d tries (%s)"
|
|
111
|
-
log_args = [details[
|
|
112
|
+
log_args = [details["target"].__name__, details["tries"]]
|
|
112
113
|
|
|
113
114
|
exc_typ, exc, _ = sys.exc_info()
|
|
114
115
|
if exc is not None:
|
|
115
116
|
exc_fmt = traceback.format_exception_only(exc_typ, exc)[-1]
|
|
116
117
|
log_args.append(exc_fmt.rstrip("\n"))
|
|
117
118
|
else:
|
|
118
|
-
log_args.append(details[
|
|
119
|
+
log_args.append(details["value"])
|
|
119
120
|
|
|
120
121
|
logger.log(log_level, msg, *log_args)
|
backoff/_decorator.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# coding:utf-8
|
|
2
1
|
import inspect
|
|
3
2
|
import logging
|
|
4
3
|
import operator
|
|
@@ -8,7 +7,7 @@ from backoff._common import (
|
|
|
8
7
|
_prepare_logger,
|
|
9
8
|
_config_handlers,
|
|
10
9
|
_log_backoff,
|
|
11
|
-
_log_giveup
|
|
10
|
+
_log_giveup,
|
|
12
11
|
)
|
|
13
12
|
from backoff._jitter import full_jitter
|
|
14
13
|
from backoff import _async, _sync
|
|
@@ -24,19 +23,21 @@ from backoff._typing import (
|
|
|
24
23
|
)
|
|
25
24
|
|
|
26
25
|
|
|
27
|
-
def on_predicate(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
26
|
+
def on_predicate(
|
|
27
|
+
wait_gen: _WaitGenerator,
|
|
28
|
+
predicate: _Predicate[Any] = operator.not_,
|
|
29
|
+
*,
|
|
30
|
+
max_tries: Optional[_MaybeCallable[int]] = None,
|
|
31
|
+
max_time: Optional[_MaybeCallable[float]] = None,
|
|
32
|
+
jitter: Union[_Jitterer, None] = full_jitter,
|
|
33
|
+
on_success: Union[_Handler, Iterable[_Handler], None] = None,
|
|
34
|
+
on_backoff: Union[_Handler, Iterable[_Handler], None] = None,
|
|
35
|
+
on_giveup: Union[_Handler, Iterable[_Handler], None] = None,
|
|
36
|
+
logger: _MaybeLogger = "backoff",
|
|
37
|
+
backoff_log_level: int = logging.INFO,
|
|
38
|
+
giveup_log_level: int = logging.ERROR,
|
|
39
|
+
**wait_gen_kwargs: Any,
|
|
40
|
+
) -> Callable[[_CallableT], _CallableT]:
|
|
40
41
|
"""Returns decorator for backoff and retry triggered by predicate.
|
|
41
42
|
|
|
42
43
|
Args:
|
|
@@ -80,6 +81,7 @@ def on_predicate(wait_gen: _WaitGenerator,
|
|
|
80
81
|
args will first be evaluated and their return values passed.
|
|
81
82
|
This is useful for runtime configuration.
|
|
82
83
|
"""
|
|
84
|
+
|
|
83
85
|
def decorate(target):
|
|
84
86
|
nonlocal logger, on_success, on_backoff, on_giveup
|
|
85
87
|
|
|
@@ -89,13 +91,13 @@ def on_predicate(wait_gen: _WaitGenerator,
|
|
|
89
91
|
on_backoff,
|
|
90
92
|
default_handler=_log_backoff,
|
|
91
93
|
logger=logger,
|
|
92
|
-
log_level=backoff_log_level
|
|
94
|
+
log_level=backoff_log_level,
|
|
93
95
|
)
|
|
94
96
|
on_giveup = _config_handlers(
|
|
95
97
|
on_giveup,
|
|
96
98
|
default_handler=_log_giveup,
|
|
97
99
|
logger=logger,
|
|
98
|
-
log_level=giveup_log_level
|
|
100
|
+
log_level=giveup_log_level,
|
|
99
101
|
)
|
|
100
102
|
|
|
101
103
|
if inspect.iscoroutinefunction(target):
|
|
@@ -113,28 +115,30 @@ def on_predicate(wait_gen: _WaitGenerator,
|
|
|
113
115
|
on_success=on_success,
|
|
114
116
|
on_backoff=on_backoff,
|
|
115
117
|
on_giveup=on_giveup,
|
|
116
|
-
wait_gen_kwargs=wait_gen_kwargs
|
|
118
|
+
wait_gen_kwargs=wait_gen_kwargs,
|
|
117
119
|
)
|
|
118
120
|
|
|
119
121
|
# Return a function which decorates a target with a retry loop.
|
|
120
122
|
return decorate
|
|
121
123
|
|
|
122
124
|
|
|
123
|
-
def on_exception(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
125
|
+
def on_exception(
|
|
126
|
+
wait_gen: _WaitGenerator,
|
|
127
|
+
exception: _MaybeSequence[Type[Exception]],
|
|
128
|
+
*,
|
|
129
|
+
max_tries: Optional[_MaybeCallable[int]] = None,
|
|
130
|
+
max_time: Optional[_MaybeCallable[float]] = None,
|
|
131
|
+
jitter: Union[_Jitterer, None] = full_jitter,
|
|
132
|
+
giveup: _Predicate[Exception] = lambda e: False,
|
|
133
|
+
on_success: Union[_Handler, Iterable[_Handler], None] = None,
|
|
134
|
+
on_backoff: Union[_Handler, Iterable[_Handler], None] = None,
|
|
135
|
+
on_giveup: Union[_Handler, Iterable[_Handler], None] = None,
|
|
136
|
+
raise_on_giveup: bool = True,
|
|
137
|
+
logger: _MaybeLogger = "backoff",
|
|
138
|
+
backoff_log_level: int = logging.INFO,
|
|
139
|
+
giveup_log_level: int = logging.ERROR,
|
|
140
|
+
**wait_gen_kwargs: Any,
|
|
141
|
+
) -> Callable[[_CallableT], _CallableT]:
|
|
138
142
|
"""Returns decorator for backoff and retry triggered by exception.
|
|
139
143
|
|
|
140
144
|
Args:
|
|
@@ -180,6 +184,7 @@ def on_exception(wait_gen: _WaitGenerator,
|
|
|
180
184
|
args will first be evaluated and their return values passed.
|
|
181
185
|
This is useful for runtime configuration.
|
|
182
186
|
"""
|
|
187
|
+
|
|
183
188
|
def decorate(target):
|
|
184
189
|
nonlocal logger, on_success, on_backoff, on_giveup
|
|
185
190
|
|
|
@@ -215,7 +220,7 @@ def on_exception(wait_gen: _WaitGenerator,
|
|
|
215
220
|
on_backoff=on_backoff,
|
|
216
221
|
on_giveup=on_giveup,
|
|
217
222
|
raise_on_giveup=raise_on_giveup,
|
|
218
|
-
wait_gen_kwargs=wait_gen_kwargs
|
|
223
|
+
wait_gen_kwargs=wait_gen_kwargs,
|
|
219
224
|
)
|
|
220
225
|
|
|
221
226
|
# Return a function which decorates a target with a retry loop.
|
backoff/_sync.py
CHANGED
|
@@ -1,42 +1,46 @@
|
|
|
1
|
-
# coding:utf-8
|
|
2
|
-
import datetime
|
|
3
1
|
import functools
|
|
4
2
|
import time
|
|
5
|
-
from datetime import timedelta
|
|
6
3
|
|
|
7
|
-
from backoff._common import
|
|
4
|
+
from backoff._common import _init_wait_gen, _maybe_call, _next_wait
|
|
8
5
|
|
|
9
6
|
|
|
10
7
|
def _call_handlers(hdlrs, target, args, kwargs, tries, elapsed, **extra):
|
|
11
8
|
details = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
"target": target,
|
|
10
|
+
"args": args,
|
|
11
|
+
"kwargs": kwargs,
|
|
12
|
+
"tries": tries,
|
|
13
|
+
"elapsed": elapsed,
|
|
17
14
|
}
|
|
18
15
|
details.update(extra)
|
|
19
16
|
for hdlr in hdlrs:
|
|
20
17
|
hdlr(details)
|
|
21
18
|
|
|
22
19
|
|
|
23
|
-
def retry_predicate(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
def retry_predicate(
|
|
21
|
+
target,
|
|
22
|
+
wait_gen,
|
|
23
|
+
predicate,
|
|
24
|
+
*,
|
|
25
|
+
max_tries,
|
|
26
|
+
max_time,
|
|
27
|
+
jitter,
|
|
28
|
+
on_success,
|
|
29
|
+
on_backoff,
|
|
30
|
+
on_giveup,
|
|
31
|
+
wait_gen_kwargs,
|
|
32
|
+
):
|
|
29
33
|
@functools.wraps(target)
|
|
30
34
|
def retry(*args, **kwargs):
|
|
31
35
|
max_tries_value = _maybe_call(max_tries)
|
|
32
36
|
max_time_value = _maybe_call(max_time)
|
|
33
37
|
|
|
34
38
|
tries = 0
|
|
35
|
-
start =
|
|
39
|
+
start = time.monotonic()
|
|
36
40
|
wait = _init_wait_gen(wait_gen, wait_gen_kwargs)
|
|
37
41
|
while True:
|
|
38
42
|
tries += 1
|
|
39
|
-
elapsed =
|
|
43
|
+
elapsed = time.monotonic() - start
|
|
40
44
|
details = {
|
|
41
45
|
"target": target,
|
|
42
46
|
"args": args,
|
|
@@ -47,23 +51,22 @@ def retry_predicate(target, wait_gen, predicate,
|
|
|
47
51
|
|
|
48
52
|
ret = target(*args, **kwargs)
|
|
49
53
|
if predicate(ret):
|
|
50
|
-
max_tries_exceeded =
|
|
51
|
-
max_time_exceeded = (
|
|
52
|
-
|
|
54
|
+
max_tries_exceeded = tries == max_tries_value
|
|
55
|
+
max_time_exceeded = (
|
|
56
|
+
max_time_value is not None and elapsed >= max_time_value
|
|
57
|
+
)
|
|
53
58
|
|
|
54
59
|
if max_tries_exceeded or max_time_exceeded:
|
|
55
60
|
_call_handlers(on_giveup, **details, value=ret)
|
|
56
61
|
break
|
|
57
62
|
|
|
58
63
|
try:
|
|
59
|
-
seconds = _next_wait(wait, ret, jitter, elapsed,
|
|
60
|
-
max_time_value)
|
|
64
|
+
seconds = _next_wait(wait, ret, jitter, elapsed, max_time_value)
|
|
61
65
|
except StopIteration:
|
|
62
66
|
_call_handlers(on_giveup, **details)
|
|
63
67
|
break
|
|
64
68
|
|
|
65
|
-
_call_handlers(on_backoff, **details,
|
|
66
|
-
value=ret, wait=seconds)
|
|
69
|
+
_call_handlers(on_backoff, **details, value=ret, wait=seconds)
|
|
67
70
|
|
|
68
71
|
time.sleep(seconds)
|
|
69
72
|
continue
|
|
@@ -76,23 +79,32 @@ def retry_predicate(target, wait_gen, predicate,
|
|
|
76
79
|
return retry
|
|
77
80
|
|
|
78
81
|
|
|
79
|
-
def retry_exception(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
def retry_exception(
|
|
83
|
+
target,
|
|
84
|
+
wait_gen,
|
|
85
|
+
exception,
|
|
86
|
+
*,
|
|
87
|
+
max_tries,
|
|
88
|
+
max_time,
|
|
89
|
+
jitter,
|
|
90
|
+
giveup,
|
|
91
|
+
on_success,
|
|
92
|
+
on_backoff,
|
|
93
|
+
on_giveup,
|
|
94
|
+
raise_on_giveup,
|
|
95
|
+
wait_gen_kwargs,
|
|
96
|
+
):
|
|
85
97
|
@functools.wraps(target)
|
|
86
98
|
def retry(*args, **kwargs):
|
|
87
99
|
max_tries_value = _maybe_call(max_tries)
|
|
88
100
|
max_time_value = _maybe_call(max_time)
|
|
89
101
|
|
|
90
102
|
tries = 0
|
|
91
|
-
start =
|
|
103
|
+
start = time.monotonic()
|
|
92
104
|
wait = _init_wait_gen(wait_gen, wait_gen_kwargs)
|
|
93
105
|
while True:
|
|
94
106
|
tries += 1
|
|
95
|
-
elapsed =
|
|
107
|
+
elapsed = time.monotonic() - start
|
|
96
108
|
details = {
|
|
97
109
|
"target": target,
|
|
98
110
|
"args": args,
|
|
@@ -104,9 +116,10 @@ def retry_exception(target, wait_gen, exception,
|
|
|
104
116
|
try:
|
|
105
117
|
ret = target(*args, **kwargs)
|
|
106
118
|
except exception as e:
|
|
107
|
-
max_tries_exceeded =
|
|
108
|
-
max_time_exceeded = (
|
|
109
|
-
|
|
119
|
+
max_tries_exceeded = tries == max_tries_value
|
|
120
|
+
max_time_exceeded = (
|
|
121
|
+
max_time_value is not None and elapsed >= max_time_value
|
|
122
|
+
)
|
|
110
123
|
|
|
111
124
|
if giveup(e) or max_tries_exceeded or max_time_exceeded:
|
|
112
125
|
_call_handlers(on_giveup, **details, exception=e)
|
|
@@ -115,18 +128,17 @@ def retry_exception(target, wait_gen, exception,
|
|
|
115
128
|
return None
|
|
116
129
|
|
|
117
130
|
try:
|
|
118
|
-
seconds = _next_wait(wait, e, jitter, elapsed,
|
|
119
|
-
max_time_value)
|
|
131
|
+
seconds = _next_wait(wait, e, jitter, elapsed, max_time_value)
|
|
120
132
|
except StopIteration:
|
|
121
133
|
_call_handlers(on_giveup, **details, exception=e)
|
|
122
134
|
raise e
|
|
123
135
|
|
|
124
|
-
_call_handlers(on_backoff, **details, wait=seconds,
|
|
125
|
-
exception=e)
|
|
136
|
+
_call_handlers(on_backoff, **details, wait=seconds, exception=e)
|
|
126
137
|
|
|
127
138
|
time.sleep(seconds)
|
|
128
139
|
else:
|
|
129
140
|
_call_handlers(on_success, **details)
|
|
130
141
|
|
|
131
142
|
return ret
|
|
143
|
+
|
|
132
144
|
return retry
|
backoff/_typing.py
CHANGED
|
@@ -1,23 +1,21 @@
|
|
|
1
|
-
# coding:utf-8
|
|
2
1
|
import logging
|
|
3
|
-
import
|
|
4
|
-
from typing import (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return super().__init_subclass__()
|
|
2
|
+
from types import FunctionType
|
|
3
|
+
from typing import (
|
|
4
|
+
Any,
|
|
5
|
+
Callable,
|
|
6
|
+
Coroutine,
|
|
7
|
+
Dict,
|
|
8
|
+
Generator,
|
|
9
|
+
Sequence,
|
|
10
|
+
Tuple,
|
|
11
|
+
TypedDict,
|
|
12
|
+
TypeVar,
|
|
13
|
+
Union,
|
|
14
|
+
)
|
|
17
15
|
|
|
18
16
|
|
|
19
17
|
class _Details(TypedDict):
|
|
20
|
-
target:
|
|
18
|
+
target: FunctionType
|
|
21
19
|
args: Tuple[Any, ...]
|
|
22
20
|
kwargs: Dict[str, Any]
|
|
23
21
|
tries: int
|
|
@@ -32,7 +30,7 @@ class Details(_Details, total=False):
|
|
|
32
30
|
|
|
33
31
|
T = TypeVar("T")
|
|
34
32
|
|
|
35
|
-
_CallableT = TypeVar(
|
|
33
|
+
_CallableT = TypeVar("_CallableT", bound=Callable[..., Any])
|
|
36
34
|
_Handler = Union[
|
|
37
35
|
Callable[[Details], None],
|
|
38
36
|
Callable[[Details], Coroutine[Any, Any, None]],
|
backoff/_wait_gen.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
# coding:utf-8
|
|
2
|
-
|
|
3
1
|
import itertools
|
|
4
2
|
import math
|
|
5
3
|
from typing import Any, Callable, Generator, Iterable, Optional, Union
|
|
@@ -8,9 +6,8 @@ from typing import Any, Callable, Generator, Iterable, Optional, Union
|
|
|
8
6
|
def expo(
|
|
9
7
|
base: float = 2,
|
|
10
8
|
factor: float = 1,
|
|
11
|
-
max_value: Optional[float] = None
|
|
9
|
+
max_value: Optional[float] = None,
|
|
12
10
|
) -> Generator[float, Any, None]:
|
|
13
|
-
|
|
14
11
|
"""Generator for exponential decay.
|
|
15
12
|
|
|
16
13
|
Args:
|
|
@@ -22,12 +19,12 @@ def expo(
|
|
|
22
19
|
"""
|
|
23
20
|
# Advance past initial .send() call
|
|
24
21
|
yield # type: ignore[misc]
|
|
25
|
-
|
|
22
|
+
base_n: float = 1
|
|
26
23
|
while True:
|
|
27
|
-
a = factor *
|
|
24
|
+
a = factor * base_n
|
|
28
25
|
if max_value is None or a < max_value:
|
|
29
26
|
yield a
|
|
30
|
-
|
|
27
|
+
base_n *= base
|
|
31
28
|
else:
|
|
32
29
|
yield max_value
|
|
33
30
|
|
|
@@ -35,9 +32,8 @@ def expo(
|
|
|
35
32
|
def decay(
|
|
36
33
|
initial_value: float = 1,
|
|
37
34
|
decay_factor: float = 1,
|
|
38
|
-
min_value: Optional[float] = None
|
|
35
|
+
min_value: Optional[float] = None,
|
|
39
36
|
) -> Generator[float, Any, None]:
|
|
40
|
-
|
|
41
37
|
"""Generator for exponential decay[1]:
|
|
42
38
|
|
|
43
39
|
Args:
|
|
@@ -83,7 +79,7 @@ def fibo(max_value: Optional[int] = None) -> Generator[int, None, None]:
|
|
|
83
79
|
|
|
84
80
|
|
|
85
81
|
def constant(
|
|
86
|
-
interval: Union[int, Iterable[float]] = 1
|
|
82
|
+
interval: Union[int, Iterable[float]] = 1,
|
|
87
83
|
) -> Generator[float, None, None]:
|
|
88
84
|
"""Generator for constant intervals.
|
|
89
85
|
|
|
@@ -104,7 +100,7 @@ def constant(
|
|
|
104
100
|
|
|
105
101
|
def runtime(
|
|
106
102
|
*,
|
|
107
|
-
value: Callable[[Any], float]
|
|
103
|
+
value: Callable[[Any], float],
|
|
108
104
|
) -> Generator[float, None, None]:
|
|
109
105
|
"""Generator that is based on parsing the return value or thrown
|
|
110
106
|
exception of the decorated method
|
backoff/types.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-backoff
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: Function decoration for backoff and retry
|
|
5
5
|
Project-URL: Documentation, https://python-backoff.github.io/backoff
|
|
6
6
|
Project-URL: Repository, https://github.com/python-backoff/backoff
|
|
@@ -16,7 +16,6 @@ Classifier: Natural Language :: English
|
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
17
|
Classifier: Programming Language :: Python
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.8
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.9
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -28,7 +27,7 @@ Classifier: Programming Language :: Python :: 3.15
|
|
|
28
27
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
29
28
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
30
29
|
Classifier: Topic :: Utilities
|
|
31
|
-
Requires-Python: >=3.
|
|
30
|
+
Requires-Python: >=3.8
|
|
32
31
|
Description-Content-Type: text/x-rst
|
|
33
32
|
|
|
34
33
|
backoff
|
|
@@ -59,7 +58,7 @@ for asynchronous code.
|
|
|
59
58
|
Examples
|
|
60
59
|
========
|
|
61
60
|
|
|
62
|
-
Since Kenneth Reitz's `requests <
|
|
61
|
+
Since Kenneth Reitz's `requests <https://requests.readthedocs.io>`_ module
|
|
63
62
|
has become a defacto standard for synchronous HTTP clients in Python,
|
|
64
63
|
networking examples below are written using it, but it is in no way required
|
|
65
64
|
by the backoff module.
|
|
@@ -73,8 +72,10 @@ is raised. Here's an example using exponential backoff when any
|
|
|
73
72
|
|
|
74
73
|
.. code-block:: python
|
|
75
74
|
|
|
76
|
-
@backoff.on_exception(
|
|
77
|
-
|
|
75
|
+
@backoff.on_exception(
|
|
76
|
+
backoff.expo,
|
|
77
|
+
requests.exceptions.RequestException,
|
|
78
|
+
)
|
|
78
79
|
def get_url(url):
|
|
79
80
|
return requests.get(url)
|
|
80
81
|
|
|
@@ -83,9 +84,13 @@ the same backoff behavior is desired for more than one exception type:
|
|
|
83
84
|
|
|
84
85
|
.. code-block:: python
|
|
85
86
|
|
|
86
|
-
@backoff.on_exception(
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
@backoff.on_exception(
|
|
88
|
+
backoff.expo,
|
|
89
|
+
(
|
|
90
|
+
requests.exceptions.Timeout,
|
|
91
|
+
requests.exceptions.ConnectionError,
|
|
92
|
+
),
|
|
93
|
+
)
|
|
89
94
|
def get_url(url):
|
|
90
95
|
return requests.get(url)
|
|
91
96
|
|
|
@@ -99,9 +104,11 @@ of total time in seconds that can elapse before giving up.
|
|
|
99
104
|
|
|
100
105
|
.. code-block:: python
|
|
101
106
|
|
|
102
|
-
@backoff.on_exception(
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
@backoff.on_exception(
|
|
108
|
+
backoff.expo,
|
|
109
|
+
requests.exceptions.RequestException,
|
|
110
|
+
max_time=60,
|
|
111
|
+
)
|
|
105
112
|
def get_url(url):
|
|
106
113
|
return requests.get(url)
|
|
107
114
|
|
|
@@ -111,10 +118,12 @@ to make to the target function before giving up.
|
|
|
111
118
|
|
|
112
119
|
.. code-block:: python
|
|
113
120
|
|
|
114
|
-
@backoff.on_exception(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
@backoff.on_exception(
|
|
122
|
+
backoff.expo,
|
|
123
|
+
requests.exceptions.RequestException,
|
|
124
|
+
max_tries=8,
|
|
125
|
+
jitter=None,
|
|
126
|
+
)
|
|
118
127
|
def get_url(url):
|
|
119
128
|
return requests.get(url)
|
|
120
129
|
|
|
@@ -130,10 +139,13 @@ be retried:
|
|
|
130
139
|
def fatal_code(e):
|
|
131
140
|
return 400 <= e.response.status_code < 500
|
|
132
141
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
142
|
+
|
|
143
|
+
@backoff.on_exception(
|
|
144
|
+
backoff.expo,
|
|
145
|
+
requests.exceptions.RequestException,
|
|
146
|
+
max_time=300,
|
|
147
|
+
giveup=fatal_code,
|
|
148
|
+
)
|
|
137
149
|
def get_url(url):
|
|
138
150
|
return requests.get(url)
|
|
139
151
|
|
|
@@ -151,11 +163,14 @@ case, regardless of the logic in the `on_exception` handler.
|
|
|
151
163
|
def fatal_code(e):
|
|
152
164
|
return 400 <= e.response.status_code < 500
|
|
153
165
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
166
|
+
|
|
167
|
+
@backoff.on_exception(
|
|
168
|
+
backoff.expo,
|
|
169
|
+
requests.exceptions.RequestException,
|
|
170
|
+
max_time=300,
|
|
171
|
+
raise_on_giveup=False,
|
|
172
|
+
giveup=fatal_code,
|
|
173
|
+
)
|
|
159
174
|
def get_url(url):
|
|
160
175
|
return requests.get(url)
|
|
161
176
|
|
|
@@ -175,7 +190,11 @@ return value of the target function is the empty list:
|
|
|
175
190
|
|
|
176
191
|
.. code-block:: python
|
|
177
192
|
|
|
178
|
-
@backoff.on_predicate(
|
|
193
|
+
@backoff.on_predicate(
|
|
194
|
+
backoff.fibo,
|
|
195
|
+
lambda x: x == [],
|
|
196
|
+
max_value=13,
|
|
197
|
+
)
|
|
179
198
|
def poll_for_messages(queue):
|
|
180
199
|
return queue.get()
|
|
181
200
|
|
|
@@ -197,11 +216,15 @@ gets a non-falsey result could be defined like like this:
|
|
|
197
216
|
|
|
198
217
|
.. code-block:: python
|
|
199
218
|
|
|
200
|
-
@backoff.on_predicate(
|
|
219
|
+
@backoff.on_predicate(
|
|
220
|
+
backoff.constant,
|
|
221
|
+
jitter=None,
|
|
222
|
+
interval=1,
|
|
223
|
+
)
|
|
201
224
|
def poll_for_message(queue):
|
|
202
225
|
return queue.get()
|
|
203
226
|
|
|
204
|
-
The jitter is disabled in order to keep the polling frequency fixed.
|
|
227
|
+
The jitter is disabled in order to keep the polling frequency fixed.
|
|
205
228
|
|
|
206
229
|
@backoff.runtime
|
|
207
230
|
----------------
|
|
@@ -250,12 +273,16 @@ backoff behavior for different cases:
|
|
|
250
273
|
.. code-block:: python
|
|
251
274
|
|
|
252
275
|
@backoff.on_predicate(backoff.fibo, max_value=13)
|
|
253
|
-
@backoff.on_exception(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
276
|
+
@backoff.on_exception(
|
|
277
|
+
backoff.expo,
|
|
278
|
+
requests.exceptions.HTTPError,
|
|
279
|
+
max_time=60,
|
|
280
|
+
)
|
|
281
|
+
@backoff.on_exception(
|
|
282
|
+
backoff.expo,
|
|
283
|
+
requests.exceptions.Timeout,
|
|
284
|
+
max_time=300,
|
|
285
|
+
)
|
|
259
286
|
def poll_for_message(queue):
|
|
260
287
|
return queue.get()
|
|
261
288
|
|
|
@@ -278,9 +305,13 @@ runtime to obtain the value:
|
|
|
278
305
|
# and that it has a dictionary-like 'config' property
|
|
279
306
|
return app.config["BACKOFF_MAX_TIME"]
|
|
280
307
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
308
|
+
|
|
309
|
+
@backoff.on_exception(
|
|
310
|
+
backoff.expo,
|
|
311
|
+
ValueError,
|
|
312
|
+
max_time=lookup_max_time,
|
|
313
|
+
)
|
|
314
|
+
def my_function(): ...
|
|
284
315
|
|
|
285
316
|
Event handlers
|
|
286
317
|
--------------
|
|
@@ -308,13 +339,18 @@ implemented like so:
|
|
|
308
339
|
.. code-block:: python
|
|
309
340
|
|
|
310
341
|
def backoff_hdlr(details):
|
|
311
|
-
print
|
|
312
|
-
|
|
313
|
-
|
|
342
|
+
print(
|
|
343
|
+
"Backing off {wait:0.1f} seconds after {tries} tries "
|
|
344
|
+
"calling function {target} with args {args} and kwargs "
|
|
345
|
+
"{kwargs}".format(**details)
|
|
346
|
+
)
|
|
347
|
+
|
|
314
348
|
|
|
315
|
-
@backoff.on_exception(
|
|
316
|
-
|
|
317
|
-
|
|
349
|
+
@backoff.on_exception(
|
|
350
|
+
backoff.expo,
|
|
351
|
+
requests.exceptions.RequestException,
|
|
352
|
+
on_backoff=backoff_hdlr,
|
|
353
|
+
)
|
|
318
354
|
def get_url(url):
|
|
319
355
|
return requests.get(url)
|
|
320
356
|
|
|
@@ -326,9 +362,14 @@ handler functions as the value of the ``on_backoff`` keyword arg:
|
|
|
326
362
|
|
|
327
363
|
.. code-block:: python
|
|
328
364
|
|
|
329
|
-
@backoff.on_exception(
|
|
330
|
-
|
|
331
|
-
|
|
365
|
+
@backoff.on_exception(
|
|
366
|
+
backoff.expo,
|
|
367
|
+
requests.exceptions.RequestException,
|
|
368
|
+
on_backoff=[
|
|
369
|
+
backoff_hdlr1,
|
|
370
|
+
backoff_hdlr2,
|
|
371
|
+
],
|
|
372
|
+
)
|
|
332
373
|
def get_url(url):
|
|
333
374
|
return requests.get(url)
|
|
334
375
|
|
|
@@ -359,9 +400,13 @@ asynchronous HTTP client/server library.
|
|
|
359
400
|
|
|
360
401
|
.. code-block:: python
|
|
361
402
|
|
|
362
|
-
@backoff.on_exception(
|
|
403
|
+
@backoff.on_exception(
|
|
404
|
+
backoff.expo,
|
|
405
|
+
aiohttp.ClientError,
|
|
406
|
+
max_time=60,
|
|
407
|
+
)
|
|
363
408
|
async def get_url(url):
|
|
364
|
-
async with aiohttp.ClientSession(raise_for_status=True) as session:
|
|
409
|
+
async with aiohttp.ClientSession(raise_for_status=True) as session: # noqa: SIM117
|
|
365
410
|
async with session.get(url) as response:
|
|
366
411
|
return await response.text()
|
|
367
412
|
|
|
@@ -376,7 +421,7 @@ as:
|
|
|
376
421
|
|
|
377
422
|
.. code-block:: python
|
|
378
423
|
|
|
379
|
-
logging.getLogger(
|
|
424
|
+
logging.getLogger("backoff").addHandler(logging.StreamHandler())
|
|
380
425
|
|
|
381
426
|
The default logging level is INFO, which corresponds to logging
|
|
382
427
|
anytime a retry event occurs. If you would instead like to log
|
|
@@ -384,7 +429,7 @@ only when a giveup event occurs, set the logger level to ERROR.
|
|
|
384
429
|
|
|
385
430
|
.. code-block:: python
|
|
386
431
|
|
|
387
|
-
logging.getLogger(
|
|
432
|
+
logging.getLogger("backoff").setLevel(logging.ERROR)
|
|
388
433
|
|
|
389
434
|
It is also possible to specify an alternate logger with the ``logger``
|
|
390
435
|
keyword argument. If a string value is specified the logger will be
|
|
@@ -392,25 +437,30 @@ looked up by name.
|
|
|
392
437
|
|
|
393
438
|
.. code-block:: python
|
|
394
439
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
440
|
+
@backoff.on_exception(
|
|
441
|
+
backoff.expo,
|
|
442
|
+
requests.exceptions.RequestException,
|
|
443
|
+
logger="my_logger",
|
|
444
|
+
)
|
|
445
|
+
def my_function(): ...
|
|
399
446
|
|
|
400
447
|
It is also supported to specify a Logger (or LoggerAdapter) object
|
|
401
448
|
directly.
|
|
402
449
|
|
|
403
450
|
.. code-block:: python
|
|
404
451
|
|
|
405
|
-
my_logger = logging.getLogger(
|
|
452
|
+
my_logger = logging.getLogger("my_logger")
|
|
406
453
|
my_handler = logging.StreamHandler()
|
|
407
454
|
my_logger.addHandler(my_handler)
|
|
408
455
|
my_logger.setLevel(logging.ERROR)
|
|
409
456
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
457
|
+
|
|
458
|
+
@backoff.on_exception(
|
|
459
|
+
backoff.expo,
|
|
460
|
+
requests.exceptions.RequestException,
|
|
461
|
+
logger=my_logger,
|
|
462
|
+
)
|
|
463
|
+
def my_function(): ...
|
|
414
464
|
|
|
415
465
|
Default logging can be disabled all together by specifying
|
|
416
466
|
``logger=None``. In this case, if desired alternative logging behavior
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
backoff/__init__.py,sha256=MS4ieVYpjBh-feDqzJHYJy8mjnuEEaprTWWuXgJI-1A,914
|
|
2
|
+
backoff/_async.py,sha256=Ehv3H3jMF9JylzZzHTLSLwK_s0PoJa_1jeGo3qtpcOY,6370
|
|
3
|
+
backoff/_common.py,sha256=Q8ZB8mCMI76WLii8JcrfoSZc11WRqu7ci_mpG1y-j9Y,3460
|
|
4
|
+
backoff/_decorator.py,sha256=61iXFb5jN2mQ9-1mtRPi_gysJwuyhkKrbDVNWH8GDxo,9483
|
|
5
|
+
backoff/_jitter.py,sha256=wzQHBDNuYg7owbc3fcZLOr5uhhRgqiuncU5rIYOB9lY,766
|
|
6
|
+
backoff/_sync.py,sha256=7W0aTlntrL237rfhy2yaFklZheQoYxBTD5F_4TUarQI,3867
|
|
7
|
+
backoff/_typing.py,sha256=hO065un_HjJEsEa_OgW66JZRyxVf-BsJ9HrswIwW3fA,1056
|
|
8
|
+
backoff/_wait_gen.py,sha256=ZTZfs4WiI295EvOJeJYPIUAABQ0WBAKcvUtg0_ZId8k,3244
|
|
9
|
+
backoff/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
backoff/types.py,sha256=rOQAfuetqVwfEIuzVvK5D_w97A3wrZ9lQ8HwuJ_uq5s,59
|
|
11
|
+
python_backoff-2.3.1.dist-info/METADATA,sha256=o7ltpQ3fk4s05tUCVIeUgZVzGFfigmw06jNoz6HM6RI,14885
|
|
12
|
+
python_backoff-2.3.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
13
|
+
python_backoff-2.3.1.dist-info/licenses/LICENSE,sha256=KmtNX4hNTXob8E6n3xlEzxKzLjWnmobQoHWi0_QPuaw,1077
|
|
14
|
+
python_backoff-2.3.1.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
backoff/__init__.py,sha256=cxmsrw6PUsF2VsUnaX-gyKsJ5If3iKVIKoVU5Fg2njc,928
|
|
2
|
-
backoff/_async.py,sha256=gX-MFZWs1-6dyUsohHtJxAP_Zg0if5n8MSmHyNFTB5I,6790
|
|
3
|
-
backoff/_common.py,sha256=8s3_5AJH8hiHd9GR2PdKqiaeE2sdEUoyf6cW9OCo1F8,3478
|
|
4
|
-
backoff/_decorator.py,sha256=Vx9IlrmA22tjNgo-YN4FHqcC0W54wL-4Ug4dTdjUHZ8,9815
|
|
5
|
-
backoff/_jitter.py,sha256=LjJShpjryk9sWBCWiz-3UX1DJCx6rebNJ5Bf3nPMlYQ,782
|
|
6
|
-
backoff/_sync.py,sha256=DT_ktufPPb0nut9WCAKe5UE7sTYRl9f93PRPX6jP8ro,4214
|
|
7
|
-
backoff/_typing.py,sha256=K4xjbkoQUuKt5E1cEAUgIPrhrV2lWD3BXmgu8_Mv7rQ,1422
|
|
8
|
-
backoff/_wait_gen.py,sha256=hObFPeXDXdzcvMhkknya3nlSKYoCAMT5LFoto25emuI,3241
|
|
9
|
-
backoff/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
backoff/types.py,sha256=4DGG6Ltcz0wVfXrk0YBOnp_oPpcki4c0BOnodRhgoqg,73
|
|
11
|
-
python_backoff-2.2.2rc2.dist-info/METADATA,sha256=ArUoHM1YP6T5LHwR_SJ5s9FyZgOjVp6XE96xVYWrKwE,14965
|
|
12
|
-
python_backoff-2.2.2rc2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
13
|
-
python_backoff-2.2.2rc2.dist-info/licenses/LICENSE,sha256=KmtNX4hNTXob8E6n3xlEzxKzLjWnmobQoHWi0_QPuaw,1077
|
|
14
|
-
python_backoff-2.2.2rc2.dist-info/RECORD,,
|
|
File without changes
|