tenacity 8.2.3__py3-none-any.whl → 8.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.
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
@@ -25,7 +24,8 @@ import typing as t
25
24
  import warnings
26
25
  from abc import ABC, abstractmethod
27
26
  from concurrent import futures
28
- from inspect import iscoroutinefunction
27
+
28
+ from . import _utils
29
29
 
30
30
  # Import all built-in retry strategies for easier usage.
31
31
  from .retry import retry_base # noqa
@@ -50,6 +50,7 @@ from .nap import sleep_using_event # noqa
50
50
  # Import all built-in stop strategies for easier usage.
51
51
  from .stop import stop_after_attempt # noqa
52
52
  from .stop import stop_after_delay # noqa
53
+ from .stop import stop_before_delay # noqa
53
54
  from .stop import stop_all # noqa
54
55
  from .stop import stop_any # noqa
55
56
  from .stop import stop_never # noqa
@@ -87,6 +88,7 @@ except ImportError:
87
88
  if t.TYPE_CHECKING:
88
89
  import types
89
90
 
91
+ from . import asyncio as tasyncio
90
92
  from .retry import RetryBaseT
91
93
  from .stop import StopBaseT
92
94
  from .wait import WaitBaseT
@@ -96,6 +98,29 @@ WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
96
98
  WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Any])
97
99
 
98
100
 
101
+ dataclass_kwargs = {}
102
+ if sys.version_info >= (3, 10):
103
+ dataclass_kwargs.update({"slots": True})
104
+
105
+
106
+ @dataclasses.dataclass(**dataclass_kwargs)
107
+ class IterState:
108
+ actions: t.List[t.Callable[["RetryCallState"], t.Any]] = dataclasses.field(
109
+ default_factory=list
110
+ )
111
+ retry_run_result: bool = False
112
+ delay_since_first_attempt: int = 0
113
+ stop_run_result: bool = False
114
+ is_explicit_retry: bool = False
115
+
116
+ def reset(self) -> None:
117
+ self.actions = []
118
+ self.retry_run_result = False
119
+ self.delay_since_first_attempt = 0
120
+ self.stop_run_result = False
121
+ self.is_explicit_retry = False
122
+
123
+
99
124
  class TryAgain(Exception):
100
125
  """Always retry the executed function when raised."""
101
126
 
@@ -124,7 +149,9 @@ class BaseAction:
124
149
  NAME: t.Optional[str] = None
125
150
 
126
151
  def __repr__(self) -> str:
127
- state_str = ", ".join(f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS)
152
+ state_str = ", ".join(
153
+ f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS
154
+ )
128
155
  return f"{self.__class__.__name__}({state_str})"
129
156
 
130
157
  def __str__(self) -> str:
@@ -220,10 +247,14 @@ class BaseRetrying(ABC):
220
247
  retry: t.Union[retry_base, object] = _unset,
221
248
  before: t.Union[t.Callable[["RetryCallState"], None], object] = _unset,
222
249
  after: t.Union[t.Callable[["RetryCallState"], None], object] = _unset,
223
- before_sleep: t.Union[t.Optional[t.Callable[["RetryCallState"], None]], object] = _unset,
250
+ before_sleep: t.Union[
251
+ t.Optional[t.Callable[["RetryCallState"], None]], object
252
+ ] = _unset,
224
253
  reraise: t.Union[bool, object] = _unset,
225
254
  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,
255
+ retry_error_callback: t.Union[
256
+ t.Optional[t.Callable[["RetryCallState"], t.Any]], object
257
+ ] = _unset,
227
258
  ) -> "BaseRetrying":
228
259
  """Copy this object with some parameters changed if needed."""
229
260
  return self.__class__(
@@ -236,7 +267,9 @@ class BaseRetrying(ABC):
236
267
  before_sleep=_first_set(before_sleep, self.before_sleep),
237
268
  reraise=_first_set(reraise, self.reraise),
238
269
  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),
270
+ retry_error_callback=_first_set(
271
+ retry_error_callback, self.retry_error_callback
272
+ ),
240
273
  )
241
274
 
242
275
  def __repr__(self) -> str:
@@ -278,13 +311,23 @@ class BaseRetrying(ABC):
278
311
  self._local.statistics = t.cast(t.Dict[str, t.Any], {})
279
312
  return self._local.statistics
280
313
 
314
+ @property
315
+ def iter_state(self) -> IterState:
316
+ try:
317
+ return self._local.iter_state # type: ignore[no-any-return]
318
+ except AttributeError:
319
+ self._local.iter_state = IterState()
320
+ return self._local.iter_state
321
+
281
322
  def wraps(self, f: WrappedFn) -> WrappedFn:
282
323
  """Wrap a function for retrying.
283
324
 
284
325
  :param f: A function to wraps for retrying.
285
326
  """
286
327
 
287
- @functools.wraps(f)
328
+ @functools.wraps(
329
+ f, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__")
330
+ )
288
331
  def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any:
289
332
  return self(f, *args, **kw)
290
333
 
@@ -302,42 +345,89 @@ class BaseRetrying(ABC):
302
345
  self.statistics["attempt_number"] = 1
303
346
  self.statistics["idle_for"] = 0
304
347
 
348
+ def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None:
349
+ self.iter_state.actions.append(fn)
350
+
351
+ def _run_retry(self, retry_state: "RetryCallState") -> None:
352
+ self.iter_state.retry_run_result = self.retry(retry_state)
353
+
354
+ def _run_wait(self, retry_state: "RetryCallState") -> None:
355
+ if self.wait:
356
+ sleep = self.wait(retry_state)
357
+ else:
358
+ sleep = 0.0
359
+
360
+ retry_state.upcoming_sleep = sleep
361
+
362
+ def _run_stop(self, retry_state: "RetryCallState") -> None:
363
+ self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start
364
+ self.iter_state.stop_run_result = self.stop(retry_state)
365
+
305
366
  def iter(self, retry_state: "RetryCallState") -> t.Union[DoAttempt, DoSleep, t.Any]: # noqa
367
+ self._begin_iter(retry_state)
368
+ result = None
369
+ for action in self.iter_state.actions:
370
+ result = action(retry_state)
371
+ return result
372
+
373
+ def _begin_iter(self, retry_state: "RetryCallState") -> None: # noqa
374
+ self.iter_state.reset()
375
+
306
376
  fut = retry_state.outcome
307
377
  if fut is None:
308
378
  if self.before is not None:
309
- self.before(retry_state)
310
- return DoAttempt()
379
+ self._add_action_func(self.before)
380
+ self._add_action_func(lambda rs: DoAttempt())
381
+ return
311
382
 
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()
383
+ self.iter_state.is_explicit_retry = fut.failed and isinstance(
384
+ fut.exception(), TryAgain
385
+ )
386
+ if not self.iter_state.is_explicit_retry:
387
+ self._add_action_func(self._run_retry)
388
+ self._add_action_func(self._post_retry_check_actions)
389
+
390
+ def _post_retry_check_actions(self, retry_state: "RetryCallState") -> None:
391
+ if not (self.iter_state.is_explicit_retry or self.iter_state.retry_run_result):
392
+ self._add_action_func(lambda rs: rs.outcome.result())
393
+ return
315
394
 
316
395
  if self.after is not None:
317
- self.after(retry_state)
396
+ self._add_action_func(self.after)
318
397
 
319
- self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start
320
- if self.stop(retry_state):
398
+ self._add_action_func(self._run_wait)
399
+ self._add_action_func(self._run_stop)
400
+ self._add_action_func(self._post_stop_check_actions)
401
+
402
+ def _post_stop_check_actions(self, retry_state: "RetryCallState") -> None:
403
+ if self.iter_state.stop_run_result:
321
404
  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()
405
+ self._add_action_func(self.retry_error_callback)
406
+ return
327
407
 
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
408
+ def exc_check(rs: "RetryCallState") -> None:
409
+ fut = t.cast(Future, rs.outcome)
410
+ retry_exc = self.retry_error_cls(fut)
411
+ if self.reraise:
412
+ raise retry_exc.reraise()
413
+ raise retry_exc from fut.exception()
414
+
415
+ self._add_action_func(exc_check)
416
+ return
417
+
418
+ def next_action(rs: "RetryCallState") -> None:
419
+ sleep = rs.upcoming_sleep
420
+ rs.next_action = RetryAction(sleep)
421
+ rs.idle_for += sleep
422
+ self.statistics["idle_for"] += sleep
423
+ self.statistics["attempt_number"] += 1
424
+
425
+ self._add_action_func(next_action)
336
426
 
337
427
  if self.before_sleep is not None:
338
- self.before_sleep(retry_state)
428
+ self._add_action_func(self.before_sleep)
339
429
 
340
- return DoSleep(sleep)
430
+ self._add_action_func(lambda rs: DoSleep(rs.upcoming_sleep))
341
431
 
342
432
  def __iter__(self) -> t.Generator[AttemptManager, None, None]:
343
433
  self.begin()
@@ -391,7 +481,7 @@ class Retrying(BaseRetrying):
391
481
  return do # type: ignore[no-any-return]
392
482
 
393
483
 
394
- if sys.version_info[1] >= 9:
484
+ if sys.version_info >= (3, 9):
395
485
  FutureGenericT = futures.Future[t.Any]
396
486
  else:
397
487
  FutureGenericT = futures.Future
@@ -410,7 +500,9 @@ class Future(FutureGenericT):
410
500
  return self.exception() is not None
411
501
 
412
502
  @classmethod
413
- def construct(cls, attempt_number: int, value: t.Any, has_exception: bool) -> "Future":
503
+ def construct(
504
+ cls, attempt_number: int, value: t.Any, has_exception: bool
505
+ ) -> "Future":
414
506
  """Construct a new Future object."""
415
507
  fut = cls(attempt_number)
416
508
  if has_exception:
@@ -451,6 +543,8 @@ class RetryCallState:
451
543
  self.idle_for: float = 0.0
452
544
  #: Next action as decided by the retry manager
453
545
  self.next_action: t.Optional[RetryAction] = None
546
+ #: Next sleep time as decided by the retry manager.
547
+ self.upcoming_sleep: float = 0.0
454
548
 
455
549
  @property
456
550
  def seconds_since_start(self) -> t.Optional[float]:
@@ -471,7 +565,10 @@ class RetryCallState:
471
565
  self.outcome, self.outcome_timestamp = fut, ts
472
566
 
473
567
  def set_exception(
474
- self, exc_info: t.Tuple[t.Type[BaseException], BaseException, "types.TracebackType| None"]
568
+ self,
569
+ exc_info: t.Tuple[
570
+ t.Type[BaseException], BaseException, "types.TracebackType| None"
571
+ ],
475
572
  ) -> None:
476
573
  ts = time.monotonic()
477
574
  fut = Future(self.attempt_number)
@@ -493,24 +590,30 @@ class RetryCallState:
493
590
 
494
591
 
495
592
  @t.overload
496
- def retry(func: WrappedFn) -> WrappedFn:
497
- ...
593
+ def retry(func: WrappedFn) -> WrappedFn: ...
498
594
 
499
595
 
500
596
  @t.overload
501
597
  def retry(
502
- sleep: t.Callable[[t.Union[int, float]], t.Optional[t.Awaitable[None]]] = sleep,
598
+ sleep: t.Callable[[t.Union[int, float]], t.Union[None, t.Awaitable[None]]] = sleep,
503
599
  stop: "StopBaseT" = stop_never,
504
600
  wait: "WaitBaseT" = wait_none(),
505
- retry: "RetryBaseT" = retry_if_exception_type(),
506
- before: t.Callable[["RetryCallState"], None] = before_nothing,
507
- after: t.Callable[["RetryCallState"], None] = after_nothing,
508
- before_sleep: t.Optional[t.Callable[["RetryCallState"], None]] = None,
601
+ retry: "t.Union[RetryBaseT, tasyncio.retry.RetryBaseT]" = retry_if_exception_type(),
602
+ before: t.Callable[
603
+ ["RetryCallState"], t.Union[None, t.Awaitable[None]]
604
+ ] = before_nothing,
605
+ after: t.Callable[
606
+ ["RetryCallState"], t.Union[None, t.Awaitable[None]]
607
+ ] = after_nothing,
608
+ before_sleep: t.Optional[
609
+ t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]]
610
+ ] = None,
509
611
  reraise: bool = False,
510
612
  retry_error_cls: t.Type["RetryError"] = RetryError,
511
- retry_error_callback: t.Optional[t.Callable[["RetryCallState"], t.Any]] = None,
512
- ) -> t.Callable[[WrappedFn], WrappedFn]:
513
- ...
613
+ retry_error_callback: t.Optional[
614
+ t.Callable[["RetryCallState"], t.Union[t.Any, t.Awaitable[t.Any]]]
615
+ ] = None,
616
+ ) -> t.Callable[[WrappedFn], WrappedFn]: ...
514
617
 
515
618
 
516
619
  def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:
@@ -531,9 +634,13 @@ def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:
531
634
  f"this will probably hang indefinitely (did you mean retry={f.__class__.__name__}(...)?)"
532
635
  )
533
636
  r: "BaseRetrying"
534
- if iscoroutinefunction(f):
637
+ if _utils.is_coroutine_callable(f):
535
638
  r = AsyncRetrying(*dargs, **dkw)
536
- elif tornado and hasattr(tornado.gen, "is_coroutine_function") and tornado.gen.is_coroutine_function(f):
639
+ elif (
640
+ tornado
641
+ and hasattr(tornado.gen, "is_coroutine_function")
642
+ and tornado.gen.is_coroutine_function(f)
643
+ ):
537
644
  r = TornadoRetrying(*dargs, **dkw)
538
645
  else:
539
646
  r = Retrying(*dargs, **dkw)
@@ -543,7 +650,7 @@ def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:
543
650
  return wrap
544
651
 
545
652
 
546
- from tenacity._asyncio import AsyncRetrying # noqa:E402,I100
653
+ from tenacity.asyncio import AsyncRetrying # noqa:E402,I100
547
654
 
548
655
  if tornado:
549
656
  from tenacity.tornadoweb import TornadoRetrying
@@ -568,6 +675,7 @@ __all__ = [
568
675
  "sleep_using_event",
569
676
  "stop_after_attempt",
570
677
  "stop_after_delay",
678
+ "stop_before_delay",
571
679
  "stop_all",
572
680
  "stop_any",
573
681
  "stop_never",
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,28 @@ 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)
90
+
91
+
92
+ def wrap_to_async_func(
93
+ call: typing.Callable[..., typing.Any],
94
+ ) -> typing.Callable[..., typing.Awaitable[typing.Any]]:
95
+ if is_coroutine_callable(call):
96
+ return call
97
+
98
+ async def inner(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
99
+ return call(*args, **kwargs)
100
+
101
+ return inner
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
@@ -30,10 +30,16 @@ class retry_base(abc.ABC):
30
30
  pass
31
31
 
32
32
  def __and__(self, other: "retry_base") -> "retry_all":
33
- return retry_all(self, other)
33
+ return other.__rand__(self)
34
+
35
+ def __rand__(self, other: "retry_base") -> "retry_all":
36
+ return retry_all(other, self)
34
37
 
35
38
  def __or__(self, other: "retry_base") -> "retry_any":
36
- return retry_any(self, other)
39
+ return other.__ror__(self)
40
+
41
+ def __ror__(self, other: "retry_base") -> "retry_any":
42
+ return retry_any(other, self)
37
43
 
38
44
 
39
45
  RetryBaseT = typing.Union[retry_base, typing.Callable[["RetryCallState"], bool]]
@@ -204,7 +210,9 @@ class retry_if_exception_message(retry_if_exception):
204
210
  match: typing.Optional[str] = None,
205
211
  ) -> None:
206
212
  if message and match:
207
- raise TypeError(f"{self.__class__.__name__}() takes either 'message' or 'match', not both")
213
+ raise TypeError(
214
+ f"{self.__class__.__name__}() takes either 'message' or 'match', not both"
215
+ )
208
216
 
209
217
  # set predicate
210
218
  if message:
@@ -221,7 +229,9 @@ class retry_if_exception_message(retry_if_exception):
221
229
 
222
230
  predicate = match_fnc
223
231
  else:
224
- raise TypeError(f"{self.__class__.__name__}() missing 1 required argument 'message' or 'match'")
232
+ raise TypeError(
233
+ f"{self.__class__.__name__}() missing 1 required argument 'message' or 'match'"
234
+ )
225
235
 
226
236
  super().__init__(predicate)
227
237
 
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.4.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,16 @@
1
+ tenacity/__init__.py,sha256=dT216tDrncc6QKV6_1AfuhzmCMfUzM_hIwW34ivIRsQ,23651
2
+ tenacity/_utils.py,sha256=5AwPoFrGOIfPkCtqeJdFBi7KKQNcJXJ3erbtGOXtn6w,2916
3
+ tenacity/after.py,sha256=NR4rGyslG7xF1hDJZb2Wf8wVApafX0HZwz2nFVLvaqE,1658
4
+ tenacity/before.py,sha256=7zDTpZ3b6rkY9sOdS-qbpjBgSjVr3xBqcIqdYAh9ZKM,1544
5
+ tenacity/before_sleep.py,sha256=upKssiY5poOO3Bv0amADZA-a5CrY5tnb4_97s8_88SM,2360
6
+ tenacity/nap.py,sha256=fRWvnz1aIzbIq9Ap3gAkAZgDH6oo5zxMrU6ZOVByq0I,1383
7
+ tenacity/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ tenacity/retry.py,sha256=PVnZfXBeLN2DzsytKexw23SPLn82AdZfFPC2OhVQ_1Y,8986
9
+ tenacity/stop.py,sha256=wQuwGfCLw8OH1C3x0G9lH9xtJCyhgviePQ40HRAUg54,4113
10
+ tenacity/tornadoweb.py,sha256=vS1ONfPYoGzPx1asQaVbfoo6D9tPIzSysJipm61Yqw8,2125
11
+ tenacity/wait.py,sha256=Q9XoZCtFra53aQOyfABpvRDuUeB-NpUUXImHsUiRQI0,8042
12
+ tenacity-8.4.0.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
13
+ tenacity-8.4.0.dist-info/METADATA,sha256=rnsKmqyLFhyN5Ydqjehhxuls6kPQ27yu6M4l2t5WeuM,1155
14
+ tenacity-8.4.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
15
+ tenacity-8.4.0.dist-info/top_level.txt,sha256=Zf8AOZMN7hr1EEcUo9U5KzXsM4TOC1pBZ22D8913JYs,9
16
+ tenacity-8.4.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
 
tenacity/_asyncio.py DELETED
@@ -1,94 +0,0 @@
1
- # Copyright 2016 Étienne Bersac
2
- # Copyright 2016 Julien Danjou
3
- # Copyright 2016 Joshua Harlow
4
- # Copyright 2013-2014 Ray Holder
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
-
18
- import functools
19
- import sys
20
- import typing as t
21
- from asyncio import sleep
22
-
23
- from tenacity import AttemptManager
24
- from tenacity import BaseRetrying
25
- from tenacity import DoAttempt
26
- from tenacity import DoSleep
27
- from tenacity import RetryCallState
28
-
29
- WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
30
- WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Awaitable[t.Any]])
31
-
32
-
33
- class AsyncRetrying(BaseRetrying):
34
- sleep: t.Callable[[float], t.Awaitable[t.Any]]
35
-
36
- def __init__(self, sleep: t.Callable[[float], t.Awaitable[t.Any]] = sleep, **kwargs: t.Any) -> None:
37
- super().__init__(**kwargs)
38
- self.sleep = sleep
39
-
40
- async def __call__( # type: ignore[override]
41
- self, fn: WrappedFn, *args: t.Any, **kwargs: t.Any
42
- ) -> WrappedFnReturnT:
43
- self.begin()
44
-
45
- retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)
46
- while True:
47
- do = self.iter(retry_state=retry_state)
48
- if isinstance(do, DoAttempt):
49
- try:
50
- result = await fn(*args, **kwargs)
51
- except BaseException: # noqa: B902
52
- retry_state.set_exception(sys.exc_info()) # type: ignore[arg-type]
53
- else:
54
- retry_state.set_result(result)
55
- elif isinstance(do, DoSleep):
56
- retry_state.prepare_for_next_attempt()
57
- await self.sleep(do)
58
- else:
59
- return do # type: ignore[no-any-return]
60
-
61
- def __iter__(self) -> t.Generator[AttemptManager, None, None]:
62
- raise TypeError("AsyncRetrying object is not iterable")
63
-
64
- def __aiter__(self) -> "AsyncRetrying":
65
- self.begin()
66
- self._retry_state = RetryCallState(self, fn=None, args=(), kwargs={})
67
- return self
68
-
69
- async def __anext__(self) -> AttemptManager:
70
- while True:
71
- do = self.iter(retry_state=self._retry_state)
72
- if do is None:
73
- raise StopAsyncIteration
74
- elif isinstance(do, DoAttempt):
75
- return AttemptManager(retry_state=self._retry_state)
76
- elif isinstance(do, DoSleep):
77
- self._retry_state.prepare_for_next_attempt()
78
- await self.sleep(do)
79
- else:
80
- raise StopAsyncIteration
81
-
82
- def wraps(self, fn: WrappedFn) -> WrappedFn:
83
- fn = super().wraps(fn)
84
- # Ensure wrapper is recognized as a coroutine function.
85
-
86
- @functools.wraps(fn)
87
- async def async_wrapped(*args: t.Any, **kwargs: t.Any) -> t.Any:
88
- return await fn(*args, **kwargs)
89
-
90
- # Preserve attributes
91
- async_wrapped.retry = fn.retry # type: ignore[attr-defined]
92
- async_wrapped.retry_with = fn.retry_with # type: ignore[attr-defined]
93
-
94
- return async_wrapped # type: ignore[return-value]
@@ -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,,