stop.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. # Copyright 2016–2021 Julien Danjou
  2. # Copyright 2016 Joshua Harlow
  3. # Copyright 2013-2014 Ray Holder
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import abc
  17. import typing
  18. from tenacity import _utils
  19. if typing.TYPE_CHECKING:
  20. import threading
  21. from tenacity import RetryCallState
  22. class stop_base(abc.ABC):
  23. """Abstract base class for stop strategies."""
  24. @abc.abstractmethod
  25. def __call__(self, retry_state: "RetryCallState") -> bool:
  26. pass
  27. def __and__(self, other: "stop_base") -> "stop_all":
  28. return stop_all(self, other)
  29. def __or__(self, other: "stop_base") -> "stop_any":
  30. return stop_any(self, other)
  31. StopBaseT = typing.Union[stop_base, typing.Callable[["RetryCallState"], bool]]
  32. class stop_any(stop_base):
  33. """Stop if any of the stop condition is valid."""
  34. def __init__(self, *stops: stop_base) -> None:
  35. self.stops = stops
  36. def __call__(self, retry_state: "RetryCallState") -> bool:
  37. return any(x(retry_state) for x in self.stops)
  38. class stop_all(stop_base):
  39. """Stop if all the stop conditions are valid."""
  40. def __init__(self, *stops: stop_base) -> None:
  41. self.stops = stops
  42. def __call__(self, retry_state: "RetryCallState") -> bool:
  43. return all(x(retry_state) for x in self.stops)
  44. class _stop_never(stop_base):
  45. """Never stop."""
  46. def __call__(self, retry_state: "RetryCallState") -> bool:
  47. return False
  48. stop_never = _stop_never()
  49. class stop_when_event_set(stop_base):
  50. """Stop when the given event is set."""
  51. def __init__(self, event: "threading.Event") -> None:
  52. self.event = event
  53. def __call__(self, retry_state: "RetryCallState") -> bool:
  54. return self.event.is_set()
  55. class stop_after_attempt(stop_base):
  56. """Stop when the previous attempt >= max_attempt."""
  57. def __init__(self, max_attempt_number: int) -> None:
  58. self.max_attempt_number = max_attempt_number
  59. def __call__(self, retry_state: "RetryCallState") -> bool:
  60. return retry_state.attempt_number >= self.max_attempt_number
  61. class stop_after_delay(stop_base):
  62. """
  63. Stop when the time from the first attempt >= limit.
  64. Note: `max_delay` will be exceeded, so when used with a `wait`, the actual total delay will be greater
  65. than `max_delay` by some of the final sleep period before `max_delay` is exceeded.
  66. If you need stricter timing with waits, consider `stop_before_delay` instead.
  67. """
  68. def __init__(self, max_delay: _utils.time_unit_type) -> None:
  69. self.max_delay = _utils.to_seconds(max_delay)
  70. def __call__(self, retry_state: "RetryCallState") -> bool:
  71. if retry_state.seconds_since_start is None:
  72. raise RuntimeError("__call__() called but seconds_since_start is not set")
  73. return retry_state.seconds_since_start >= self.max_delay
  74. class stop_before_delay(stop_base):
  75. """
  76. Stop right before the next attempt would take place after the time from the first attempt >= limit.
  77. Most useful when you are using with a `wait` function like wait_random_exponential, but need to make
  78. sure that the max_delay is not exceeded.
  79. """
  80. def __init__(self, max_delay: _utils.time_unit_type) -> None:
  81. self.max_delay = _utils.to_seconds(max_delay)
  82. def __call__(self, retry_state: "RetryCallState") -> bool:
  83. if retry_state.seconds_since_start is None:
  84. raise RuntimeError("__call__() called but seconds_since_start is not set")
  85. return (
  86. retry_state.seconds_since_start + retry_state.upcoming_sleep
  87. >= self.max_delay
  88. )