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 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
- 'on_predicate',
21
- 'on_exception',
22
- 'constant',
23
- 'expo',
24
- 'decay',
25
- 'fibo',
26
- 'runtime',
27
- 'full_jitter',
28
- 'random_jitter',
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.2.1"
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
- from datetime import timedelta
4
+ import time
7
5
 
8
- from backoff._common import (_init_wait_gen, _maybe_call, _next_wait)
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
- 'target': target,
31
- 'args': args,
32
- 'kwargs': kwargs,
33
- 'tries': tries,
34
- 'elapsed': elapsed,
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(target, wait_gen, predicate,
42
- *,
43
- max_tries, max_time, jitter,
44
- on_success, on_backoff, on_giveup,
45
- wait_gen_kwargs):
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 = datetime.datetime.now()
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 = timedelta.total_seconds(datetime.datetime.now() - start)
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 = (tries == max_tries_value)
80
- max_time_exceeded = (max_time_value is not None and
81
- elapsed >= max_time_value)
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(target, wait_gen, exception,
118
- *,
119
- max_tries, max_time, jitter, giveup,
120
- on_success, on_backoff, on_giveup, raise_on_giveup,
121
- wait_gen_kwargs):
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 = datetime.datetime.now()
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 = timedelta.total_seconds(datetime.datetime.now() - start)
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 = (tries == max_tries_value)
156
- max_time_exceeded = (max_time_value is not None and
157
- elapsed >= max_time_value)
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('backoff')
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, *, default_handler=None, logger=None, log_level=None
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, logger=logger, log_level=log_level
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, '__iter__'):
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['target'].__name__, details['wait']]
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['value'])
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['target'].__name__, details['tries']]
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['value'])
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(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) -> Callable[[_CallableT], _CallableT]:
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(wait_gen: _WaitGenerator,
124
- exception: _MaybeSequence[Type[Exception]],
125
- *,
126
- max_tries: Optional[_MaybeCallable[int]] = None,
127
- max_time: Optional[_MaybeCallable[float]] = None,
128
- jitter: Union[_Jitterer, None] = full_jitter,
129
- giveup: _Predicate[Exception] = lambda e: False,
130
- on_success: Union[_Handler, Iterable[_Handler], None] = None,
131
- on_backoff: Union[_Handler, Iterable[_Handler], None] = None,
132
- on_giveup: Union[_Handler, Iterable[_Handler], None] = None,
133
- raise_on_giveup: bool = True,
134
- logger: _MaybeLogger = 'backoff',
135
- backoff_log_level: int = logging.INFO,
136
- giveup_log_level: int = logging.ERROR,
137
- **wait_gen_kwargs: Any) -> Callable[[_CallableT], _CallableT]:
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/_jitter.py CHANGED
@@ -1,5 +1,3 @@
1
- # coding:utf-8
2
-
3
1
  import random
4
2
 
5
3
 
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 (_init_wait_gen, _maybe_call, _next_wait)
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
- 'target': target,
13
- 'args': args,
14
- 'kwargs': kwargs,
15
- 'tries': tries,
16
- 'elapsed': elapsed,
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(target, wait_gen, predicate,
24
- *,
25
- max_tries, max_time, jitter,
26
- on_success, on_backoff, on_giveup,
27
- wait_gen_kwargs):
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 = datetime.datetime.now()
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 = timedelta.total_seconds(datetime.datetime.now() - start)
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 = (tries == max_tries_value)
51
- max_time_exceeded = (max_time_value is not None and
52
- elapsed >= max_time_value)
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(target, wait_gen, exception,
80
- *,
81
- max_tries, max_time, jitter, giveup,
82
- on_success, on_backoff, on_giveup, raise_on_giveup,
83
- wait_gen_kwargs):
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 = datetime.datetime.now()
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 = timedelta.total_seconds(datetime.datetime.now() - start)
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 = (tries == max_tries_value)
108
- max_time_exceeded = (max_time_value is not None and
109
- elapsed >= max_time_value)
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 sys
4
- from typing import (Any, Callable, Coroutine, Dict, Generator, Sequence, Tuple,
5
- TypeVar, Union)
6
-
7
- if sys.version_info >= (3, 8): # pragma: no cover
8
- from typing import TypedDict
9
- else: # pragma: no cover
10
- # use typing_extensions if installed but don't require it
11
- try:
12
- from typing_extensions import TypedDict
13
- except ImportError:
14
- class TypedDict(dict):
15
- def __init_subclass__(cls, **kwargs: Any) -> None:
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: Callable[..., Any]
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('_CallableT', bound=Callable[..., Any])
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
- n = 0
22
+ base_n: float = 1
26
23
  while True:
27
- a = factor * base ** n
24
+ a = factor * base_n
28
25
  if max_value is None or a < max_value:
29
26
  yield a
30
- n += 1
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,5 @@
1
- # coding:utf-8
2
1
  from ._typing import Details
3
2
 
4
3
  __all__ = [
5
- 'Details'
4
+ "Details",
6
5
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-backoff
3
- Version: 2.2.2rc2
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.7
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 <http://python-requests.org>`_ module
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(backoff.expo,
77
- requests.exceptions.RequestException)
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(backoff.expo,
87
- (requests.exceptions.Timeout,
88
- requests.exceptions.ConnectionError))
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(backoff.expo,
103
- requests.exceptions.RequestException,
104
- max_time=60)
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(backoff.expo,
115
- requests.exceptions.RequestException,
116
- max_tries=8,
117
- jitter=None)
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
- @backoff.on_exception(backoff.expo,
134
- requests.exceptions.RequestException,
135
- max_time=300,
136
- giveup=fatal_code)
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
- @backoff.on_exception(backoff.expo,
155
- requests.exceptions.RequestException,
156
- max_time=300,
157
- raise_on_giveup=False,
158
- giveup=fatal_code)
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(backoff.fibo, lambda x: x == [], max_value=13)
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(backoff.constant, jitter=None, interval=1)
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(backoff.expo,
254
- requests.exceptions.HTTPError,
255
- max_time=60)
256
- @backoff.on_exception(backoff.expo,
257
- requests.exceptions.Timeout,
258
- max_time=300)
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
- @backoff.on_exception(backoff.expo,
282
- ValueError,
283
- max_time=lookup_max_time)
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 ("Backing off {wait:0.1f} seconds after {tries} tries "
312
- "calling function {target} with args {args} and kwargs "
313
- "{kwargs}".format(**details))
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(backoff.expo,
316
- requests.exceptions.RequestException,
317
- on_backoff=backoff_hdlr)
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(backoff.expo,
330
- requests.exceptions.RequestException,
331
- on_backoff=[backoff_hdlr1, backoff_hdlr2])
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(backoff.expo, aiohttp.ClientError, max_time=60)
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('backoff').addHandler(logging.StreamHandler())
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('backoff').setLevel(logging.ERROR)
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
- @backoff.on_exception(backoff.expo,
396
- requests.exceptions.RequestException,
397
- logger='my_logger')
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('my_logger')
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
- @backoff.on_exception(backoff.expo,
411
- requests.exceptions.RequestException,
412
- logger=my_logger)
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,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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,,