python-backoff 2.3.1__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,8 +1,29 @@
1
+ from __future__ import annotations
2
+
1
3
  import functools
2
4
  import logging
3
- import sys
5
+ import time
4
6
  import traceback
5
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
6
27
 
7
28
 
8
29
  # Use module-specific logger with a default null handler.
@@ -10,26 +31,38 @@ _logger = logging.getLogger("backoff")
10
31
  _logger.addHandler(logging.NullHandler()) # pragma: no cover
11
32
  _logger.setLevel(logging.INFO)
12
33
 
34
+ T = TypeVar("T")
35
+ _HandlerT = TypeVar("_HandlerT")
36
+
13
37
 
14
38
  # Evaluate arg that can be either a fixed value or a callable.
15
- def _maybe_call(f, *args, **kwargs):
39
+ def _maybe_call(f: _MaybeCallable[T] | None, *args: Any, **kwargs: Any) -> T | None:
16
40
  if callable(f):
17
41
  try:
18
- return f(*args, **kwargs)
42
+ return f(*args, **kwargs) # ty:ignore[call-top-callable, invalid-return-type]
19
43
  except TypeError:
20
- return f
44
+ return f # type: ignore[return-value] # ty:ignore[invalid-return-type]
21
45
  else:
22
46
  return f
23
47
 
24
48
 
25
- 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]:
26
53
  kwargs = {k: _maybe_call(v) for k, v in wait_gen_kwargs.items()}
27
54
  initialized = wait_gen(**kwargs)
28
55
  initialized.send(None) # Initialize with an empty send
29
56
  return initialized
30
57
 
31
58
 
32
- 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:
33
66
  value = wait.send(send_value)
34
67
  try:
35
68
  seconds = jitter(value) if jitter is not None else value
@@ -42,7 +75,7 @@ def _next_wait(wait, send_value, jitter, elapsed, max_time):
42
75
  stacklevel=2,
43
76
  )
44
77
 
45
- seconds = value + jitter()
78
+ seconds = value + jitter() # type: ignore[call-arg, misc] # ty:ignore[missing-argument, call-non-callable]
46
79
 
47
80
  # don't sleep longer than remaining allotted max_time
48
81
  if max_time is not None:
@@ -51,7 +84,103 @@ def _next_wait(wait, send_value, jitter, elapsed, max_time):
51
84
  return seconds
52
85
 
53
86
 
54
- 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:
55
184
  if isinstance(logger, str):
56
185
  logger = logging.getLogger(logger)
57
186
  return logger
@@ -60,22 +189,23 @@ def _prepare_logger(logger):
60
189
  # Configure handler list with user specified handler and optionally
61
190
  # with a default handler bound to the specified logger.
62
191
  def _config_handlers(
63
- user_handlers,
192
+ user_handlers: _HandlerT | Iterable[_HandlerT] | None,
64
193
  *,
65
- default_handler=None,
66
- logger=None,
67
- log_level=None,
68
- ):
69
- handlers = []
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] = []
70
199
  if logger is not None:
71
200
  assert log_level is not None, "Log level is not specified"
201
+ assert default_handler is not None, "Default handler is not specified"
72
202
  # bind the specified logger to the default log handler
73
203
  log_handler = functools.partial(
74
204
  default_handler,
75
205
  logger=logger,
76
206
  log_level=log_level,
77
207
  )
78
- handlers.append(log_handler)
208
+ handlers.append(log_handler) # type: ignore[arg-type] # ty:ignore[invalid-argument-type]
79
209
 
80
210
  if user_handlers is None:
81
211
  return handlers
@@ -84,22 +214,28 @@ def _config_handlers(
84
214
  # or a single handler. either way append them to the list.
85
215
  if hasattr(user_handlers, "__iter__"):
86
216
  # add all handlers in the iterable
87
- handlers += list(user_handlers)
217
+ # pyrefly: ignore [bad-argument-type]
218
+ handlers += list(user_handlers) # ty:ignore[invalid-argument-type]
88
219
  else:
89
220
  # append a single handler
221
+ # pyrefly: ignore [bad-argument-type]
90
222
  handlers.append(user_handlers)
91
223
 
92
224
  return handlers
93
225
 
94
226
 
95
227
  # Default backoff handler
96
- 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:
97
233
  msg = "Backing off %s(...) for %.1fs (%s)"
98
- log_args = [details["target"].__name__, details["wait"]]
234
+ log_args = [details["target"].__name__, details["wait"]] # ty:ignore[unresolved-attribute]
99
235
 
100
- exc_typ, exc, _ = sys.exc_info()
236
+ exc = details.get("exception")
101
237
  if exc is not None:
102
- exc_fmt = traceback.format_exception_only(exc_typ, exc)[-1]
238
+ exc_fmt = traceback.format_exception_only(type(exc), exc)[-1]
103
239
  log_args.append(exc_fmt.rstrip("\n"))
104
240
  else:
105
241
  log_args.append(details["value"])
@@ -107,15 +243,49 @@ def _log_backoff(details, logger, log_level):
107
243
 
108
244
 
109
245
  # Default giveup handler
110
- 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:
111
251
  msg = "Giving up %s(...) after %d tries (%s)"
112
- log_args = [details["target"].__name__, details["tries"]]
252
+ log_args = [details["target"].__name__, details["tries"]] # ty:ignore[unresolved-attribute]
113
253
 
114
- exc_typ, exc, _ = sys.exc_info()
254
+ exc = details.get("exception")
115
255
  if exc is not None:
116
- exc_fmt = traceback.format_exception_only(exc_typ, exc)[-1]
256
+ exc_fmt = traceback.format_exception_only(type(exc), exc)[-1]
117
257
  log_args.append(exc_fmt.rstrip("\n"))
118
258
  else:
119
259
  log_args.append(details["value"])
120
260
 
121
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,43 +1,62 @@
1
+ from __future__ import annotations
2
+
1
3
  import inspect
2
4
  import logging
3
5
  import operator
4
- from typing import Any, Callable, Iterable, Optional, Type, Union
6
+ from typing import TYPE_CHECKING, Any, Callable, TypeVar
5
7
 
8
+ from backoff import _async, _sync
6
9
  from backoff._common import (
7
- _prepare_logger,
8
10
  _config_handlers,
9
11
  _log_backoff,
12
+ _log_backoff_context,
10
13
  _log_giveup,
14
+ _log_giveup_context,
15
+ _prepare_logger,
11
16
  )
12
17
  from backoff._jitter import full_jitter
13
- from backoff import _async, _sync
14
- from backoff._typing import (
15
- _CallableT,
16
- _Handler,
17
- _Jitterer,
18
- _MaybeCallable,
19
- _MaybeLogger,
20
- _MaybeSequence,
21
- _Predicate,
22
- _WaitGenerator,
23
- )
18
+ from backoff._wait_gen import expo
19
+
20
+ if TYPE_CHECKING:
21
+ import sys
22
+ from collections.abc import AsyncGenerator, Generator, Iterable
23
+
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")
24
43
 
25
44
 
26
45
  def on_predicate(
27
46
  wait_gen: _WaitGenerator,
28
47
  predicate: _Predicate[Any] = operator.not_,
29
48
  *,
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,
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,
36
55
  logger: _MaybeLogger = "backoff",
37
56
  backoff_log_level: int = logging.INFO,
38
57
  giveup_log_level: int = logging.ERROR,
39
58
  **wait_gen_kwargs: Any,
40
- ) -> Callable[[_CallableT], _CallableT]:
59
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]:
41
60
  """Returns decorator for backoff and retry triggered by predicate.
42
61
 
43
62
  Args:
@@ -82,7 +101,7 @@ def on_predicate(
82
101
  This is useful for runtime configuration.
83
102
  """
84
103
 
85
- def decorate(target):
104
+ def decorate(target: Callable[P, T]) -> Callable[P, T]:
86
105
  nonlocal logger, on_success, on_backoff, on_giveup
87
106
 
88
107
  logger = _prepare_logger(logger)
@@ -124,21 +143,21 @@ def on_predicate(
124
143
 
125
144
  def on_exception(
126
145
  wait_gen: _WaitGenerator,
127
- exception: _MaybeSequence[Type[Exception]],
146
+ exception: _MaybeTuple[type[Exception]],
128
147
  *,
129
- max_tries: Optional[_MaybeCallable[int]] = None,
130
- max_time: Optional[_MaybeCallable[float]] = None,
131
- jitter: Union[_Jitterer, None] = full_jitter,
148
+ max_tries: _MaybeCallable[int] | None = None,
149
+ max_time: _MaybeCallable[float] | None = None,
150
+ jitter: _Jitterer | None = full_jitter,
132
151
  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,
152
+ on_success: _Handler | Iterable[_Handler] | None = None,
153
+ on_backoff: _Handler | Iterable[_Handler] | None = None,
154
+ on_giveup: _Handler | Iterable[_Handler] | None = None,
136
155
  raise_on_giveup: bool = True,
137
156
  logger: _MaybeLogger = "backoff",
138
157
  backoff_log_level: int = logging.INFO,
139
158
  giveup_log_level: int = logging.ERROR,
140
159
  **wait_gen_kwargs: Any,
141
- ) -> Callable[[_CallableT], _CallableT]:
160
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]:
142
161
  """Returns decorator for backoff and retry triggered by exception.
143
162
 
144
163
  Args:
@@ -185,7 +204,7 @@ def on_exception(
185
204
  This is useful for runtime configuration.
186
205
  """
187
206
 
188
- def decorate(target):
207
+ def decorate(target: Callable[P, T]) -> Callable[P, T]:
189
208
  nonlocal logger, on_success, on_backoff, on_giveup
190
209
 
191
210
  logger = _prepare_logger(logger)
@@ -225,3 +244,155 @@ def on_exception(
225
244
 
226
245
  # Return a function which decorates a target with a retry loop.
227
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
+ )