tenacity 8.2.3__py3-none-any.whl → 8.3.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.
tenacity/__init__.py CHANGED
@@ -15,8 +15,7 @@
15
15
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
-
19
-
18
+ import dataclasses
20
19
  import functools
21
20
  import sys
22
21
  import threading
@@ -50,6 +49,7 @@ from .nap import sleep_using_event # noqa
50
49
  # Import all built-in stop strategies for easier usage.
51
50
  from .stop import stop_after_attempt # noqa
52
51
  from .stop import stop_after_delay # noqa
52
+ from .stop import stop_before_delay # noqa
53
53
  from .stop import stop_all # noqa
54
54
  from .stop import stop_any # noqa
55
55
  from .stop import stop_never # noqa
@@ -96,6 +96,29 @@ WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
96
96
  WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Any])
97
97
 
98
98
 
99
+ dataclass_kwargs = {}
100
+ if sys.version_info >= (3, 10):
101
+ dataclass_kwargs.update({"slots": True})
102
+
103
+
104
+ @dataclasses.dataclass(**dataclass_kwargs)
105
+ class IterState:
106
+ actions: t.List[t.Callable[["RetryCallState"], t.Any]] = dataclasses.field(
107
+ default_factory=list
108
+ )
109
+ retry_run_result: bool = False
110
+ delay_since_first_attempt: int = 0
111
+ stop_run_result: bool = False
112
+ is_explicit_retry: bool = False
113
+
114
+ def reset(self) -> None:
115
+ self.actions = []
116
+ self.retry_run_result = False
117
+ self.delay_since_first_attempt = 0
118
+ self.stop_run_result = False
119
+ self.is_explicit_retry = False
120
+
121
+
99
122
  class TryAgain(Exception):
100
123
  """Always retry the executed function when raised."""
101
124
 
@@ -124,7 +147,9 @@ class BaseAction:
124
147
  NAME: t.Optional[str] = None
125
148
 
126
149
  def __repr__(self) -> str:
127
- state_str = ", ".join(f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS)
150
+ state_str = ", ".join(
151
+ f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS
152
+ )
128
153
  return f"{self.__class__.__name__}({state_str})"
129
154
 
130
155
  def __str__(self) -> str:
@@ -220,10 +245,14 @@ class BaseRetrying(ABC):
220
245
  retry: t.Union[retry_base, object] = _unset,
221
246
  before: t.Union[t.Callable[["RetryCallState"], None], object] = _unset,
222
247
  after: t.Union[t.Callable[["RetryCallState"], None], object] = _unset,
223
- before_sleep: t.Union[t.Optional[t.Callable[["RetryCallState"], None]], object] = _unset,
248
+ before_sleep: t.Union[
249
+ t.Optional[t.Callable[["RetryCallState"], None]], object
250
+ ] = _unset,
224
251
  reraise: t.Union[bool, object] = _unset,
225
252
  retry_error_cls: t.Union[t.Type[RetryError], object] = _unset,
226
- retry_error_callback: t.Union[t.Optional[t.Callable[["RetryCallState"], t.Any]], object] = _unset,
253
+ retry_error_callback: t.Union[
254
+ t.Optional[t.Callable[["RetryCallState"], t.Any]], object
255
+ ] = _unset,
227
256
  ) -> "BaseRetrying":
228
257
  """Copy this object with some parameters changed if needed."""
229
258
  return self.__class__(
@@ -236,7 +265,9 @@ class BaseRetrying(ABC):
236
265
  before_sleep=_first_set(before_sleep, self.before_sleep),
237
266
  reraise=_first_set(reraise, self.reraise),
238
267
  retry_error_cls=_first_set(retry_error_cls, self.retry_error_cls),
239
- retry_error_callback=_first_set(retry_error_callback, self.retry_error_callback),
268
+ retry_error_callback=_first_set(
269
+ retry_error_callback, self.retry_error_callback
270
+ ),
240
271
  )
241
272
 
242
273
  def __repr__(self) -> str:
@@ -278,13 +309,23 @@ class BaseRetrying(ABC):
278
309
  self._local.statistics = t.cast(t.Dict[str, t.Any], {})
279
310
  return self._local.statistics
280
311
 
312
+ @property
313
+ def iter_state(self) -> IterState:
314
+ try:
315
+ return self._local.iter_state # type: ignore[no-any-return]
316
+ except AttributeError:
317
+ self._local.iter_state = IterState()
318
+ return self._local.iter_state
319
+
281
320
  def wraps(self, f: WrappedFn) -> WrappedFn:
282
321
  """Wrap a function for retrying.
283
322
 
284
323
  :param f: A function to wraps for retrying.
285
324
  """
286
325
 
287
- @functools.wraps(f)
326
+ @functools.wraps(
327
+ f, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__")
328
+ )
288
329
  def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any:
289
330
  return self(f, *args, **kw)
290
331
 
@@ -302,42 +343,89 @@ class BaseRetrying(ABC):
302
343
  self.statistics["attempt_number"] = 1
303
344
  self.statistics["idle_for"] = 0
304
345
 
346
+ def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None:
347
+ self.iter_state.actions.append(fn)
348
+
349
+ def _run_retry(self, retry_state: "RetryCallState") -> None:
350
+ self.iter_state.retry_run_result = self.retry(retry_state)
351
+
352
+ def _run_wait(self, retry_state: "RetryCallState") -> None:
353
+ if self.wait:
354
+ sleep = self.wait(retry_state)
355
+ else:
356
+ sleep = 0.0
357
+
358
+ retry_state.upcoming_sleep = sleep
359
+
360
+ def _run_stop(self, retry_state: "RetryCallState") -> None:
361
+ self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start
362
+ self.iter_state.stop_run_result = self.stop(retry_state)
363
+
305
364
  def iter(self, retry_state: "RetryCallState") -> t.Union[DoAttempt, DoSleep, t.Any]: # noqa
365
+ self._begin_iter(retry_state)
366
+ result = None
367
+ for action in self.iter_state.actions:
368
+ result = action(retry_state)
369
+ return result
370
+
371
+ def _begin_iter(self, retry_state: "RetryCallState") -> None: # noqa
372
+ self.iter_state.reset()
373
+
306
374
  fut = retry_state.outcome
307
375
  if fut is None:
308
376
  if self.before is not None:
309
- self.before(retry_state)
310
- return DoAttempt()
377
+ self._add_action_func(self.before)
378
+ self._add_action_func(lambda rs: DoAttempt())
379
+ return
380
+
381
+ self.iter_state.is_explicit_retry = fut.failed and isinstance(
382
+ fut.exception(), TryAgain
383
+ )
384
+ if not self.iter_state.is_explicit_retry:
385
+ self._add_action_func(self._run_retry)
386
+ self._add_action_func(self._post_retry_check_actions)
311
387
 
312
- is_explicit_retry = fut.failed and isinstance(fut.exception(), TryAgain)
313
- if not (is_explicit_retry or self.retry(retry_state)):
314
- return fut.result()
388
+ def _post_retry_check_actions(self, retry_state: "RetryCallState") -> None:
389
+ if not (self.iter_state.is_explicit_retry or self.iter_state.retry_run_result):
390
+ self._add_action_func(lambda rs: rs.outcome.result())
391
+ return
315
392
 
316
393
  if self.after is not None:
317
- self.after(retry_state)
394
+ self._add_action_func(self.after)
318
395
 
319
- self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start
320
- if self.stop(retry_state):
396
+ self._add_action_func(self._run_wait)
397
+ self._add_action_func(self._run_stop)
398
+ self._add_action_func(self._post_stop_check_actions)
399
+
400
+ def _post_stop_check_actions(self, retry_state: "RetryCallState") -> None:
401
+ if self.iter_state.stop_run_result:
321
402
  if self.retry_error_callback:
322
- return self.retry_error_callback(retry_state)
323
- retry_exc = self.retry_error_cls(fut)
324
- if self.reraise:
325
- raise retry_exc.reraise()
326
- raise retry_exc from fut.exception()
403
+ self._add_action_func(self.retry_error_callback)
404
+ return
327
405
 
328
- if self.wait:
329
- sleep = self.wait(retry_state)
330
- else:
331
- sleep = 0.0
332
- retry_state.next_action = RetryAction(sleep)
333
- retry_state.idle_for += sleep
334
- self.statistics["idle_for"] += sleep
335
- self.statistics["attempt_number"] += 1
406
+ def exc_check(rs: "RetryCallState") -> None:
407
+ fut = t.cast(Future, rs.outcome)
408
+ retry_exc = self.retry_error_cls(fut)
409
+ if self.reraise:
410
+ raise retry_exc.reraise()
411
+ raise retry_exc from fut.exception()
412
+
413
+ self._add_action_func(exc_check)
414
+ return
415
+
416
+ def next_action(rs: "RetryCallState") -> None:
417
+ sleep = rs.upcoming_sleep
418
+ rs.next_action = RetryAction(sleep)
419
+ rs.idle_for += sleep
420
+ self.statistics["idle_for"] += sleep
421
+ self.statistics["attempt_number"] += 1
422
+
423
+ self._add_action_func(next_action)
336
424
 
337
425
  if self.before_sleep is not None:
338
- self.before_sleep(retry_state)
426
+ self._add_action_func(self.before_sleep)
339
427
 
340
- return DoSleep(sleep)
428
+ self._add_action_func(lambda rs: DoSleep(rs.upcoming_sleep))
341
429
 
342
430
  def __iter__(self) -> t.Generator[AttemptManager, None, None]:
343
431
  self.begin()
@@ -391,7 +479,7 @@ class Retrying(BaseRetrying):
391
479
  return do # type: ignore[no-any-return]
392
480
 
393
481
 
394
- if sys.version_info[1] >= 9:
482
+ if sys.version_info >= (3, 9):
395
483
  FutureGenericT = futures.Future[t.Any]
396
484
  else:
397
485
  FutureGenericT = futures.Future
@@ -410,7 +498,9 @@ class Future(FutureGenericT):
410
498
  return self.exception() is not None
411
499
 
412
500
  @classmethod
413
- def construct(cls, attempt_number: int, value: t.Any, has_exception: bool) -> "Future":
501
+ def construct(
502
+ cls, attempt_number: int, value: t.Any, has_exception: bool
503
+ ) -> "Future":
414
504
  """Construct a new Future object."""
415
505
  fut = cls(attempt_number)
416
506
  if has_exception:
@@ -451,6 +541,8 @@ class RetryCallState:
451
541
  self.idle_for: float = 0.0
452
542
  #: Next action as decided by the retry manager
453
543
  self.next_action: t.Optional[RetryAction] = None
544
+ #: Next sleep time as decided by the retry manager.
545
+ self.upcoming_sleep: float = 0.0
454
546
 
455
547
  @property
456
548
  def seconds_since_start(self) -> t.Optional[float]:
@@ -471,7 +563,10 @@ class RetryCallState:
471
563
  self.outcome, self.outcome_timestamp = fut, ts
472
564
 
473
565
  def set_exception(
474
- self, exc_info: t.Tuple[t.Type[BaseException], BaseException, "types.TracebackType| None"]
566
+ self,
567
+ exc_info: t.Tuple[
568
+ t.Type[BaseException], BaseException, "types.TracebackType| None"
569
+ ],
475
570
  ) -> None:
476
571
  ts = time.monotonic()
477
572
  fut = Future(self.attempt_number)
@@ -493,8 +588,7 @@ class RetryCallState:
493
588
 
494
589
 
495
590
  @t.overload
496
- def retry(func: WrappedFn) -> WrappedFn:
497
- ...
591
+ def retry(func: WrappedFn) -> WrappedFn: ...
498
592
 
499
593
 
500
594
  @t.overload
@@ -509,8 +603,7 @@ def retry(
509
603
  reraise: bool = False,
510
604
  retry_error_cls: t.Type["RetryError"] = RetryError,
511
605
  retry_error_callback: t.Optional[t.Callable[["RetryCallState"], t.Any]] = None,
512
- ) -> t.Callable[[WrappedFn], WrappedFn]:
513
- ...
606
+ ) -> t.Callable[[WrappedFn], WrappedFn]: ...
514
607
 
515
608
 
516
609
  def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:
@@ -533,7 +626,11 @@ def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:
533
626
  r: "BaseRetrying"
534
627
  if iscoroutinefunction(f):
535
628
  r = AsyncRetrying(*dargs, **dkw)
536
- elif tornado and hasattr(tornado.gen, "is_coroutine_function") and tornado.gen.is_coroutine_function(f):
629
+ elif (
630
+ tornado
631
+ and hasattr(tornado.gen, "is_coroutine_function")
632
+ and tornado.gen.is_coroutine_function(f)
633
+ ):
537
634
  r = TornadoRetrying(*dargs, **dkw)
538
635
  else:
539
636
  r = Retrying(*dargs, **dkw)
@@ -568,6 +665,7 @@ __all__ = [
568
665
  "sleep_using_event",
569
666
  "stop_after_attempt",
570
667
  "stop_after_delay",
668
+ "stop_before_delay",
571
669
  "stop_all",
572
670
  "stop_any",
573
671
  "stop_never",
tenacity/_asyncio.py CHANGED
@@ -18,22 +18,33 @@
18
18
  import functools
19
19
  import sys
20
20
  import typing as t
21
- from asyncio import sleep
22
21
 
23
22
  from tenacity import AttemptManager
24
23
  from tenacity import BaseRetrying
25
24
  from tenacity import DoAttempt
26
25
  from tenacity import DoSleep
27
26
  from tenacity import RetryCallState
27
+ from tenacity import _utils
28
28
 
29
29
  WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
30
30
  WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Awaitable[t.Any]])
31
31
 
32
32
 
33
+ def asyncio_sleep(duration: float) -> t.Awaitable[None]:
34
+ # Lazy import asyncio as it's expensive (responsible for 25-50% of total import overhead).
35
+ import asyncio
36
+
37
+ return asyncio.sleep(duration)
38
+
39
+
33
40
  class AsyncRetrying(BaseRetrying):
34
41
  sleep: t.Callable[[float], t.Awaitable[t.Any]]
35
42
 
36
- def __init__(self, sleep: t.Callable[[float], t.Awaitable[t.Any]] = sleep, **kwargs: t.Any) -> None:
43
+ def __init__(
44
+ self,
45
+ sleep: t.Callable[[float], t.Awaitable[t.Any]] = asyncio_sleep,
46
+ **kwargs: t.Any,
47
+ ) -> None:
37
48
  super().__init__(**kwargs)
38
49
  self.sleep = sleep
39
50
 
@@ -44,7 +55,7 @@ class AsyncRetrying(BaseRetrying):
44
55
 
45
56
  retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)
46
57
  while True:
47
- do = self.iter(retry_state=retry_state)
58
+ do = await self.iter(retry_state=retry_state)
48
59
  if isinstance(do, DoAttempt):
49
60
  try:
50
61
  result = await fn(*args, **kwargs)
@@ -58,6 +69,47 @@ class AsyncRetrying(BaseRetrying):
58
69
  else:
59
70
  return do # type: ignore[no-any-return]
60
71
 
72
+ @classmethod
73
+ def _wrap_action_func(cls, fn: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
74
+ if _utils.is_coroutine_callable(fn):
75
+ return fn
76
+
77
+ async def inner(*args: t.Any, **kwargs: t.Any) -> t.Any:
78
+ return fn(*args, **kwargs)
79
+
80
+ return inner
81
+
82
+ def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None:
83
+ self.iter_state.actions.append(self._wrap_action_func(fn))
84
+
85
+ async def _run_retry(self, retry_state: "RetryCallState") -> None: # type: ignore[override]
86
+ self.iter_state.retry_run_result = await self._wrap_action_func(self.retry)(
87
+ retry_state
88
+ )
89
+
90
+ async def _run_wait(self, retry_state: "RetryCallState") -> None: # type: ignore[override]
91
+ if self.wait:
92
+ sleep = await self._wrap_action_func(self.wait)(retry_state)
93
+ else:
94
+ sleep = 0.0
95
+
96
+ retry_state.upcoming_sleep = sleep
97
+
98
+ async def _run_stop(self, retry_state: "RetryCallState") -> None: # type: ignore[override]
99
+ self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start
100
+ self.iter_state.stop_run_result = await self._wrap_action_func(self.stop)(
101
+ retry_state
102
+ )
103
+
104
+ async def iter(
105
+ self, retry_state: "RetryCallState"
106
+ ) -> t.Union[DoAttempt, DoSleep, t.Any]: # noqa: A003
107
+ self._begin_iter(retry_state)
108
+ result = None
109
+ for action in self.iter_state.actions:
110
+ result = await action(retry_state)
111
+ return result
112
+
61
113
  def __iter__(self) -> t.Generator[AttemptManager, None, None]:
62
114
  raise TypeError("AsyncRetrying object is not iterable")
63
115
 
@@ -68,7 +120,7 @@ class AsyncRetrying(BaseRetrying):
68
120
 
69
121
  async def __anext__(self) -> AttemptManager:
70
122
  while True:
71
- do = self.iter(retry_state=self._retry_state)
123
+ do = await self.iter(retry_state=self._retry_state)
72
124
  if do is None:
73
125
  raise StopAsyncIteration
74
126
  elif isinstance(do, DoAttempt):
@@ -83,7 +135,9 @@ class AsyncRetrying(BaseRetrying):
83
135
  fn = super().wraps(fn)
84
136
  # Ensure wrapper is recognized as a coroutine function.
85
137
 
86
- @functools.wraps(fn)
138
+ @functools.wraps(
139
+ fn, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__")
140
+ )
87
141
  async def async_wrapped(*args: t.Any, **kwargs: t.Any) -> t.Any:
88
142
  return await fn(*args, **kwargs)
89
143
 
tenacity/_utils.py CHANGED
@@ -13,7 +13,8 @@
13
13
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
-
16
+ import functools
17
+ import inspect
17
18
  import sys
18
19
  import typing
19
20
  from datetime import timedelta
@@ -73,4 +74,16 @@ time_unit_type = typing.Union[int, float, timedelta]
73
74
 
74
75
 
75
76
  def to_seconds(time_unit: time_unit_type) -> float:
76
- return float(time_unit.total_seconds() if isinstance(time_unit, timedelta) else time_unit)
77
+ return float(
78
+ time_unit.total_seconds() if isinstance(time_unit, timedelta) else time_unit
79
+ )
80
+
81
+
82
+ def is_coroutine_callable(call: typing.Callable[..., typing.Any]) -> bool:
83
+ if inspect.isclass(call):
84
+ return False
85
+ if inspect.iscoroutinefunction(call):
86
+ return True
87
+ partial_call = isinstance(call, functools.partial) and call.func
88
+ dunder_call = partial_call or getattr(call, "__call__", None)
89
+ return inspect.iscoroutinefunction(dunder_call)
tenacity/before.py CHANGED
@@ -28,7 +28,9 @@ def before_nothing(retry_state: "RetryCallState") -> None:
28
28
  """Before call strategy that does nothing."""
29
29
 
30
30
 
31
- def before_log(logger: "logging.Logger", log_level: int) -> typing.Callable[["RetryCallState"], None]:
31
+ def before_log(
32
+ logger: "logging.Logger", log_level: int
33
+ ) -> typing.Callable[["RetryCallState"], None]:
32
34
  """Before call strategy that logs to some logger the attempt."""
33
35
 
34
36
  def log_it(retry_state: "RetryCallState") -> None:
tenacity/before_sleep.py CHANGED
@@ -64,7 +64,8 @@ def before_sleep_log(
64
64
 
65
65
  logger.log(
66
66
  log_level,
67
- f"Retrying {fn_name} " f"in {retry_state.next_action.sleep} seconds as it {verb} {value}.",
67
+ f"Retrying {fn_name} "
68
+ f"in {retry_state.next_action.sleep} seconds as it {verb} {value}.",
68
69
  exc_info=local_exc_info,
69
70
  )
70
71
 
tenacity/retry.py CHANGED
@@ -204,7 +204,9 @@ class retry_if_exception_message(retry_if_exception):
204
204
  match: typing.Optional[str] = None,
205
205
  ) -> None:
206
206
  if message and match:
207
- raise TypeError(f"{self.__class__.__name__}() takes either 'message' or 'match', not both")
207
+ raise TypeError(
208
+ f"{self.__class__.__name__}() takes either 'message' or 'match', not both"
209
+ )
208
210
 
209
211
  # set predicate
210
212
  if message:
@@ -221,7 +223,9 @@ class retry_if_exception_message(retry_if_exception):
221
223
 
222
224
  predicate = match_fnc
223
225
  else:
224
- raise TypeError(f"{self.__class__.__name__}() missing 1 required argument 'message' or 'match'")
226
+ raise TypeError(
227
+ f"{self.__class__.__name__}() missing 1 required argument 'message' or 'match'"
228
+ )
225
229
 
226
230
  super().__init__(predicate)
227
231
 
tenacity/stop.py CHANGED
@@ -92,7 +92,14 @@ class stop_after_attempt(stop_base):
92
92
 
93
93
 
94
94
  class stop_after_delay(stop_base):
95
- """Stop when the time from the first attempt >= limit."""
95
+ """
96
+ Stop when the time from the first attempt >= limit.
97
+
98
+ Note: `max_delay` will be exceeded, so when used with a `wait`, the actual total delay will be greater
99
+ than `max_delay` by some of the final sleep period before `max_delay` is exceeded.
100
+
101
+ If you need stricter timing with waits, consider `stop_before_delay` instead.
102
+ """
96
103
 
97
104
  def __init__(self, max_delay: _utils.time_unit_type) -> None:
98
105
  self.max_delay = _utils.to_seconds(max_delay)
@@ -101,3 +108,23 @@ class stop_after_delay(stop_base):
101
108
  if retry_state.seconds_since_start is None:
102
109
  raise RuntimeError("__call__() called but seconds_since_start is not set")
103
110
  return retry_state.seconds_since_start >= self.max_delay
111
+
112
+
113
+ class stop_before_delay(stop_base):
114
+ """
115
+ Stop right before the next attempt would take place after the time from the first attempt >= limit.
116
+
117
+ Most useful when you are using with a `wait` function like wait_random_exponential, but need to make
118
+ sure that the max_delay is not exceeded.
119
+ """
120
+
121
+ def __init__(self, max_delay: _utils.time_unit_type) -> None:
122
+ self.max_delay = _utils.to_seconds(max_delay)
123
+
124
+ def __call__(self, retry_state: "RetryCallState") -> bool:
125
+ if retry_state.seconds_since_start is None:
126
+ raise RuntimeError("__call__() called but seconds_since_start is not set")
127
+ return (
128
+ retry_state.seconds_since_start + retry_state.upcoming_sleep
129
+ >= self.max_delay
130
+ )
tenacity/tornadoweb.py CHANGED
@@ -29,7 +29,11 @@ _RetValT = typing.TypeVar("_RetValT")
29
29
 
30
30
 
31
31
  class TornadoRetrying(BaseRetrying):
32
- def __init__(self, sleep: "typing.Callable[[float], Future[None]]" = gen.sleep, **kwargs: typing.Any) -> None:
32
+ def __init__(
33
+ self,
34
+ sleep: "typing.Callable[[float], Future[None]]" = gen.sleep,
35
+ **kwargs: typing.Any,
36
+ ) -> None:
33
37
  super().__init__(**kwargs)
34
38
  self.sleep = sleep
35
39
 
tenacity/wait.py CHANGED
@@ -41,7 +41,9 @@ class wait_base(abc.ABC):
41
41
  return self.__add__(other)
42
42
 
43
43
 
44
- WaitBaseT = typing.Union[wait_base, typing.Callable[["RetryCallState"], typing.Union[float, int]]]
44
+ WaitBaseT = typing.Union[
45
+ wait_base, typing.Callable[["RetryCallState"], typing.Union[float, int]]
46
+ ]
45
47
 
46
48
 
47
49
  class wait_fixed(wait_base):
@@ -64,12 +66,16 @@ class wait_none(wait_fixed):
64
66
  class wait_random(wait_base):
65
67
  """Wait strategy that waits a random amount of time between min/max."""
66
68
 
67
- def __init__(self, min: _utils.time_unit_type = 0, max: _utils.time_unit_type = 1) -> None: # noqa
69
+ def __init__(
70
+ self, min: _utils.time_unit_type = 0, max: _utils.time_unit_type = 1
71
+ ) -> None: # noqa
68
72
  self.wait_random_min = _utils.to_seconds(min)
69
73
  self.wait_random_max = _utils.to_seconds(max)
70
74
 
71
75
  def __call__(self, retry_state: "RetryCallState") -> float:
72
- return self.wait_random_min + (random.random() * (self.wait_random_max - self.wait_random_min))
76
+ return self.wait_random_min + (
77
+ random.random() * (self.wait_random_max - self.wait_random_min)
78
+ )
73
79
 
74
80
 
75
81
  class wait_combine(wait_base):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tenacity
3
- Version: 8.2.3
3
+ Version: 8.3.0
4
4
  Summary: Retry code until it succeeds
5
5
  Home-page: https://github.com/jd/tenacity
6
6
  Author: Julien Danjou
@@ -11,17 +11,20 @@ Classifier: License :: OSI Approved :: Apache Software License
11
11
  Classifier: Programming Language :: Python
12
12
  Classifier: Programming Language :: Python :: 3
13
13
  Classifier: Programming Language :: Python :: 3 :: Only
14
- Classifier: Programming Language :: Python :: 3.7
15
14
  Classifier: Programming Language :: Python :: 3.8
16
15
  Classifier: Programming Language :: Python :: 3.9
17
16
  Classifier: Programming Language :: Python :: 3.10
18
17
  Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Topic :: Utilities
20
- Requires-Python: >=3.7
20
+ Requires-Python: >=3.8
21
21
  License-File: LICENSE
22
22
  Provides-Extra: doc
23
23
  Requires-Dist: reno ; extra == 'doc'
24
24
  Requires-Dist: sphinx ; extra == 'doc'
25
- Requires-Dist: tornado >=4.5 ; extra == 'doc'
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest ; extra == 'test'
27
+ Requires-Dist: tornado >=4.5 ; extra == 'test'
28
+ Requires-Dist: typeguard ; extra == 'test'
26
29
 
27
30
  Tenacity is a general-purpose retrying library to simplify the task of adding retry behavior to just about anything.
@@ -0,0 +1,17 @@
1
+ tenacity/__init__.py,sha256=pXkuWY13-Ual7lJs3t2GfFBaFY8Epq6ipLxA5TA4c04,23415
2
+ tenacity/_asyncio.py,sha256=AcnW-qYS1Lf9d7P_Y9FMd7hyh6bJlQU1C073sIbZ6U4,5369
3
+ tenacity/_utils.py,sha256=GGNfSHJRrfph7kPySn-2xnY_dVUopdl6LUZdXRPuGVg,2601
4
+ tenacity/after.py,sha256=NR4rGyslG7xF1hDJZb2Wf8wVApafX0HZwz2nFVLvaqE,1658
5
+ tenacity/before.py,sha256=7zDTpZ3b6rkY9sOdS-qbpjBgSjVr3xBqcIqdYAh9ZKM,1544
6
+ tenacity/before_sleep.py,sha256=upKssiY5poOO3Bv0amADZA-a5CrY5tnb4_97s8_88SM,2360
7
+ tenacity/nap.py,sha256=fRWvnz1aIzbIq9Ap3gAkAZgDH6oo5zxMrU6ZOVByq0I,1383
8
+ tenacity/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ tenacity/retry.py,sha256=_Yb081RabODdc6FbI2Ipg8Cno4SpHKGao0i2jDvXPJQ,8794
10
+ tenacity/stop.py,sha256=wQuwGfCLw8OH1C3x0G9lH9xtJCyhgviePQ40HRAUg54,4113
11
+ tenacity/tornadoweb.py,sha256=vS1ONfPYoGzPx1asQaVbfoo6D9tPIzSysJipm61Yqw8,2125
12
+ tenacity/wait.py,sha256=Q9XoZCtFra53aQOyfABpvRDuUeB-NpUUXImHsUiRQI0,8042
13
+ tenacity-8.3.0.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
14
+ tenacity-8.3.0.dist-info/METADATA,sha256=EkN7WPJ0qwEErJNURA8THEGpBSKbYrpT5SEZMQkH7yk,1155
15
+ tenacity-8.3.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
16
+ tenacity-8.3.0.dist-info/top_level.txt,sha256=Zf8AOZMN7hr1EEcUo9U5KzXsM4TOC1pBZ22D8913JYs,9
17
+ tenacity-8.3.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.1)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,17 +0,0 @@
1
- tenacity/__init__.py,sha256=gToezN7ZiWd_GjOzGmIgjOv_H7r-D7XG0HpxAaJjxUw,20214
2
- tenacity/_asyncio.py,sha256=VLYBFVbYGPPegt-DstLBvpJ7b5QBGjzZ2RT_7z4HetE,3491
3
- tenacity/_utils.py,sha256=ubs6a7sxj3JDNRKWCyCU2j5r1CB7rgyONgZzYZq6D_4,2179
4
- tenacity/after.py,sha256=NR4rGyslG7xF1hDJZb2Wf8wVApafX0HZwz2nFVLvaqE,1658
5
- tenacity/before.py,sha256=Oqrd_9zVSuhd-OVN5WJeSq7t0PwT13lF2UPFxtLS_uM,1538
6
- tenacity/before_sleep.py,sha256=nUotitueDNDDbxM74rXMIEngcHUze1JpWXASgBXyK8A,2348
7
- tenacity/nap.py,sha256=fRWvnz1aIzbIq9Ap3gAkAZgDH6oo5zxMrU6ZOVByq0I,1383
8
- tenacity/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- tenacity/retry.py,sha256=r8MbifrSsqhpdnv8r4knkQINdlJUpieqna_8MnHtm68,8734
10
- tenacity/stop.py,sha256=Ecxg66eJwa0dhuJfLaH2g7jKd7f3KMrPRjz24bX9ZMY,3062
11
- tenacity/tornadoweb.py,sha256=arKDIDu61nQDj5vIbQoR3Uo-m8vGOjd5ypGdOHZcTVk,2094
12
- tenacity/wait.py,sha256=KlQW4UQBOdQjDQWa5aqlO67Ou73y72N_i85CWE1BOkQ,8000
13
- tenacity-8.2.3.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
14
- tenacity-8.2.3.dist-info/METADATA,sha256=g2xkakn1Zzo_4cTLL2dLSxdQ-xqBr3koN7l9toDVQLk,1049
15
- tenacity-8.2.3.dist-info/WHEEL,sha256=5sUXSg9e4bi7lTLOHcm6QEYwO5TIF1TNbTSVFVjcJcc,92
16
- tenacity-8.2.3.dist-info/top_level.txt,sha256=Zf8AOZMN7hr1EEcUo9U5KzXsM4TOC1pBZ22D8913JYs,9
17
- tenacity-8.2.3.dist-info/RECORD,,