Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tenacity/__init__.py: 49%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright 2016-2018 Julien Danjou
2# Copyright 2017 Elisey Zanko
3# Copyright 2016 Étienne Bersac
4# Copyright 2016 Joshua Harlow
5# Copyright 2013-2014 Ray Holder
6#
7# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18import dataclasses
19import functools
20import sys
21import threading
22import time
23import typing as t
24import warnings
25from abc import ABC, abstractmethod
26from concurrent import futures
27from inspect import iscoroutinefunction
29# Import all built-in retry strategies for easier usage.
30from .retry import retry_base # noqa
31from .retry import retry_all # noqa
32from .retry import retry_always # noqa
33from .retry import retry_any # noqa
34from .retry import retry_if_exception # noqa
35from .retry import retry_if_exception_type # noqa
36from .retry import retry_if_exception_cause_type # noqa
37from .retry import retry_if_not_exception_type # noqa
38from .retry import retry_if_not_result # noqa
39from .retry import retry_if_result # noqa
40from .retry import retry_never # noqa
41from .retry import retry_unless_exception_type # noqa
42from .retry import retry_if_exception_message # noqa
43from .retry import retry_if_not_exception_message # noqa
45# Import all nap strategies for easier usage.
46from .nap import sleep # noqa
47from .nap import sleep_using_event # noqa
49# Import all built-in stop strategies for easier usage.
50from .stop import stop_after_attempt # noqa
51from .stop import stop_after_delay # noqa
52from .stop import stop_before_delay # noqa
53from .stop import stop_all # noqa
54from .stop import stop_any # noqa
55from .stop import stop_never # noqa
56from .stop import stop_when_event_set # noqa
58# Import all built-in wait strategies for easier usage.
59from .wait import wait_chain # noqa
60from .wait import wait_combine # noqa
61from .wait import wait_exponential # noqa
62from .wait import wait_fixed # noqa
63from .wait import wait_incrementing # noqa
64from .wait import wait_none # noqa
65from .wait import wait_random # noqa
66from .wait import wait_random_exponential # noqa
67from .wait import wait_random_exponential as wait_full_jitter # noqa
68from .wait import wait_exponential_jitter # noqa
70# Import all built-in before strategies for easier usage.
71from .before import before_log # noqa
72from .before import before_nothing # noqa
74# Import all built-in after strategies for easier usage.
75from .after import after_log # noqa
76from .after import after_nothing # noqa
78# Import all built-in after strategies for easier usage.
79from .before_sleep import before_sleep_log # noqa
80from .before_sleep import before_sleep_nothing # noqa
82try:
83 import tornado
84except ImportError:
85 tornado = None
87if t.TYPE_CHECKING:
88 import types
90 from .retry import RetryBaseT
91 from .stop import StopBaseT
92 from .wait import WaitBaseT
95WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
96WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Any])
99dataclass_kwargs = {}
100if sys.version_info >= (3, 10):
101 dataclass_kwargs.update({"slots": True})
104@dataclasses.dataclass(**dataclass_kwargs)
105class 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
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
122class TryAgain(Exception):
123 """Always retry the executed function when raised."""
126NO_RESULT = object()
129class DoAttempt:
130 pass
133class DoSleep(float):
134 pass
137class BaseAction:
138 """Base class for representing actions to take by retry object.
140 Concrete implementations must define:
141 - __init__: to initialize all necessary fields
142 - REPR_FIELDS: class variable specifying attributes to include in repr(self)
143 - NAME: for identification in retry object methods and callbacks
144 """
146 REPR_FIELDS: t.Sequence[str] = ()
147 NAME: t.Optional[str] = None
149 def __repr__(self) -> str:
150 state_str = ", ".join(
151 f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS
152 )
153 return f"{self.__class__.__name__}({state_str})"
155 def __str__(self) -> str:
156 return repr(self)
159class RetryAction(BaseAction):
160 REPR_FIELDS = ("sleep",)
161 NAME = "retry"
163 def __init__(self, sleep: t.SupportsFloat) -> None:
164 self.sleep = float(sleep)
167_unset = object()
170def _first_set(first: t.Union[t.Any, object], second: t.Any) -> t.Any:
171 return second if first is _unset else first
174class RetryError(Exception):
175 """Encapsulates the last attempt instance right before giving up."""
177 def __init__(self, last_attempt: "Future") -> None:
178 self.last_attempt = last_attempt
179 super().__init__(last_attempt)
181 def reraise(self) -> t.NoReturn:
182 if self.last_attempt.failed:
183 raise self.last_attempt.result()
184 raise self
186 def __str__(self) -> str:
187 return f"{self.__class__.__name__}[{self.last_attempt}]"
190class AttemptManager:
191 """Manage attempt context."""
193 def __init__(self, retry_state: "RetryCallState"):
194 self.retry_state = retry_state
196 def __enter__(self) -> None:
197 pass
199 def __exit__(
200 self,
201 exc_type: t.Optional[t.Type[BaseException]],
202 exc_value: t.Optional[BaseException],
203 traceback: t.Optional["types.TracebackType"],
204 ) -> t.Optional[bool]:
205 if exc_type is not None and exc_value is not None:
206 self.retry_state.set_exception((exc_type, exc_value, traceback))
207 return True # Swallow exception.
208 else:
209 # We don't have the result, actually.
210 self.retry_state.set_result(None)
211 return None
214class BaseRetrying(ABC):
215 def __init__(
216 self,
217 sleep: t.Callable[[t.Union[int, float]], None] = sleep,
218 stop: "StopBaseT" = stop_never,
219 wait: "WaitBaseT" = wait_none(),
220 retry: "RetryBaseT" = retry_if_exception_type(),
221 before: t.Callable[["RetryCallState"], None] = before_nothing,
222 after: t.Callable[["RetryCallState"], None] = after_nothing,
223 before_sleep: t.Optional[t.Callable[["RetryCallState"], None]] = None,
224 reraise: bool = False,
225 retry_error_cls: t.Type[RetryError] = RetryError,
226 retry_error_callback: t.Optional[t.Callable[["RetryCallState"], t.Any]] = None,
227 ):
228 self.sleep = sleep
229 self.stop = stop
230 self.wait = wait
231 self.retry = retry
232 self.before = before
233 self.after = after
234 self.before_sleep = before_sleep
235 self.reraise = reraise
236 self._local = threading.local()
237 self.retry_error_cls = retry_error_cls
238 self.retry_error_callback = retry_error_callback
240 def copy(
241 self,
242 sleep: t.Union[t.Callable[[t.Union[int, float]], None], object] = _unset,
243 stop: t.Union["StopBaseT", object] = _unset,
244 wait: t.Union["WaitBaseT", object] = _unset,
245 retry: t.Union[retry_base, object] = _unset,
246 before: t.Union[t.Callable[["RetryCallState"], None], object] = _unset,
247 after: t.Union[t.Callable[["RetryCallState"], None], object] = _unset,
248 before_sleep: t.Union[
249 t.Optional[t.Callable[["RetryCallState"], None]], object
250 ] = _unset,
251 reraise: t.Union[bool, object] = _unset,
252 retry_error_cls: t.Union[t.Type[RetryError], object] = _unset,
253 retry_error_callback: t.Union[
254 t.Optional[t.Callable[["RetryCallState"], t.Any]], object
255 ] = _unset,
256 ) -> "BaseRetrying":
257 """Copy this object with some parameters changed if needed."""
258 return self.__class__(
259 sleep=_first_set(sleep, self.sleep),
260 stop=_first_set(stop, self.stop),
261 wait=_first_set(wait, self.wait),
262 retry=_first_set(retry, self.retry),
263 before=_first_set(before, self.before),
264 after=_first_set(after, self.after),
265 before_sleep=_first_set(before_sleep, self.before_sleep),
266 reraise=_first_set(reraise, self.reraise),
267 retry_error_cls=_first_set(retry_error_cls, self.retry_error_cls),
268 retry_error_callback=_first_set(
269 retry_error_callback, self.retry_error_callback
270 ),
271 )
273 def __repr__(self) -> str:
274 return (
275 f"<{self.__class__.__name__} object at 0x{id(self):x} ("
276 f"stop={self.stop}, "
277 f"wait={self.wait}, "
278 f"sleep={self.sleep}, "
279 f"retry={self.retry}, "
280 f"before={self.before}, "
281 f"after={self.after})>"
282 )
284 @property
285 def statistics(self) -> t.Dict[str, t.Any]:
286 """Return a dictionary of runtime statistics.
288 This dictionary will be empty when the controller has never been
289 ran. When it is running or has ran previously it should have (but
290 may not) have useful and/or informational keys and values when
291 running is underway and/or completed.
293 .. warning:: The keys in this dictionary **should** be some what
294 stable (not changing), but there existence **may**
295 change between major releases as new statistics are
296 gathered or removed so before accessing keys ensure that
297 they actually exist and handle when they do not.
299 .. note:: The values in this dictionary are local to the thread
300 running call (so if multiple threads share the same retrying
301 object - either directly or indirectly) they will each have
302 there own view of statistics they have collected (in the
303 future we may provide a way to aggregate the various
304 statistics from each thread).
305 """
306 try:
307 return self._local.statistics # type: ignore[no-any-return]
308 except AttributeError:
309 self._local.statistics = t.cast(t.Dict[str, t.Any], {})
310 return self._local.statistics
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
320 def wraps(self, f: WrappedFn) -> WrappedFn:
321 """Wrap a function for retrying.
323 :param f: A function to wraps for retrying.
324 """
326 @functools.wraps(
327 f, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__")
328 )
329 def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any:
330 return self(f, *args, **kw)
332 def retry_with(*args: t.Any, **kwargs: t.Any) -> WrappedFn:
333 return self.copy(*args, **kwargs).wraps(f)
335 wrapped_f.retry = self # type: ignore[attr-defined]
336 wrapped_f.retry_with = retry_with # type: ignore[attr-defined]
338 return wrapped_f # type: ignore[return-value]
340 def begin(self) -> None:
341 self.statistics.clear()
342 self.statistics["start_time"] = time.monotonic()
343 self.statistics["attempt_number"] = 1
344 self.statistics["idle_for"] = 0
346 def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None:
347 self.iter_state.actions.append(fn)
349 def _run_retry(self, retry_state: "RetryCallState") -> None:
350 self.iter_state.retry_run_result = self.retry(retry_state)
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
358 retry_state.upcoming_sleep = sleep
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)
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
371 def _begin_iter(self, retry_state: "RetryCallState") -> None: # noqa
372 self.iter_state.reset()
374 fut = retry_state.outcome
375 if fut is None:
376 if self.before is not None:
377 self._add_action_func(self.before)
378 self._add_action_func(lambda rs: DoAttempt())
379 return
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)
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
393 if self.after is not None:
394 self._add_action_func(self.after)
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)
400 def _post_stop_check_actions(self, retry_state: "RetryCallState") -> None:
401 if self.iter_state.stop_run_result:
402 if self.retry_error_callback:
403 self._add_action_func(self.retry_error_callback)
404 return
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()
413 self._add_action_func(exc_check)
414 return
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
423 self._add_action_func(next_action)
425 if self.before_sleep is not None:
426 self._add_action_func(self.before_sleep)
428 self._add_action_func(lambda rs: DoSleep(rs.upcoming_sleep))
430 def __iter__(self) -> t.Generator[AttemptManager, None, None]:
431 self.begin()
433 retry_state = RetryCallState(self, fn=None, args=(), kwargs={})
434 while True:
435 do = self.iter(retry_state=retry_state)
436 if isinstance(do, DoAttempt):
437 yield AttemptManager(retry_state=retry_state)
438 elif isinstance(do, DoSleep):
439 retry_state.prepare_for_next_attempt()
440 self.sleep(do)
441 else:
442 break
444 @abstractmethod
445 def __call__(
446 self,
447 fn: t.Callable[..., WrappedFnReturnT],
448 *args: t.Any,
449 **kwargs: t.Any,
450 ) -> WrappedFnReturnT:
451 pass
454class Retrying(BaseRetrying):
455 """Retrying controller."""
457 def __call__(
458 self,
459 fn: t.Callable[..., WrappedFnReturnT],
460 *args: t.Any,
461 **kwargs: t.Any,
462 ) -> WrappedFnReturnT:
463 self.begin()
465 retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)
466 while True:
467 do = self.iter(retry_state=retry_state)
468 if isinstance(do, DoAttempt):
469 try:
470 result = fn(*args, **kwargs)
471 except BaseException: # noqa: B902
472 retry_state.set_exception(sys.exc_info()) # type: ignore[arg-type]
473 else:
474 retry_state.set_result(result)
475 elif isinstance(do, DoSleep):
476 retry_state.prepare_for_next_attempt()
477 self.sleep(do)
478 else:
479 return do # type: ignore[no-any-return]
482if sys.version_info >= (3, 9):
483 FutureGenericT = futures.Future[t.Any]
484else:
485 FutureGenericT = futures.Future
488class Future(FutureGenericT):
489 """Encapsulates a (future or past) attempted call to a target function."""
491 def __init__(self, attempt_number: int) -> None:
492 super().__init__()
493 self.attempt_number = attempt_number
495 @property
496 def failed(self) -> bool:
497 """Return whether a exception is being held in this future."""
498 return self.exception() is not None
500 @classmethod
501 def construct(
502 cls, attempt_number: int, value: t.Any, has_exception: bool
503 ) -> "Future":
504 """Construct a new Future object."""
505 fut = cls(attempt_number)
506 if has_exception:
507 fut.set_exception(value)
508 else:
509 fut.set_result(value)
510 return fut
513class RetryCallState:
514 """State related to a single call wrapped with Retrying."""
516 def __init__(
517 self,
518 retry_object: BaseRetrying,
519 fn: t.Optional[WrappedFn],
520 args: t.Any,
521 kwargs: t.Any,
522 ) -> None:
523 #: Retry call start timestamp
524 self.start_time = time.monotonic()
525 #: Retry manager object
526 self.retry_object = retry_object
527 #: Function wrapped by this retry call
528 self.fn = fn
529 #: Arguments of the function wrapped by this retry call
530 self.args = args
531 #: Keyword arguments of the function wrapped by this retry call
532 self.kwargs = kwargs
534 #: The number of the current attempt
535 self.attempt_number: int = 1
536 #: Last outcome (result or exception) produced by the function
537 self.outcome: t.Optional[Future] = None
538 #: Timestamp of the last outcome
539 self.outcome_timestamp: t.Optional[float] = None
540 #: Time spent sleeping in retries
541 self.idle_for: float = 0.0
542 #: Next action as decided by the retry manager
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
547 @property
548 def seconds_since_start(self) -> t.Optional[float]:
549 if self.outcome_timestamp is None:
550 return None
551 return self.outcome_timestamp - self.start_time
553 def prepare_for_next_attempt(self) -> None:
554 self.outcome = None
555 self.outcome_timestamp = None
556 self.attempt_number += 1
557 self.next_action = None
559 def set_result(self, val: t.Any) -> None:
560 ts = time.monotonic()
561 fut = Future(self.attempt_number)
562 fut.set_result(val)
563 self.outcome, self.outcome_timestamp = fut, ts
565 def set_exception(
566 self,
567 exc_info: t.Tuple[
568 t.Type[BaseException], BaseException, "types.TracebackType| None"
569 ],
570 ) -> None:
571 ts = time.monotonic()
572 fut = Future(self.attempt_number)
573 fut.set_exception(exc_info[1])
574 self.outcome, self.outcome_timestamp = fut, ts
576 def __repr__(self) -> str:
577 if self.outcome is None:
578 result = "none yet"
579 elif self.outcome.failed:
580 exception = self.outcome.exception()
581 result = f"failed ({exception.__class__.__name__} {exception})"
582 else:
583 result = f"returned {self.outcome.result()}"
585 slept = float(round(self.idle_for, 2))
586 clsname = self.__class__.__name__
587 return f"<{clsname} {id(self)}: attempt #{self.attempt_number}; slept for {slept}; last result: {result}>"
590@t.overload
591def retry(func: WrappedFn) -> WrappedFn: ...
594@t.overload
595def retry(
596 sleep: t.Callable[[t.Union[int, float]], t.Optional[t.Awaitable[None]]] = sleep,
597 stop: "StopBaseT" = stop_never,
598 wait: "WaitBaseT" = wait_none(),
599 retry: "RetryBaseT" = retry_if_exception_type(),
600 before: t.Callable[["RetryCallState"], None] = before_nothing,
601 after: t.Callable[["RetryCallState"], None] = after_nothing,
602 before_sleep: t.Optional[t.Callable[["RetryCallState"], None]] = None,
603 reraise: bool = False,
604 retry_error_cls: t.Type["RetryError"] = RetryError,
605 retry_error_callback: t.Optional[t.Callable[["RetryCallState"], t.Any]] = None,
606) -> t.Callable[[WrappedFn], WrappedFn]: ...
609def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:
610 """Wrap a function with a new `Retrying` object.
612 :param dargs: positional arguments passed to Retrying object
613 :param dkw: keyword arguments passed to the Retrying object
614 """
615 # support both @retry and @retry() as valid syntax
616 if len(dargs) == 1 and callable(dargs[0]):
617 return retry()(dargs[0])
618 else:
620 def wrap(f: WrappedFn) -> WrappedFn:
621 if isinstance(f, retry_base):
622 warnings.warn(
623 f"Got retry_base instance ({f.__class__.__name__}) as callable argument, "
624 f"this will probably hang indefinitely (did you mean retry={f.__class__.__name__}(...)?)"
625 )
626 r: "BaseRetrying"
627 if iscoroutinefunction(f):
628 r = AsyncRetrying(*dargs, **dkw)
629 elif (
630 tornado
631 and hasattr(tornado.gen, "is_coroutine_function")
632 and tornado.gen.is_coroutine_function(f)
633 ):
634 r = TornadoRetrying(*dargs, **dkw)
635 else:
636 r = Retrying(*dargs, **dkw)
638 return r.wraps(f)
640 return wrap
643from tenacity._asyncio import AsyncRetrying # noqa:E402,I100
645if tornado:
646 from tenacity.tornadoweb import TornadoRetrying
649__all__ = [
650 "retry_base",
651 "retry_all",
652 "retry_always",
653 "retry_any",
654 "retry_if_exception",
655 "retry_if_exception_type",
656 "retry_if_exception_cause_type",
657 "retry_if_not_exception_type",
658 "retry_if_not_result",
659 "retry_if_result",
660 "retry_never",
661 "retry_unless_exception_type",
662 "retry_if_exception_message",
663 "retry_if_not_exception_message",
664 "sleep",
665 "sleep_using_event",
666 "stop_after_attempt",
667 "stop_after_delay",
668 "stop_before_delay",
669 "stop_all",
670 "stop_any",
671 "stop_never",
672 "stop_when_event_set",
673 "wait_chain",
674 "wait_combine",
675 "wait_exponential",
676 "wait_fixed",
677 "wait_incrementing",
678 "wait_none",
679 "wait_random",
680 "wait_random_exponential",
681 "wait_full_jitter",
682 "wait_exponential_jitter",
683 "before_log",
684 "before_nothing",
685 "after_log",
686 "after_nothing",
687 "before_sleep_log",
688 "before_sleep_nothing",
689 "retry",
690 "WrappedFn",
691 "TryAgain",
692 "NO_RESULT",
693 "DoAttempt",
694 "DoSleep",
695 "BaseAction",
696 "RetryAction",
697 "RetryError",
698 "AttemptManager",
699 "BaseRetrying",
700 "Retrying",
701 "Future",
702 "RetryCallState",
703 "AsyncRetrying",
704]