python-backoff 2.3.0__py3-none-any.whl → 2.4.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.
backoff/_common.py CHANGED
@@ -1,43 +1,71 @@
1
- # coding:utf-8
1
+ from __future__ import annotations
2
2
 
3
3
  import functools
4
4
  import logging
5
- import sys
5
+ import time
6
6
  import traceback
7
7
  import warnings
8
+ from typing import TYPE_CHECKING, Any, Callable, TypeVar
9
+
10
+ if TYPE_CHECKING:
11
+ import sys
12
+ from collections.abc import Generator, Iterable
13
+
14
+ from backoff._typing import (
15
+ ContextDetails,
16
+ Details,
17
+ _ContextHandler,
18
+ _Jitterer,
19
+ _MaybeCallable,
20
+ _WaitGenerator,
21
+ )
22
+
23
+ if sys.version_info >= (3, 11):
24
+ from typing import Self
25
+ else:
26
+ from typing_extensions import Self
8
27
 
9
28
 
10
29
  # Use module-specific logger with a default null handler.
11
- _logger = logging.getLogger('backoff')
30
+ _logger = logging.getLogger("backoff")
12
31
  _logger.addHandler(logging.NullHandler()) # pragma: no cover
13
32
  _logger.setLevel(logging.INFO)
14
33
 
34
+ T = TypeVar("T")
35
+ _HandlerT = TypeVar("_HandlerT")
36
+
15
37
 
16
38
  # Evaluate arg that can be either a fixed value or a callable.
17
- def _maybe_call(f, *args, **kwargs):
39
+ def _maybe_call(f: _MaybeCallable[T] | None, *args: Any, **kwargs: Any) -> T | None:
18
40
  if callable(f):
19
41
  try:
20
- return f(*args, **kwargs)
42
+ return f(*args, **kwargs) # ty:ignore[call-top-callable, invalid-return-type]
21
43
  except TypeError:
22
- return f
44
+ return f # type: ignore[return-value] # ty:ignore[invalid-return-type]
23
45
  else:
24
46
  return f
25
47
 
26
48
 
27
- def _init_wait_gen(wait_gen, wait_gen_kwargs):
49
+ def _init_wait_gen(
50
+ wait_gen: _WaitGenerator,
51
+ wait_gen_kwargs: dict[str, Any],
52
+ ) -> Generator[float, Any, None]:
28
53
  kwargs = {k: _maybe_call(v) for k, v in wait_gen_kwargs.items()}
29
54
  initialized = wait_gen(**kwargs)
30
55
  initialized.send(None) # Initialize with an empty send
31
56
  return initialized
32
57
 
33
58
 
34
- def _next_wait(wait, send_value, jitter, elapsed, max_time):
59
+ def _next_wait(
60
+ wait: Generator[float, None, None],
61
+ send_value: Any,
62
+ jitter: _Jitterer | None,
63
+ elapsed: float,
64
+ max_time: float | None,
65
+ ) -> float:
35
66
  value = wait.send(send_value)
36
67
  try:
37
- if jitter is not None:
38
- seconds = jitter(value)
39
- else:
40
- seconds = value
68
+ seconds = jitter(value) if jitter is not None else value
41
69
  except TypeError:
42
70
  warnings.warn(
43
71
  "Nullary jitter function signature is deprecated. Use "
@@ -47,7 +75,7 @@ def _next_wait(wait, send_value, jitter, elapsed, max_time):
47
75
  stacklevel=2,
48
76
  )
49
77
 
50
- seconds = value + jitter()
78
+ seconds = value + jitter() # type: ignore[call-arg, misc] # ty:ignore[missing-argument, call-non-callable]
51
79
 
52
80
  # don't sleep longer than remaining allotted max_time
53
81
  if max_time is not None:
@@ -56,7 +84,103 @@ def _next_wait(wait, send_value, jitter, elapsed, max_time):
56
84
  return seconds
57
85
 
58
86
 
59
- def _prepare_logger(logger):
87
+ class _RetryState:
88
+ """Bookkeeping shared by the sync and async retry loops.
89
+
90
+ Tracks try count and elapsed time, and owns the initialized wait
91
+ generator, so `retry_predicate`/`retry_exception` only need to drive
92
+ the call/check/sleep sequence around it.
93
+ """
94
+
95
+ __slots__ = (
96
+ "elapsed",
97
+ "max_time",
98
+ "max_tries",
99
+ "start",
100
+ "tries",
101
+ "wait",
102
+ )
103
+
104
+ def __init__(
105
+ self,
106
+ wait_gen: _WaitGenerator,
107
+ wait_gen_kwargs: dict[str, Any],
108
+ *,
109
+ max_tries: _MaybeCallable[int] | None = None,
110
+ max_time: _MaybeCallable[float] | None = None,
111
+ ) -> None:
112
+ self.tries = 0
113
+ self.elapsed: float = 0
114
+ self.start = time.monotonic()
115
+ self.max_tries = _maybe_call(max_tries)
116
+ self.max_time = _maybe_call(max_time)
117
+ self.wait = _init_wait_gen(wait_gen, wait_gen_kwargs)
118
+
119
+ def start_attempt(self) -> None:
120
+ self.tries += 1
121
+
122
+ def record_elapsed(self) -> float:
123
+ self.elapsed = time.monotonic() - self.start
124
+ return self.elapsed
125
+
126
+ def exhausted(self) -> bool:
127
+ max_tries_exceeded = self.tries == self.max_tries
128
+ max_time_exceeded = self.max_time is not None and self.elapsed >= self.max_time
129
+ return max_tries_exceeded or max_time_exceeded
130
+
131
+ def next_wait(self, send_value: Any, jitter: _Jitterer | None) -> float:
132
+ return _next_wait(self.wait, send_value, jitter, self.elapsed, self.max_time)
133
+
134
+
135
+ class _Attempt:
136
+ """A single attempt yielded by `retry_context`/`aretry_context`.
137
+
138
+ Used as `with attempt: ...`. Exceptions matching `exception_types` are
139
+ caught here so the driving generator (not this object) decides whether
140
+ to retry, sleep, or let the exception propagate; anything else, or no
141
+ exception at all, is left for the `with` block's normal exit behavior.
142
+ """
143
+
144
+ __slots__ = (
145
+ "exception",
146
+ "exception_types",
147
+ )
148
+
149
+ def __init__(
150
+ self,
151
+ exception_types: type[Exception] | tuple[type[Exception], ...],
152
+ ) -> None:
153
+ self.exception_types = exception_types
154
+ self.exception: BaseException | None = None
155
+
156
+ def __enter__(self) -> Self:
157
+ return self
158
+
159
+ def __exit__(
160
+ self,
161
+ exc_type: type[BaseException] | None,
162
+ exc: BaseException | None,
163
+ tb: object,
164
+ ) -> bool:
165
+ if exc_type is None:
166
+ # self.outcome = _Success()
167
+ return False
168
+
169
+ if not issubclass(exc_type, self.exception_types):
170
+ return False # not ours; propagate immediately
171
+
172
+ self.exception = exc
173
+ return True # suppress for now; the driving generator decides next
174
+
175
+
176
+ def _dispatch_handlers(handlers: Iterable[_ContextHandler], **details: Any) -> None:
177
+ for hdlr in handlers:
178
+ hdlr(details) # type: ignore[arg-type] # ty:ignore[invalid-argument-type]
179
+
180
+
181
+ def _prepare_logger(
182
+ logger: str | logging.Logger | logging.LoggerAdapter | None,
183
+ ) -> logging.Logger | logging.LoggerAdapter | None:
60
184
  if isinstance(logger, str):
61
185
  logger = logging.getLogger(logger)
62
186
  return logger
@@ -65,56 +189,103 @@ def _prepare_logger(logger):
65
189
  # Configure handler list with user specified handler and optionally
66
190
  # with a default handler bound to the specified logger.
67
191
  def _config_handlers(
68
- user_handlers, *, default_handler=None, logger=None, log_level=None,
69
- ):
70
- handlers = []
192
+ user_handlers: _HandlerT | Iterable[_HandlerT] | None,
193
+ *,
194
+ default_handler: Callable[..., None] | None = None,
195
+ logger: logging.Logger | logging.LoggerAdapter | None = None,
196
+ log_level: int | None = None,
197
+ ) -> list[_HandlerT]:
198
+ handlers: list[_HandlerT] = []
71
199
  if logger is not None:
72
200
  assert log_level is not None, "Log level is not specified"
201
+ assert default_handler is not None, "Default handler is not specified"
73
202
  # bind the specified logger to the default log handler
74
203
  log_handler = functools.partial(
75
- default_handler, logger=logger, log_level=log_level,
204
+ default_handler,
205
+ logger=logger,
206
+ log_level=log_level,
76
207
  )
77
- handlers.append(log_handler)
208
+ handlers.append(log_handler) # type: ignore[arg-type] # ty:ignore[invalid-argument-type]
78
209
 
79
210
  if user_handlers is None:
80
211
  return handlers
81
212
 
82
213
  # user specified handlers can either be an iterable of handlers
83
214
  # or a single handler. either way append them to the list.
84
- if hasattr(user_handlers, '__iter__'):
215
+ if hasattr(user_handlers, "__iter__"):
85
216
  # add all handlers in the iterable
86
- handlers += list(user_handlers)
217
+ # pyrefly: ignore [bad-argument-type]
218
+ handlers += list(user_handlers) # ty:ignore[invalid-argument-type]
87
219
  else:
88
220
  # append a single handler
221
+ # pyrefly: ignore [bad-argument-type]
89
222
  handlers.append(user_handlers)
90
223
 
91
224
  return handlers
92
225
 
93
226
 
94
227
  # Default backoff handler
95
- def _log_backoff(details, logger, log_level):
228
+ def _log_backoff(
229
+ details: Details,
230
+ logger: logging.Logger | logging.LoggerAdapter,
231
+ log_level: int,
232
+ ) -> None:
96
233
  msg = "Backing off %s(...) for %.1fs (%s)"
97
- log_args = [details['target'].__name__, details['wait']]
234
+ log_args = [details["target"].__name__, details["wait"]] # ty:ignore[unresolved-attribute]
98
235
 
99
- exc_typ, exc, _ = sys.exc_info()
236
+ exc = details.get("exception")
100
237
  if exc is not None:
101
- exc_fmt = traceback.format_exception_only(exc_typ, exc)[-1]
238
+ exc_fmt = traceback.format_exception_only(type(exc), exc)[-1]
102
239
  log_args.append(exc_fmt.rstrip("\n"))
103
240
  else:
104
- log_args.append(details['value'])
241
+ log_args.append(details["value"])
105
242
  logger.log(log_level, msg, *log_args)
106
243
 
107
244
 
108
245
  # Default giveup handler
109
- def _log_giveup(details, logger, log_level):
246
+ def _log_giveup(
247
+ details: Details,
248
+ logger: logging.Logger | logging.LoggerAdapter,
249
+ log_level: int,
250
+ ) -> None:
110
251
  msg = "Giving up %s(...) after %d tries (%s)"
111
- log_args = [details['target'].__name__, details['tries']]
252
+ log_args = [details["target"].__name__, details["tries"]] # ty:ignore[unresolved-attribute]
112
253
 
113
- exc_typ, exc, _ = sys.exc_info()
254
+ exc = details.get("exception")
114
255
  if exc is not None:
115
- exc_fmt = traceback.format_exception_only(exc_typ, exc)[-1]
256
+ exc_fmt = traceback.format_exception_only(type(exc), exc)[-1]
116
257
  log_args.append(exc_fmt.rstrip("\n"))
117
258
  else:
118
- log_args.append(details['value'])
259
+ log_args.append(details["value"])
119
260
 
120
261
  logger.log(log_level, msg, *log_args)
262
+
263
+
264
+ # Default backoff handler for retry_context/aretry_context (no wrapped
265
+ # callable, so no name to log; the exception is read from `details`
266
+ # directly since it's no longer the active exception by this point).
267
+ def _log_backoff_context(
268
+ details: ContextDetails,
269
+ logger: logging.Logger | logging.LoggerAdapter,
270
+ log_level: int,
271
+ ) -> None:
272
+ logger.log(
273
+ log_level,
274
+ "Backing off retry_context(...) for %.1fs (%s)",
275
+ details["wait"],
276
+ details.get("exception"),
277
+ )
278
+
279
+
280
+ # Default giveup handler for retry_context/aretry_context.
281
+ def _log_giveup_context(
282
+ details: ContextDetails,
283
+ logger: logging.Logger | logging.LoggerAdapter,
284
+ log_level: int,
285
+ ) -> None:
286
+ logger.log(
287
+ log_level,
288
+ "Giving up retry_context(...) after %d tries (%s)",
289
+ details["tries"],
290
+ details.get("exception"),
291
+ )
backoff/_decorator.py CHANGED
@@ -1,42 +1,62 @@
1
- # coding:utf-8
1
+ from __future__ import annotations
2
+
2
3
  import inspect
3
4
  import logging
4
5
  import operator
5
- from typing import Any, Callable, Iterable, Optional, Type, Union
6
+ from typing import TYPE_CHECKING, Any, Callable, TypeVar
6
7
 
8
+ from backoff import _async, _sync
7
9
  from backoff._common import (
8
- _prepare_logger,
9
10
  _config_handlers,
10
11
  _log_backoff,
12
+ _log_backoff_context,
11
13
  _log_giveup,
14
+ _log_giveup_context,
15
+ _prepare_logger,
12
16
  )
13
17
  from backoff._jitter import full_jitter
14
- from backoff import _async, _sync
15
- from backoff._typing import (
16
- _CallableT,
17
- _Handler,
18
- _Jitterer,
19
- _MaybeCallable,
20
- _MaybeLogger,
21
- _MaybeSequence,
22
- _Predicate,
23
- _WaitGenerator,
24
- )
18
+ from backoff._wait_gen import expo
25
19
 
20
+ if TYPE_CHECKING:
21
+ import sys
22
+ from collections.abc import AsyncGenerator, Generator, Iterable
26
23
 
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]:
24
+ from backoff._common import _Attempt
25
+ from backoff._typing import (
26
+ _ContextHandler,
27
+ _Handler,
28
+ _Jitterer,
29
+ _MaybeCallable,
30
+ _MaybeLogger,
31
+ _MaybeTuple,
32
+ _Predicate,
33
+ _WaitGenerator,
34
+ )
35
+
36
+ if sys.version_info >= (3, 10):
37
+ from typing import ParamSpec
38
+ else:
39
+ from typing_extensions import ParamSpec
40
+
41
+ T = TypeVar("T")
42
+ P = ParamSpec("P")
43
+
44
+
45
+ def on_predicate(
46
+ wait_gen: _WaitGenerator,
47
+ predicate: _Predicate[Any] = operator.not_,
48
+ *,
49
+ max_tries: _MaybeCallable[int] | None = None,
50
+ max_time: _MaybeCallable[float] | None = None,
51
+ jitter: _Jitterer | None = full_jitter,
52
+ on_success: _Handler | Iterable[_Handler] | None = None,
53
+ on_backoff: _Handler | Iterable[_Handler] | None = None,
54
+ on_giveup: _Handler | Iterable[_Handler] | None = None,
55
+ logger: _MaybeLogger = "backoff",
56
+ backoff_log_level: int = logging.INFO,
57
+ giveup_log_level: int = logging.ERROR,
58
+ **wait_gen_kwargs: Any,
59
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]:
40
60
  """Returns decorator for backoff and retry triggered by predicate.
41
61
 
42
62
  Args:
@@ -80,7 +100,8 @@ def on_predicate(wait_gen: _WaitGenerator,
80
100
  args will first be evaluated and their return values passed.
81
101
  This is useful for runtime configuration.
82
102
  """
83
- def decorate(target):
103
+
104
+ def decorate(target: Callable[P, T]) -> Callable[P, T]:
84
105
  nonlocal logger, on_success, on_backoff, on_giveup
85
106
 
86
107
  logger = _prepare_logger(logger)
@@ -120,21 +141,23 @@ def on_predicate(wait_gen: _WaitGenerator,
120
141
  return decorate
121
142
 
122
143
 
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]:
144
+ def on_exception(
145
+ wait_gen: _WaitGenerator,
146
+ exception: _MaybeTuple[type[Exception]],
147
+ *,
148
+ max_tries: _MaybeCallable[int] | None = None,
149
+ max_time: _MaybeCallable[float] | None = None,
150
+ jitter: _Jitterer | None = full_jitter,
151
+ giveup: _Predicate[Exception] = lambda e: False,
152
+ on_success: _Handler | Iterable[_Handler] | None = None,
153
+ on_backoff: _Handler | Iterable[_Handler] | None = None,
154
+ on_giveup: _Handler | Iterable[_Handler] | None = None,
155
+ raise_on_giveup: bool = True,
156
+ logger: _MaybeLogger = "backoff",
157
+ backoff_log_level: int = logging.INFO,
158
+ giveup_log_level: int = logging.ERROR,
159
+ **wait_gen_kwargs: Any,
160
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]:
138
161
  """Returns decorator for backoff and retry triggered by exception.
139
162
 
140
163
  Args:
@@ -180,7 +203,8 @@ def on_exception(wait_gen: _WaitGenerator,
180
203
  args will first be evaluated and their return values passed.
181
204
  This is useful for runtime configuration.
182
205
  """
183
- def decorate(target):
206
+
207
+ def decorate(target: Callable[P, T]) -> Callable[P, T]:
184
208
  nonlocal logger, on_success, on_backoff, on_giveup
185
209
 
186
210
  logger = _prepare_logger(logger)
@@ -220,3 +244,155 @@ def on_exception(wait_gen: _WaitGenerator,
220
244
 
221
245
  # Return a function which decorates a target with a retry loop.
222
246
  return decorate
247
+
248
+
249
+ def retry_context(
250
+ exception: _MaybeTuple[type[Exception]] = Exception,
251
+ wait_gen: _WaitGenerator = expo,
252
+ *,
253
+ max_tries: _MaybeCallable[int] | None = None,
254
+ max_time: _MaybeCallable[float] | None = None,
255
+ jitter: _Jitterer | None = full_jitter,
256
+ giveup: _Predicate[BaseException] = lambda e: False,
257
+ on_success: _ContextHandler | Iterable[_ContextHandler] | None = None,
258
+ on_backoff: _ContextHandler | Iterable[_ContextHandler] | None = None,
259
+ on_giveup: _ContextHandler | Iterable[_ContextHandler] | None = None,
260
+ raise_on_giveup: bool = True,
261
+ logger: _MaybeLogger = "backoff",
262
+ backoff_log_level: int = logging.INFO,
263
+ giveup_log_level: int = logging.ERROR,
264
+ **wait_gen_kwargs: Any,
265
+ ) -> Generator[_Attempt, None, None]:
266
+ """Returns a generator of retry attempts, for direct use with a `for` loop.
267
+
268
+ Unlike `on_exception`, this doesn't wrap a whole function; it lets a
269
+ caller retry an arbitrary block of code:
270
+
271
+ for attempt in backoff.retry_context(ValueError, backoff.expo):
272
+ with attempt:
273
+ do_something()
274
+
275
+ Each `attempt` is a context manager: exceptions matching `exception`
276
+ are caught, and the loop either sleeps and retries or lets the
277
+ exception (or a different one) propagate once retries are exhausted.
278
+ Succeeding (no exception raised in the `with` block) ends the loop.
279
+
280
+ Args:
281
+ exception: An exception type (or tuple of types) which triggers
282
+ backoff.
283
+ wait_gen: A generator yielding successive wait times in seconds.
284
+ max_tries: The maximum number of attempts to make before giving
285
+ up. Once exhausted, the exception will be allowed to escape.
286
+ The default value of None means there is no limit to the
287
+ number of tries. If a callable is passed, it will be
288
+ evaluated at runtime and its return value used.
289
+ max_time: The maximum total amount of time to try for before
290
+ giving up. Once expired, the exception will be allowed to
291
+ escape. If a callable is passed, it will be evaluated at
292
+ runtime and its return value used.
293
+ jitter: A function of the value yielded by wait_gen returning
294
+ the actual time to wait. Jittered by default using
295
+ full_jitter; disable with jitter=None.
296
+ giveup: Function accepting an exception instance and returning
297
+ whether or not to give up. Optional. The default is to
298
+ always continue.
299
+ on_success: Callable (or iterable of callables) with a unary
300
+ signature called on success. The parameter is a dict with
301
+ `tries` and `elapsed`.
302
+ on_backoff: Callable (or iterable of callables) called on
303
+ backoff. The parameter dict additionally has `wait` and
304
+ `exception`.
305
+ on_giveup: Callable (or iterable of callables) called when
306
+ giving up. The parameter dict additionally has `exception`.
307
+ raise_on_giveup: Boolean indicating whether the registered
308
+ exception should be raised on giveup. Defaults to `True`.
309
+ logger: Name or Logger object to log to. Defaults to 'backoff'.
310
+ backoff_log_level: log level for the backoff event. Defaults to "INFO"
311
+ giveup_log_level: log level for the give up event. Defaults to "ERROR"
312
+ **wait_gen_kwargs: Any additional keyword args specified will be
313
+ passed to wait_gen when it is initialized.
314
+ """
315
+ logger = _prepare_logger(logger)
316
+ on_success = _config_handlers(on_success)
317
+ on_backoff = _config_handlers(
318
+ on_backoff,
319
+ default_handler=_log_backoff_context,
320
+ logger=logger,
321
+ log_level=backoff_log_level,
322
+ )
323
+ on_giveup = _config_handlers(
324
+ on_giveup,
325
+ default_handler=_log_giveup_context,
326
+ logger=logger,
327
+ log_level=giveup_log_level,
328
+ )
329
+
330
+ return _sync.retry_context(
331
+ exception,
332
+ wait_gen,
333
+ max_tries=max_tries,
334
+ max_time=max_time,
335
+ jitter=jitter,
336
+ giveup=giveup,
337
+ on_success=on_success,
338
+ on_backoff=on_backoff,
339
+ on_giveup=on_giveup,
340
+ raise_on_giveup=raise_on_giveup,
341
+ wait_gen_kwargs=wait_gen_kwargs,
342
+ )
343
+
344
+
345
+ def aretry_context(
346
+ exception: _MaybeTuple[type[Exception]] = Exception,
347
+ wait_gen: _WaitGenerator = expo,
348
+ *,
349
+ max_tries: _MaybeCallable[int] | None = None,
350
+ max_time: _MaybeCallable[float] | None = None,
351
+ jitter: _Jitterer | None = full_jitter,
352
+ giveup: _Predicate[BaseException] = lambda e: False,
353
+ on_success: _ContextHandler | Iterable[_ContextHandler] | None = None,
354
+ on_backoff: _ContextHandler | Iterable[_ContextHandler] | None = None,
355
+ on_giveup: _ContextHandler | Iterable[_ContextHandler] | None = None,
356
+ raise_on_giveup: bool = True,
357
+ logger: _MaybeLogger = "backoff",
358
+ backoff_log_level: int = logging.INFO,
359
+ giveup_log_level: int = logging.ERROR,
360
+ **wait_gen_kwargs: Any,
361
+ ) -> AsyncGenerator[_Attempt, None]:
362
+ """Async counterpart to `retry_context`, for use with `async for`.
363
+
364
+ async for attempt in backoff.aretry_context(ValueError, backoff.expo):
365
+ with attempt:
366
+ await do_something()
367
+
368
+ `giveup` and the handlers may be sync or async callables. See
369
+ `retry_context` for the full argument reference.
370
+ """
371
+ logger = _prepare_logger(logger)
372
+ on_success = _config_handlers(on_success)
373
+ on_backoff = _config_handlers(
374
+ on_backoff,
375
+ default_handler=_log_backoff_context,
376
+ logger=logger,
377
+ log_level=backoff_log_level,
378
+ )
379
+ on_giveup = _config_handlers(
380
+ on_giveup,
381
+ default_handler=_log_giveup_context,
382
+ logger=logger,
383
+ log_level=giveup_log_level,
384
+ )
385
+
386
+ return _async.aretry_context(
387
+ exception,
388
+ wait_gen,
389
+ max_tries=max_tries,
390
+ max_time=max_time,
391
+ jitter=jitter,
392
+ giveup=giveup,
393
+ on_success=on_success,
394
+ on_backoff=on_backoff,
395
+ on_giveup=on_giveup,
396
+ raise_on_giveup=raise_on_giveup,
397
+ wait_gen_kwargs=wait_gen_kwargs,
398
+ )
backoff/_jitter.py CHANGED
@@ -1,5 +1,3 @@
1
- # coding:utf-8
2
-
3
1
  import random
4
2
 
5
3