backon 3.0.0__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.
- backon/__init__.py +23 -0
- backon/_async.py +208 -0
- backon/_common.py +117 -0
- backon/_decorator.py +150 -0
- backon/_jitter.py +9 -0
- backon/_retry.py +464 -0
- backon/_sync.py +173 -0
- backon/_typing.py +51 -0
- backon/_wait_gen.py +62 -0
- backon/py.typed +0 -0
- backon/types.py +3 -0
- backon-3.0.0.dist-info/METADATA +179 -0
- backon-3.0.0.dist-info/RECORD +16 -0
- backon-3.0.0.dist-info/WHEEL +4 -0
- backon-3.0.0.dist-info/entry_points.txt +4 -0
- backon-3.0.0.dist-info/licenses/LICENSE +21 -0
backon/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from backon._common import disable, enable
|
|
2
|
+
from backon._decorator import on_exception, on_predicate
|
|
3
|
+
from backon._jitter import full_jitter, random_jitter
|
|
4
|
+
from backon._retry import Retrying, retry
|
|
5
|
+
from backon._wait_gen import constant, decay, expo, fibo, runtime
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"on_predicate",
|
|
9
|
+
"on_exception",
|
|
10
|
+
"retry",
|
|
11
|
+
"Retrying",
|
|
12
|
+
"constant",
|
|
13
|
+
"expo",
|
|
14
|
+
"decay",
|
|
15
|
+
"fibo",
|
|
16
|
+
"runtime",
|
|
17
|
+
"full_jitter",
|
|
18
|
+
"random_jitter",
|
|
19
|
+
"disable",
|
|
20
|
+
"enable",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
__version__ = "3.0.0"
|
backon/_async.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import functools
|
|
3
|
+
|
|
4
|
+
from backon._common import (
|
|
5
|
+
_elapsed,
|
|
6
|
+
_init_wait_gen,
|
|
7
|
+
_maybe_call,
|
|
8
|
+
_next_wait,
|
|
9
|
+
_now,
|
|
10
|
+
is_enabled,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _unwrap(target):
|
|
15
|
+
if isinstance(target, staticmethod):
|
|
16
|
+
return target.__func__
|
|
17
|
+
return target
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _ensure_coroutine(coro_or_func):
|
|
21
|
+
if asyncio.iscoroutinefunction(coro_or_func):
|
|
22
|
+
return coro_or_func
|
|
23
|
+
else:
|
|
24
|
+
|
|
25
|
+
@functools.wraps(coro_or_func)
|
|
26
|
+
async def f(*args, **kwargs):
|
|
27
|
+
return coro_or_func(*args, **kwargs)
|
|
28
|
+
|
|
29
|
+
return f
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _ensure_coroutines(coros_or_funcs):
|
|
33
|
+
return [_ensure_coroutine(f) for f in coros_or_funcs]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def _call_handlers(handlers, *, target, args, kwargs, tries, elapsed, **extra):
|
|
37
|
+
details = {
|
|
38
|
+
"target": target,
|
|
39
|
+
"args": args,
|
|
40
|
+
"kwargs": kwargs,
|
|
41
|
+
"tries": tries,
|
|
42
|
+
"elapsed": elapsed,
|
|
43
|
+
}
|
|
44
|
+
details.update(extra)
|
|
45
|
+
for handler in handlers:
|
|
46
|
+
await handler(details)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def retry_predicate(
|
|
50
|
+
target,
|
|
51
|
+
wait_gen,
|
|
52
|
+
predicate,
|
|
53
|
+
*,
|
|
54
|
+
max_tries,
|
|
55
|
+
max_time,
|
|
56
|
+
jitter,
|
|
57
|
+
on_success,
|
|
58
|
+
on_backoff,
|
|
59
|
+
on_giveup,
|
|
60
|
+
on_attempt,
|
|
61
|
+
sleep,
|
|
62
|
+
wait_gen_kwargs,
|
|
63
|
+
):
|
|
64
|
+
target = _unwrap(target)
|
|
65
|
+
on_success = _ensure_coroutines(on_success)
|
|
66
|
+
on_backoff = _ensure_coroutines(on_backoff)
|
|
67
|
+
on_giveup = _ensure_coroutines(on_giveup)
|
|
68
|
+
on_attempt = _ensure_coroutines(on_attempt)
|
|
69
|
+
|
|
70
|
+
assert not asyncio.iscoroutinefunction(max_tries)
|
|
71
|
+
assert not asyncio.iscoroutinefunction(jitter)
|
|
72
|
+
|
|
73
|
+
assert asyncio.iscoroutinefunction(target)
|
|
74
|
+
|
|
75
|
+
@functools.wraps(target)
|
|
76
|
+
async def retry(*args, **kwargs):
|
|
77
|
+
if not is_enabled():
|
|
78
|
+
return await target(*args, **kwargs)
|
|
79
|
+
|
|
80
|
+
max_tries_value = _maybe_call(max_tries)
|
|
81
|
+
max_time_value = _maybe_call(max_time)
|
|
82
|
+
|
|
83
|
+
tries = 0
|
|
84
|
+
start = _now()
|
|
85
|
+
wait = _init_wait_gen(wait_gen, wait_gen_kwargs)
|
|
86
|
+
while True:
|
|
87
|
+
tries += 1
|
|
88
|
+
elapsed = _elapsed(start)
|
|
89
|
+
details = {
|
|
90
|
+
"target": target,
|
|
91
|
+
"args": args,
|
|
92
|
+
"kwargs": kwargs,
|
|
93
|
+
"tries": tries,
|
|
94
|
+
"elapsed": elapsed,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await _call_handlers(on_attempt, **details)
|
|
98
|
+
|
|
99
|
+
ret = await target(*args, **kwargs)
|
|
100
|
+
if predicate(ret):
|
|
101
|
+
max_tries_exceeded = tries == max_tries_value
|
|
102
|
+
max_time_exceeded = (
|
|
103
|
+
max_time_value is not None and elapsed >= max_time_value
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if max_tries_exceeded or max_time_exceeded:
|
|
107
|
+
await _call_handlers(on_giveup, **details, value=ret)
|
|
108
|
+
break
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
seconds = _next_wait(wait, ret, jitter, elapsed, max_time_value)
|
|
112
|
+
except StopIteration:
|
|
113
|
+
await _call_handlers(on_giveup, **details, value=ret)
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
await _call_handlers(on_backoff, **details, value=ret, wait=seconds)
|
|
117
|
+
|
|
118
|
+
await sleep(seconds)
|
|
119
|
+
continue
|
|
120
|
+
else:
|
|
121
|
+
await _call_handlers(on_success, **details, value=ret)
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
return ret
|
|
125
|
+
|
|
126
|
+
return retry
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def retry_exception(
|
|
130
|
+
target,
|
|
131
|
+
wait_gen,
|
|
132
|
+
exception,
|
|
133
|
+
*,
|
|
134
|
+
max_tries,
|
|
135
|
+
max_time,
|
|
136
|
+
jitter,
|
|
137
|
+
giveup,
|
|
138
|
+
on_success,
|
|
139
|
+
on_backoff,
|
|
140
|
+
on_giveup,
|
|
141
|
+
on_attempt,
|
|
142
|
+
raise_on_giveup,
|
|
143
|
+
sleep,
|
|
144
|
+
wait_gen_kwargs,
|
|
145
|
+
):
|
|
146
|
+
target = _unwrap(target)
|
|
147
|
+
on_success = _ensure_coroutines(on_success)
|
|
148
|
+
on_backoff = _ensure_coroutines(on_backoff)
|
|
149
|
+
on_giveup = _ensure_coroutines(on_giveup)
|
|
150
|
+
on_attempt = _ensure_coroutines(on_attempt)
|
|
151
|
+
giveup = _ensure_coroutine(giveup)
|
|
152
|
+
|
|
153
|
+
assert not asyncio.iscoroutinefunction(max_tries)
|
|
154
|
+
assert not asyncio.iscoroutinefunction(jitter)
|
|
155
|
+
|
|
156
|
+
@functools.wraps(target)
|
|
157
|
+
async def retry(*args, **kwargs):
|
|
158
|
+
if not is_enabled():
|
|
159
|
+
return await target(*args, **kwargs)
|
|
160
|
+
|
|
161
|
+
max_tries_value = _maybe_call(max_tries)
|
|
162
|
+
max_time_value = _maybe_call(max_time)
|
|
163
|
+
|
|
164
|
+
tries = 0
|
|
165
|
+
start = _now()
|
|
166
|
+
wait = _init_wait_gen(wait_gen, wait_gen_kwargs)
|
|
167
|
+
while True:
|
|
168
|
+
tries += 1
|
|
169
|
+
elapsed = _elapsed(start)
|
|
170
|
+
details = {
|
|
171
|
+
"target": target,
|
|
172
|
+
"args": args,
|
|
173
|
+
"kwargs": kwargs,
|
|
174
|
+
"tries": tries,
|
|
175
|
+
"elapsed": elapsed,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
await _call_handlers(on_attempt, **details)
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
ret = await target(*args, **kwargs)
|
|
182
|
+
except exception as e:
|
|
183
|
+
giveup_result = await giveup(e)
|
|
184
|
+
max_tries_exceeded = tries == max_tries_value
|
|
185
|
+
max_time_exceeded = (
|
|
186
|
+
max_time_value is not None and elapsed >= max_time_value
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if giveup_result or max_tries_exceeded or max_time_exceeded:
|
|
190
|
+
await _call_handlers(on_giveup, **details, exception=e)
|
|
191
|
+
if raise_on_giveup:
|
|
192
|
+
raise
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
seconds = _next_wait(wait, e, jitter, elapsed, max_time_value)
|
|
197
|
+
except StopIteration:
|
|
198
|
+
await _call_handlers(on_giveup, **details, exception=e)
|
|
199
|
+
raise e
|
|
200
|
+
|
|
201
|
+
await _call_handlers(on_backoff, **details, wait=seconds, exception=e)
|
|
202
|
+
|
|
203
|
+
await sleep(seconds)
|
|
204
|
+
else:
|
|
205
|
+
await _call_handlers(on_success, **details)
|
|
206
|
+
return ret
|
|
207
|
+
|
|
208
|
+
return retry
|
backon/_common.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
import time as time_module
|
|
5
|
+
import traceback
|
|
6
|
+
|
|
7
|
+
_logger = logging.getLogger("backon")
|
|
8
|
+
_logger.addHandler(logging.NullHandler())
|
|
9
|
+
|
|
10
|
+
_GLOBAL_ENABLED = True
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def disable():
|
|
14
|
+
global _GLOBAL_ENABLED
|
|
15
|
+
_GLOBAL_ENABLED = False
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def enable():
|
|
19
|
+
global _GLOBAL_ENABLED
|
|
20
|
+
_GLOBAL_ENABLED = True
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def is_enabled():
|
|
24
|
+
return _GLOBAL_ENABLED
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _maybe_call(f, *args, **kwargs):
|
|
28
|
+
if callable(f):
|
|
29
|
+
try:
|
|
30
|
+
return f(*args, **kwargs)
|
|
31
|
+
except TypeError:
|
|
32
|
+
return f
|
|
33
|
+
else:
|
|
34
|
+
return f
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _init_wait_gen(wait_gen, wait_gen_kwargs):
|
|
38
|
+
kwargs = {k: _maybe_call(v) for k, v in wait_gen_kwargs.items()}
|
|
39
|
+
initialized = wait_gen(**kwargs)
|
|
40
|
+
initialized.send(None)
|
|
41
|
+
return initialized
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _next_wait(wait, send_value, jitter, elapsed, max_time):
|
|
45
|
+
value = wait.send(send_value)
|
|
46
|
+
if jitter is not None:
|
|
47
|
+
seconds = jitter(value)
|
|
48
|
+
else:
|
|
49
|
+
seconds = value
|
|
50
|
+
|
|
51
|
+
if max_time is not None:
|
|
52
|
+
seconds = min(seconds, max_time - elapsed)
|
|
53
|
+
|
|
54
|
+
return seconds
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _prepare_logger(logger):
|
|
58
|
+
if isinstance(logger, str):
|
|
59
|
+
logger = logging.getLogger(logger)
|
|
60
|
+
return logger
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _config_handlers(
|
|
64
|
+
user_handlers, *, default_handler=None, logger=None, log_level=None
|
|
65
|
+
):
|
|
66
|
+
handlers = []
|
|
67
|
+
if logger is not None:
|
|
68
|
+
assert log_level is not None
|
|
69
|
+
log_handler = functools.partial(
|
|
70
|
+
default_handler, logger=logger, log_level=log_level
|
|
71
|
+
)
|
|
72
|
+
handlers.append(log_handler)
|
|
73
|
+
|
|
74
|
+
if user_handlers is None:
|
|
75
|
+
return handlers
|
|
76
|
+
|
|
77
|
+
if hasattr(user_handlers, "__iter__"):
|
|
78
|
+
handlers += list(user_handlers)
|
|
79
|
+
else:
|
|
80
|
+
handlers.append(user_handlers)
|
|
81
|
+
|
|
82
|
+
return handlers
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _log_backoff(details, logger, log_level):
|
|
86
|
+
msg = "Backing off %s(...) for %.1fs (%s)"
|
|
87
|
+
log_args = [details["target"].__name__, details["wait"]]
|
|
88
|
+
|
|
89
|
+
exc_typ, exc, _ = sys.exc_info()
|
|
90
|
+
if exc is not None:
|
|
91
|
+
exc_fmt = traceback.format_exception_only(exc_typ, exc)[-1]
|
|
92
|
+
log_args.append(exc_fmt.rstrip("\n"))
|
|
93
|
+
else:
|
|
94
|
+
log_args.append(details["value"])
|
|
95
|
+
logger.log(log_level, msg, *log_args)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _log_giveup(details, logger, log_level):
|
|
99
|
+
msg = "Giving up %s(...) after %d tries (%s)"
|
|
100
|
+
log_args = [details["target"].__name__, details["tries"]]
|
|
101
|
+
|
|
102
|
+
exc_typ, exc, _ = sys.exc_info()
|
|
103
|
+
if exc is not None:
|
|
104
|
+
exc_fmt = traceback.format_exception_only(exc_typ, exc)[-1]
|
|
105
|
+
log_args.append(exc_fmt.rstrip("\n"))
|
|
106
|
+
else:
|
|
107
|
+
log_args.append(details["value"])
|
|
108
|
+
|
|
109
|
+
logger.log(log_level, msg, *log_args)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _now():
|
|
113
|
+
return time_module.monotonic()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _elapsed(start):
|
|
117
|
+
return _now() - start
|
backon/_decorator.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import operator
|
|
4
|
+
import time as time_module
|
|
5
|
+
from typing import Any, Callable, Iterable, Optional, Type, Union
|
|
6
|
+
|
|
7
|
+
from backon import _async, _sync
|
|
8
|
+
from backon._common import (
|
|
9
|
+
_config_handlers,
|
|
10
|
+
_log_backoff,
|
|
11
|
+
_log_giveup,
|
|
12
|
+
_prepare_logger,
|
|
13
|
+
)
|
|
14
|
+
from backon._jitter import full_jitter
|
|
15
|
+
from backon._typing import (
|
|
16
|
+
_CallableT,
|
|
17
|
+
_Handler,
|
|
18
|
+
_Jitterer,
|
|
19
|
+
_MaybeCallable,
|
|
20
|
+
_MaybeLogger,
|
|
21
|
+
_MaybeSequence,
|
|
22
|
+
_Predicate,
|
|
23
|
+
_WaitGenerator,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def on_predicate(
|
|
28
|
+
wait_gen: _WaitGenerator,
|
|
29
|
+
predicate: _Predicate[Any] = operator.not_,
|
|
30
|
+
*,
|
|
31
|
+
max_tries: Optional[_MaybeCallable[int]] = None,
|
|
32
|
+
max_time: Optional[_MaybeCallable[float]] = None,
|
|
33
|
+
jitter: Union[_Jitterer, None] = full_jitter,
|
|
34
|
+
on_success: Union[_Handler, Iterable[_Handler], None] = None,
|
|
35
|
+
on_backoff: Union[_Handler, Iterable[_Handler], None] = None,
|
|
36
|
+
on_giveup: Union[_Handler, Iterable[_Handler], None] = None,
|
|
37
|
+
on_attempt: Union[_Handler, Iterable[_Handler], None] = None,
|
|
38
|
+
logger: _MaybeLogger = "backon",
|
|
39
|
+
backoff_log_level: int = logging.INFO,
|
|
40
|
+
giveup_log_level: int = logging.ERROR,
|
|
41
|
+
sleep: Optional[Callable[[float], Any]] = None,
|
|
42
|
+
**wait_gen_kwargs: Any,
|
|
43
|
+
) -> Callable[[_CallableT], _CallableT]:
|
|
44
|
+
def decorate(target):
|
|
45
|
+
nonlocal logger, on_success, on_backoff, on_giveup, on_attempt
|
|
46
|
+
|
|
47
|
+
logger = _prepare_logger(logger)
|
|
48
|
+
on_success = _config_handlers(on_success)
|
|
49
|
+
on_backoff = _config_handlers(
|
|
50
|
+
on_backoff,
|
|
51
|
+
default_handler=_log_backoff,
|
|
52
|
+
logger=logger,
|
|
53
|
+
log_level=backoff_log_level,
|
|
54
|
+
)
|
|
55
|
+
on_giveup = _config_handlers(
|
|
56
|
+
on_giveup,
|
|
57
|
+
default_handler=_log_giveup,
|
|
58
|
+
logger=logger,
|
|
59
|
+
log_level=giveup_log_level,
|
|
60
|
+
)
|
|
61
|
+
on_attempt = _config_handlers(on_attempt)
|
|
62
|
+
|
|
63
|
+
if asyncio.iscoroutinefunction(target):
|
|
64
|
+
retry = _async.retry_predicate
|
|
65
|
+
_sleep = sleep or asyncio.sleep
|
|
66
|
+
else:
|
|
67
|
+
retry = _sync.retry_predicate
|
|
68
|
+
_sleep = sleep or time_module.sleep
|
|
69
|
+
|
|
70
|
+
return retry(
|
|
71
|
+
target,
|
|
72
|
+
wait_gen,
|
|
73
|
+
predicate,
|
|
74
|
+
max_tries=max_tries,
|
|
75
|
+
max_time=max_time,
|
|
76
|
+
jitter=jitter,
|
|
77
|
+
on_success=on_success,
|
|
78
|
+
on_backoff=on_backoff,
|
|
79
|
+
on_giveup=on_giveup,
|
|
80
|
+
on_attempt=on_attempt,
|
|
81
|
+
sleep=_sleep,
|
|
82
|
+
wait_gen_kwargs=wait_gen_kwargs,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return decorate
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def on_exception(
|
|
89
|
+
wait_gen: _WaitGenerator,
|
|
90
|
+
exception: _MaybeSequence[Type[Exception]],
|
|
91
|
+
*,
|
|
92
|
+
max_tries: Optional[_MaybeCallable[int]] = None,
|
|
93
|
+
max_time: Optional[_MaybeCallable[float]] = None,
|
|
94
|
+
jitter: Union[_Jitterer, None] = full_jitter,
|
|
95
|
+
giveup: _Predicate[Exception] = lambda e: False,
|
|
96
|
+
on_success: Union[_Handler, Iterable[_Handler], None] = None,
|
|
97
|
+
on_backoff: Union[_Handler, Iterable[_Handler], None] = None,
|
|
98
|
+
on_giveup: Union[_Handler, Iterable[_Handler], None] = None,
|
|
99
|
+
on_attempt: Union[_Handler, Iterable[_Handler], None] = None,
|
|
100
|
+
raise_on_giveup: bool = True,
|
|
101
|
+
logger: _MaybeLogger = "backon",
|
|
102
|
+
backoff_log_level: int = logging.INFO,
|
|
103
|
+
giveup_log_level: int = logging.ERROR,
|
|
104
|
+
sleep: Optional[Callable[[float], Any]] = None,
|
|
105
|
+
**wait_gen_kwargs: Any,
|
|
106
|
+
) -> Callable[[_CallableT], _CallableT]:
|
|
107
|
+
def decorate(target):
|
|
108
|
+
nonlocal logger, on_success, on_backoff, on_giveup, on_attempt
|
|
109
|
+
|
|
110
|
+
logger = _prepare_logger(logger)
|
|
111
|
+
on_success = _config_handlers(on_success)
|
|
112
|
+
on_backoff = _config_handlers(
|
|
113
|
+
on_backoff,
|
|
114
|
+
default_handler=_log_backoff,
|
|
115
|
+
logger=logger,
|
|
116
|
+
log_level=backoff_log_level,
|
|
117
|
+
)
|
|
118
|
+
on_giveup = _config_handlers(
|
|
119
|
+
on_giveup,
|
|
120
|
+
default_handler=_log_giveup,
|
|
121
|
+
logger=logger,
|
|
122
|
+
log_level=giveup_log_level,
|
|
123
|
+
)
|
|
124
|
+
on_attempt = _config_handlers(on_attempt)
|
|
125
|
+
|
|
126
|
+
if asyncio.iscoroutinefunction(target):
|
|
127
|
+
retry = _async.retry_exception
|
|
128
|
+
_sleep = sleep or asyncio.sleep
|
|
129
|
+
else:
|
|
130
|
+
retry = _sync.retry_exception
|
|
131
|
+
_sleep = sleep or time_module.sleep
|
|
132
|
+
|
|
133
|
+
return retry(
|
|
134
|
+
target,
|
|
135
|
+
wait_gen,
|
|
136
|
+
exception,
|
|
137
|
+
max_tries=max_tries,
|
|
138
|
+
max_time=max_time,
|
|
139
|
+
jitter=jitter,
|
|
140
|
+
giveup=giveup,
|
|
141
|
+
on_success=on_success,
|
|
142
|
+
on_backoff=on_backoff,
|
|
143
|
+
on_giveup=on_giveup,
|
|
144
|
+
on_attempt=on_attempt,
|
|
145
|
+
raise_on_giveup=raise_on_giveup,
|
|
146
|
+
sleep=_sleep,
|
|
147
|
+
wait_gen_kwargs=wait_gen_kwargs,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return decorate
|