python-backoff 2.3.0__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 +49 -36
- backoff/_common.py +15 -14
- backoff/_decorator.py +34 -29
- backoff/_jitter.py +0 -2
- backoff/_sync.py +47 -33
- backoff/_typing.py +15 -17
- backoff/_wait_gen.py +0 -4
- backoff/types.py +1 -2
- {python_backoff-2.3.0.dist-info → python_backoff-2.3.1.dist-info}/METADATA +107 -56
- python_backoff-2.3.1.dist-info/RECORD +14 -0
- python_backoff-2.3.0.dist-info/RECORD +0 -14
- {python_backoff-2.3.0.dist-info → python_backoff-2.3.1.dist-info}/WHEEL +0 -0
- {python_backoff-2.3.0.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,4 +1,3 @@
|
|
|
1
|
-
# coding:utf-8
|
|
2
1
|
import asyncio
|
|
3
2
|
import functools
|
|
4
3
|
import inspect
|
|
@@ -11,9 +10,11 @@ def _ensure_coroutine(coro_or_func):
|
|
|
11
10
|
if inspect.iscoroutinefunction(coro_or_func):
|
|
12
11
|
return coro_or_func
|
|
13
12
|
else:
|
|
13
|
+
|
|
14
14
|
@functools.wraps(coro_or_func)
|
|
15
15
|
async def f(*args, **kwargs):
|
|
16
16
|
return coro_or_func(*args, **kwargs)
|
|
17
|
+
|
|
17
18
|
return f
|
|
18
19
|
|
|
19
20
|
|
|
@@ -21,27 +22,32 @@ def _ensure_coroutines(coros_or_funcs):
|
|
|
21
22
|
return [_ensure_coroutine(f) for f in coros_or_funcs]
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
async def _call_handlers(handlers,
|
|
25
|
-
*,
|
|
26
|
-
target, args, kwargs, tries, elapsed,
|
|
27
|
-
**extra):
|
|
25
|
+
async def _call_handlers(handlers, *, target, args, kwargs, tries, elapsed, **extra):
|
|
28
26
|
details = {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
"target": target,
|
|
28
|
+
"args": args,
|
|
29
|
+
"kwargs": kwargs,
|
|
30
|
+
"tries": tries,
|
|
31
|
+
"elapsed": elapsed,
|
|
34
32
|
}
|
|
35
33
|
details.update(extra)
|
|
36
34
|
for handler in handlers:
|
|
37
35
|
await handler(details)
|
|
38
36
|
|
|
39
37
|
|
|
40
|
-
def retry_predicate(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
+
):
|
|
45
51
|
on_success = _ensure_coroutines(on_success)
|
|
46
52
|
on_backoff = _ensure_coroutines(on_backoff)
|
|
47
53
|
on_giveup = _ensure_coroutines(on_giveup)
|
|
@@ -54,7 +60,6 @@ def retry_predicate(target, wait_gen, predicate,
|
|
|
54
60
|
|
|
55
61
|
@functools.wraps(target)
|
|
56
62
|
async def retry(*args, **kwargs):
|
|
57
|
-
|
|
58
63
|
# update variables from outer function args
|
|
59
64
|
max_tries_value = _maybe_call(max_tries)
|
|
60
65
|
max_time_value = _maybe_call(max_time)
|
|
@@ -75,23 +80,22 @@ def retry_predicate(target, wait_gen, predicate,
|
|
|
75
80
|
|
|
76
81
|
ret = await target(*args, **kwargs)
|
|
77
82
|
if predicate(ret):
|
|
78
|
-
max_tries_exceeded =
|
|
79
|
-
max_time_exceeded = (
|
|
80
|
-
|
|
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
|
+
)
|
|
81
87
|
|
|
82
88
|
if max_tries_exceeded or max_time_exceeded:
|
|
83
89
|
await _call_handlers(on_giveup, **details, value=ret)
|
|
84
90
|
break
|
|
85
91
|
|
|
86
92
|
try:
|
|
87
|
-
seconds = _next_wait(wait, ret, jitter, elapsed,
|
|
88
|
-
max_time_value)
|
|
93
|
+
seconds = _next_wait(wait, ret, jitter, elapsed, max_time_value)
|
|
89
94
|
except StopIteration:
|
|
90
95
|
await _call_handlers(on_giveup, **details, value=ret)
|
|
91
96
|
break
|
|
92
97
|
|
|
93
|
-
await _call_handlers(on_backoff, **details, value=ret,
|
|
94
|
-
wait=seconds)
|
|
98
|
+
await _call_handlers(on_backoff, **details, value=ret, wait=seconds)
|
|
95
99
|
|
|
96
100
|
# Note: there is no convenient way to pass explicit event
|
|
97
101
|
# loop to decorator, so here we assume that either default
|
|
@@ -113,11 +117,21 @@ def retry_predicate(target, wait_gen, predicate,
|
|
|
113
117
|
return retry
|
|
114
118
|
|
|
115
119
|
|
|
116
|
-
def retry_exception(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
+
):
|
|
121
135
|
on_success = _ensure_coroutines(on_success)
|
|
122
136
|
on_backoff = _ensure_coroutines(on_backoff)
|
|
123
137
|
on_giveup = _ensure_coroutines(on_giveup)
|
|
@@ -129,7 +143,6 @@ def retry_exception(target, wait_gen, exception,
|
|
|
129
143
|
|
|
130
144
|
@functools.wraps(target)
|
|
131
145
|
async def retry(*args, **kwargs):
|
|
132
|
-
|
|
133
146
|
max_tries_value = _maybe_call(max_tries)
|
|
134
147
|
max_time_value = _maybe_call(max_time)
|
|
135
148
|
|
|
@@ -151,9 +164,10 @@ def retry_exception(target, wait_gen, exception,
|
|
|
151
164
|
ret = await target(*args, **kwargs)
|
|
152
165
|
except exception as e:
|
|
153
166
|
giveup_result = await giveup(e)
|
|
154
|
-
max_tries_exceeded =
|
|
155
|
-
max_time_exceeded = (
|
|
156
|
-
|
|
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
|
+
)
|
|
157
171
|
|
|
158
172
|
if giveup_result or max_tries_exceeded or max_time_exceeded:
|
|
159
173
|
await _call_handlers(on_giveup, **details, exception=e)
|
|
@@ -162,14 +176,12 @@ def retry_exception(target, wait_gen, exception,
|
|
|
162
176
|
return None
|
|
163
177
|
|
|
164
178
|
try:
|
|
165
|
-
seconds = _next_wait(wait, e, jitter, elapsed,
|
|
166
|
-
max_time_value)
|
|
179
|
+
seconds = _next_wait(wait, e, jitter, elapsed, max_time_value)
|
|
167
180
|
except StopIteration:
|
|
168
181
|
await _call_handlers(on_giveup, **details, exception=e)
|
|
169
182
|
raise e
|
|
170
183
|
|
|
171
|
-
await _call_handlers(on_backoff, **details, wait=seconds,
|
|
172
|
-
exception=e)
|
|
184
|
+
await _call_handlers(on_backoff, **details, wait=seconds, exception=e)
|
|
173
185
|
|
|
174
186
|
# Note: there is no convenient way to pass explicit event
|
|
175
187
|
# loop to decorator, so here we assume that either default
|
|
@@ -185,4 +197,5 @@ def retry_exception(target, wait_gen, exception,
|
|
|
185
197
|
await _call_handlers(on_success, **details)
|
|
186
198
|
|
|
187
199
|
return ret
|
|
200
|
+
|
|
188
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
|
|
@@ -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
|
|
|
@@ -120,21 +122,23 @@ def on_predicate(wait_gen: _WaitGenerator,
|
|
|
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
|
|
backoff/_sync.py
CHANGED
|
@@ -1,29 +1,35 @@
|
|
|
1
|
-
# coding:utf-8
|
|
2
1
|
import functools
|
|
3
2
|
import time
|
|
4
3
|
|
|
5
|
-
from backoff._common import
|
|
4
|
+
from backoff._common import _init_wait_gen, _maybe_call, _next_wait
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
def _call_handlers(hdlrs, target, args, kwargs, tries, elapsed, **extra):
|
|
9
8
|
details = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
"target": target,
|
|
10
|
+
"args": args,
|
|
11
|
+
"kwargs": kwargs,
|
|
12
|
+
"tries": tries,
|
|
13
|
+
"elapsed": elapsed,
|
|
15
14
|
}
|
|
16
15
|
details.update(extra)
|
|
17
16
|
for hdlr in hdlrs:
|
|
18
17
|
hdlr(details)
|
|
19
18
|
|
|
20
19
|
|
|
21
|
-
def retry_predicate(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
+
):
|
|
27
33
|
@functools.wraps(target)
|
|
28
34
|
def retry(*args, **kwargs):
|
|
29
35
|
max_tries_value = _maybe_call(max_tries)
|
|
@@ -45,23 +51,22 @@ def retry_predicate(target, wait_gen, predicate,
|
|
|
45
51
|
|
|
46
52
|
ret = target(*args, **kwargs)
|
|
47
53
|
if predicate(ret):
|
|
48
|
-
max_tries_exceeded =
|
|
49
|
-
max_time_exceeded = (
|
|
50
|
-
|
|
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
|
+
)
|
|
51
58
|
|
|
52
59
|
if max_tries_exceeded or max_time_exceeded:
|
|
53
60
|
_call_handlers(on_giveup, **details, value=ret)
|
|
54
61
|
break
|
|
55
62
|
|
|
56
63
|
try:
|
|
57
|
-
seconds = _next_wait(wait, ret, jitter, elapsed,
|
|
58
|
-
max_time_value)
|
|
64
|
+
seconds = _next_wait(wait, ret, jitter, elapsed, max_time_value)
|
|
59
65
|
except StopIteration:
|
|
60
66
|
_call_handlers(on_giveup, **details)
|
|
61
67
|
break
|
|
62
68
|
|
|
63
|
-
_call_handlers(on_backoff, **details,
|
|
64
|
-
value=ret, wait=seconds)
|
|
69
|
+
_call_handlers(on_backoff, **details, value=ret, wait=seconds)
|
|
65
70
|
|
|
66
71
|
time.sleep(seconds)
|
|
67
72
|
continue
|
|
@@ -74,12 +79,21 @@ def retry_predicate(target, wait_gen, predicate,
|
|
|
74
79
|
return retry
|
|
75
80
|
|
|
76
81
|
|
|
77
|
-
def retry_exception(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
+
):
|
|
83
97
|
@functools.wraps(target)
|
|
84
98
|
def retry(*args, **kwargs):
|
|
85
99
|
max_tries_value = _maybe_call(max_tries)
|
|
@@ -102,9 +116,10 @@ def retry_exception(target, wait_gen, exception,
|
|
|
102
116
|
try:
|
|
103
117
|
ret = target(*args, **kwargs)
|
|
104
118
|
except exception as e:
|
|
105
|
-
max_tries_exceeded =
|
|
106
|
-
max_time_exceeded = (
|
|
107
|
-
|
|
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
|
+
)
|
|
108
123
|
|
|
109
124
|
if giveup(e) or max_tries_exceeded or max_time_exceeded:
|
|
110
125
|
_call_handlers(on_giveup, **details, exception=e)
|
|
@@ -113,18 +128,17 @@ def retry_exception(target, wait_gen, exception,
|
|
|
113
128
|
return None
|
|
114
129
|
|
|
115
130
|
try:
|
|
116
|
-
seconds = _next_wait(wait, e, jitter, elapsed,
|
|
117
|
-
max_time_value)
|
|
131
|
+
seconds = _next_wait(wait, e, jitter, elapsed, max_time_value)
|
|
118
132
|
except StopIteration:
|
|
119
133
|
_call_handlers(on_giveup, **details, exception=e)
|
|
120
134
|
raise e
|
|
121
135
|
|
|
122
|
-
_call_handlers(on_backoff, **details, wait=seconds,
|
|
123
|
-
exception=e)
|
|
136
|
+
_call_handlers(on_backoff, **details, wait=seconds, exception=e)
|
|
124
137
|
|
|
125
138
|
time.sleep(seconds)
|
|
126
139
|
else:
|
|
127
140
|
_call_handlers(on_success, **details)
|
|
128
141
|
|
|
129
142
|
return ret
|
|
143
|
+
|
|
130
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
|
|
@@ -10,7 +8,6 @@ def expo(
|
|
|
10
8
|
factor: float = 1,
|
|
11
9
|
max_value: Optional[float] = None,
|
|
12
10
|
) -> Generator[float, Any, None]:
|
|
13
|
-
|
|
14
11
|
"""Generator for exponential decay.
|
|
15
12
|
|
|
16
13
|
Args:
|
|
@@ -37,7 +34,6 @@ def decay(
|
|
|
37
34
|
decay_factor: float = 1,
|
|
38
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:
|
backoff/types.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-backoff
|
|
3
|
-
Version: 2.3.
|
|
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
|
|
@@ -58,7 +58,7 @@ for asynchronous code.
|
|
|
58
58
|
Examples
|
|
59
59
|
========
|
|
60
60
|
|
|
61
|
-
Since Kenneth Reitz's `requests <
|
|
61
|
+
Since Kenneth Reitz's `requests <https://requests.readthedocs.io>`_ module
|
|
62
62
|
has become a defacto standard for synchronous HTTP clients in Python,
|
|
63
63
|
networking examples below are written using it, but it is in no way required
|
|
64
64
|
by the backoff module.
|
|
@@ -72,8 +72,10 @@ is raised. Here's an example using exponential backoff when any
|
|
|
72
72
|
|
|
73
73
|
.. code-block:: python
|
|
74
74
|
|
|
75
|
-
@backoff.on_exception(
|
|
76
|
-
|
|
75
|
+
@backoff.on_exception(
|
|
76
|
+
backoff.expo,
|
|
77
|
+
requests.exceptions.RequestException,
|
|
78
|
+
)
|
|
77
79
|
def get_url(url):
|
|
78
80
|
return requests.get(url)
|
|
79
81
|
|
|
@@ -82,9 +84,13 @@ the same backoff behavior is desired for more than one exception type:
|
|
|
82
84
|
|
|
83
85
|
.. code-block:: python
|
|
84
86
|
|
|
85
|
-
@backoff.on_exception(
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
@backoff.on_exception(
|
|
88
|
+
backoff.expo,
|
|
89
|
+
(
|
|
90
|
+
requests.exceptions.Timeout,
|
|
91
|
+
requests.exceptions.ConnectionError,
|
|
92
|
+
),
|
|
93
|
+
)
|
|
88
94
|
def get_url(url):
|
|
89
95
|
return requests.get(url)
|
|
90
96
|
|
|
@@ -98,9 +104,11 @@ of total time in seconds that can elapse before giving up.
|
|
|
98
104
|
|
|
99
105
|
.. code-block:: python
|
|
100
106
|
|
|
101
|
-
@backoff.on_exception(
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
@backoff.on_exception(
|
|
108
|
+
backoff.expo,
|
|
109
|
+
requests.exceptions.RequestException,
|
|
110
|
+
max_time=60,
|
|
111
|
+
)
|
|
104
112
|
def get_url(url):
|
|
105
113
|
return requests.get(url)
|
|
106
114
|
|
|
@@ -110,10 +118,12 @@ to make to the target function before giving up.
|
|
|
110
118
|
|
|
111
119
|
.. code-block:: python
|
|
112
120
|
|
|
113
|
-
@backoff.on_exception(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
@backoff.on_exception(
|
|
122
|
+
backoff.expo,
|
|
123
|
+
requests.exceptions.RequestException,
|
|
124
|
+
max_tries=8,
|
|
125
|
+
jitter=None,
|
|
126
|
+
)
|
|
117
127
|
def get_url(url):
|
|
118
128
|
return requests.get(url)
|
|
119
129
|
|
|
@@ -129,10 +139,13 @@ be retried:
|
|
|
129
139
|
def fatal_code(e):
|
|
130
140
|
return 400 <= e.response.status_code < 500
|
|
131
141
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
142
|
+
|
|
143
|
+
@backoff.on_exception(
|
|
144
|
+
backoff.expo,
|
|
145
|
+
requests.exceptions.RequestException,
|
|
146
|
+
max_time=300,
|
|
147
|
+
giveup=fatal_code,
|
|
148
|
+
)
|
|
136
149
|
def get_url(url):
|
|
137
150
|
return requests.get(url)
|
|
138
151
|
|
|
@@ -150,11 +163,14 @@ case, regardless of the logic in the `on_exception` handler.
|
|
|
150
163
|
def fatal_code(e):
|
|
151
164
|
return 400 <= e.response.status_code < 500
|
|
152
165
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
+
)
|
|
158
174
|
def get_url(url):
|
|
159
175
|
return requests.get(url)
|
|
160
176
|
|
|
@@ -174,7 +190,11 @@ return value of the target function is the empty list:
|
|
|
174
190
|
|
|
175
191
|
.. code-block:: python
|
|
176
192
|
|
|
177
|
-
@backoff.on_predicate(
|
|
193
|
+
@backoff.on_predicate(
|
|
194
|
+
backoff.fibo,
|
|
195
|
+
lambda x: x == [],
|
|
196
|
+
max_value=13,
|
|
197
|
+
)
|
|
178
198
|
def poll_for_messages(queue):
|
|
179
199
|
return queue.get()
|
|
180
200
|
|
|
@@ -196,7 +216,11 @@ gets a non-falsey result could be defined like like this:
|
|
|
196
216
|
|
|
197
217
|
.. code-block:: python
|
|
198
218
|
|
|
199
|
-
@backoff.on_predicate(
|
|
219
|
+
@backoff.on_predicate(
|
|
220
|
+
backoff.constant,
|
|
221
|
+
jitter=None,
|
|
222
|
+
interval=1,
|
|
223
|
+
)
|
|
200
224
|
def poll_for_message(queue):
|
|
201
225
|
return queue.get()
|
|
202
226
|
|
|
@@ -249,12 +273,16 @@ backoff behavior for different cases:
|
|
|
249
273
|
.. code-block:: python
|
|
250
274
|
|
|
251
275
|
@backoff.on_predicate(backoff.fibo, max_value=13)
|
|
252
|
-
@backoff.on_exception(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
+
)
|
|
258
286
|
def poll_for_message(queue):
|
|
259
287
|
return queue.get()
|
|
260
288
|
|
|
@@ -277,9 +305,13 @@ runtime to obtain the value:
|
|
|
277
305
|
# and that it has a dictionary-like 'config' property
|
|
278
306
|
return app.config["BACKOFF_MAX_TIME"]
|
|
279
307
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
308
|
+
|
|
309
|
+
@backoff.on_exception(
|
|
310
|
+
backoff.expo,
|
|
311
|
+
ValueError,
|
|
312
|
+
max_time=lookup_max_time,
|
|
313
|
+
)
|
|
314
|
+
def my_function(): ...
|
|
283
315
|
|
|
284
316
|
Event handlers
|
|
285
317
|
--------------
|
|
@@ -307,13 +339,18 @@ implemented like so:
|
|
|
307
339
|
.. code-block:: python
|
|
308
340
|
|
|
309
341
|
def backoff_hdlr(details):
|
|
310
|
-
print
|
|
311
|
-
|
|
312
|
-
|
|
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
|
+
|
|
313
348
|
|
|
314
|
-
@backoff.on_exception(
|
|
315
|
-
|
|
316
|
-
|
|
349
|
+
@backoff.on_exception(
|
|
350
|
+
backoff.expo,
|
|
351
|
+
requests.exceptions.RequestException,
|
|
352
|
+
on_backoff=backoff_hdlr,
|
|
353
|
+
)
|
|
317
354
|
def get_url(url):
|
|
318
355
|
return requests.get(url)
|
|
319
356
|
|
|
@@ -325,9 +362,14 @@ handler functions as the value of the ``on_backoff`` keyword arg:
|
|
|
325
362
|
|
|
326
363
|
.. code-block:: python
|
|
327
364
|
|
|
328
|
-
@backoff.on_exception(
|
|
329
|
-
|
|
330
|
-
|
|
365
|
+
@backoff.on_exception(
|
|
366
|
+
backoff.expo,
|
|
367
|
+
requests.exceptions.RequestException,
|
|
368
|
+
on_backoff=[
|
|
369
|
+
backoff_hdlr1,
|
|
370
|
+
backoff_hdlr2,
|
|
371
|
+
],
|
|
372
|
+
)
|
|
331
373
|
def get_url(url):
|
|
332
374
|
return requests.get(url)
|
|
333
375
|
|
|
@@ -358,9 +400,13 @@ asynchronous HTTP client/server library.
|
|
|
358
400
|
|
|
359
401
|
.. code-block:: python
|
|
360
402
|
|
|
361
|
-
@backoff.on_exception(
|
|
403
|
+
@backoff.on_exception(
|
|
404
|
+
backoff.expo,
|
|
405
|
+
aiohttp.ClientError,
|
|
406
|
+
max_time=60,
|
|
407
|
+
)
|
|
362
408
|
async def get_url(url):
|
|
363
|
-
async with aiohttp.ClientSession(raise_for_status=True) as session:
|
|
409
|
+
async with aiohttp.ClientSession(raise_for_status=True) as session: # noqa: SIM117
|
|
364
410
|
async with session.get(url) as response:
|
|
365
411
|
return await response.text()
|
|
366
412
|
|
|
@@ -375,7 +421,7 @@ as:
|
|
|
375
421
|
|
|
376
422
|
.. code-block:: python
|
|
377
423
|
|
|
378
|
-
logging.getLogger(
|
|
424
|
+
logging.getLogger("backoff").addHandler(logging.StreamHandler())
|
|
379
425
|
|
|
380
426
|
The default logging level is INFO, which corresponds to logging
|
|
381
427
|
anytime a retry event occurs. If you would instead like to log
|
|
@@ -383,7 +429,7 @@ only when a giveup event occurs, set the logger level to ERROR.
|
|
|
383
429
|
|
|
384
430
|
.. code-block:: python
|
|
385
431
|
|
|
386
|
-
logging.getLogger(
|
|
432
|
+
logging.getLogger("backoff").setLevel(logging.ERROR)
|
|
387
433
|
|
|
388
434
|
It is also possible to specify an alternate logger with the ``logger``
|
|
389
435
|
keyword argument. If a string value is specified the logger will be
|
|
@@ -391,25 +437,30 @@ looked up by name.
|
|
|
391
437
|
|
|
392
438
|
.. code-block:: python
|
|
393
439
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
440
|
+
@backoff.on_exception(
|
|
441
|
+
backoff.expo,
|
|
442
|
+
requests.exceptions.RequestException,
|
|
443
|
+
logger="my_logger",
|
|
444
|
+
)
|
|
445
|
+
def my_function(): ...
|
|
398
446
|
|
|
399
447
|
It is also supported to specify a Logger (or LoggerAdapter) object
|
|
400
448
|
directly.
|
|
401
449
|
|
|
402
450
|
.. code-block:: python
|
|
403
451
|
|
|
404
|
-
my_logger = logging.getLogger(
|
|
452
|
+
my_logger = logging.getLogger("my_logger")
|
|
405
453
|
my_handler = logging.StreamHandler()
|
|
406
454
|
my_logger.addHandler(my_handler)
|
|
407
455
|
my_logger.setLevel(logging.ERROR)
|
|
408
456
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
457
|
+
|
|
458
|
+
@backoff.on_exception(
|
|
459
|
+
backoff.expo,
|
|
460
|
+
requests.exceptions.RequestException,
|
|
461
|
+
logger=my_logger,
|
|
462
|
+
)
|
|
463
|
+
def my_function(): ...
|
|
413
464
|
|
|
414
465
|
Default logging can be disabled all together by specifying
|
|
415
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=e09CDHH3x16oCS5SEi8RRK9DIyBqGT5pK1JE80awIqk,6675
|
|
3
|
-
backoff/_common.py,sha256=cz5AKeSyFxjZ5-g9YK_MB_u1yRt3N3x81EbMZGz7inI,3480
|
|
4
|
-
backoff/_decorator.py,sha256=EKqIQ6Igv2sNGAYhZlqsWGHyvu90AE9HQvTj8joXJA8,9820
|
|
5
|
-
backoff/_jitter.py,sha256=LjJShpjryk9sWBCWiz-3UX1DJCx6rebNJ5Bf3nPMlYQ,782
|
|
6
|
-
backoff/_sync.py,sha256=2Klcl79pg7AeX6h6j0Pnt-Cfx0w3CftgiMSPRPq6TL4,4089
|
|
7
|
-
backoff/_typing.py,sha256=K4xjbkoQUuKt5E1cEAUgIPrhrV2lWD3BXmgu8_Mv7rQ,1422
|
|
8
|
-
backoff/_wait_gen.py,sha256=aPTQQHmi9k_XpOUsuSoGyW-EpEOwUErLfG_IbNXmaY0,3262
|
|
9
|
-
backoff/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
backoff/types.py,sha256=fPaXJ_J81Bme-F2-RKeVDjB3Rvfnj9K4tumDDS03bNs,74
|
|
11
|
-
python_backoff-2.3.0.dist-info/METADATA,sha256=RjKF4ttvWjzuy-7IPMhbevvS92tpeyu-woDJs3IOMMs,14910
|
|
12
|
-
python_backoff-2.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
13
|
-
python_backoff-2.3.0.dist-info/licenses/LICENSE,sha256=KmtNX4hNTXob8E6n3xlEzxKzLjWnmobQoHWi0_QPuaw,1077
|
|
14
|
-
python_backoff-2.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|