retry.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. # -*- encoding: utf-8 -*-
  2. #
  3. # Copyright 2016–2021 Julien Danjou
  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.
  18. import abc
  19. import re
  20. import six
  21. @six.add_metaclass(abc.ABCMeta)
  22. class retry_base(object):
  23. """Abstract base class for retry strategies."""
  24. @abc.abstractmethod
  25. def __call__(self, retry_state):
  26. pass
  27. def __and__(self, other):
  28. return retry_all(self, other)
  29. def __or__(self, other):
  30. return retry_any(self, other)
  31. class _retry_never(retry_base):
  32. """Retry strategy that never rejects any result."""
  33. def __call__(self, retry_state):
  34. return False
  35. retry_never = _retry_never()
  36. class _retry_always(retry_base):
  37. """Retry strategy that always rejects any result."""
  38. def __call__(self, retry_state):
  39. return True
  40. retry_always = _retry_always()
  41. class retry_if_exception(retry_base):
  42. """Retry strategy that retries if an exception verifies a predicate."""
  43. def __init__(self, predicate):
  44. self.predicate = predicate
  45. def __call__(self, retry_state):
  46. if retry_state.outcome.failed:
  47. return self.predicate(retry_state.outcome.exception())
  48. else:
  49. return False
  50. class retry_if_exception_type(retry_if_exception):
  51. """Retries if an exception has been raised of one or more types."""
  52. def __init__(self, exception_types=Exception):
  53. self.exception_types = exception_types
  54. super(retry_if_exception_type, self).__init__(
  55. lambda e: isinstance(e, exception_types)
  56. )
  57. class retry_unless_exception_type(retry_if_exception):
  58. """Retries until an exception is raised of one or more types."""
  59. def __init__(self, exception_types=Exception):
  60. self.exception_types = exception_types
  61. super(retry_unless_exception_type, self).__init__(
  62. lambda e: not isinstance(e, exception_types)
  63. )
  64. def __call__(self, retry_state):
  65. # always retry if no exception was raised
  66. if not retry_state.outcome.failed:
  67. return True
  68. return self.predicate(retry_state.outcome.exception())
  69. class retry_if_result(retry_base):
  70. """Retries if the result verifies a predicate."""
  71. def __init__(self, predicate):
  72. self.predicate = predicate
  73. def __call__(self, retry_state):
  74. if not retry_state.outcome.failed:
  75. return self.predicate(retry_state.outcome.result())
  76. else:
  77. return False
  78. class retry_if_not_result(retry_base):
  79. """Retries if the result refutes a predicate."""
  80. def __init__(self, predicate):
  81. self.predicate = predicate
  82. def __call__(self, retry_state):
  83. if not retry_state.outcome.failed:
  84. return not self.predicate(retry_state.outcome.result())
  85. else:
  86. return False
  87. class retry_if_exception_message(retry_if_exception):
  88. """Retries if an exception message equals or matches."""
  89. def __init__(self, message=None, match=None):
  90. if message and match:
  91. raise TypeError(
  92. "{}() takes either 'message' or 'match', not both".format(
  93. self.__class__.__name__
  94. )
  95. )
  96. # set predicate
  97. if message:
  98. def message_fnc(exception):
  99. return message == str(exception)
  100. predicate = message_fnc
  101. elif match:
  102. prog = re.compile(match)
  103. def match_fnc(exception):
  104. return prog.match(str(exception))
  105. predicate = match_fnc
  106. else:
  107. raise TypeError(
  108. "{}() missing 1 required argument 'message' or 'match'".format(
  109. self.__class__.__name__
  110. )
  111. )
  112. super(retry_if_exception_message, self).__init__(predicate)
  113. class retry_if_not_exception_message(retry_if_exception_message):
  114. """Retries until an exception message equals or matches."""
  115. def __init__(self, *args, **kwargs):
  116. super(retry_if_not_exception_message, self).__init__(*args, **kwargs)
  117. # invert predicate
  118. if_predicate = self.predicate
  119. self.predicate = lambda *args_, **kwargs_: not if_predicate(*args_, **kwargs_)
  120. def __call__(self, retry_state):
  121. if not retry_state.outcome.failed:
  122. return True
  123. return self.predicate(retry_state.outcome.exception())
  124. class retry_any(retry_base):
  125. """Retries if any of the retries condition is valid."""
  126. def __init__(self, *retries):
  127. self.retries = retries
  128. def __call__(self, retry_state):
  129. return any(r(retry_state) for r in self.retries)
  130. class retry_all(retry_base):
  131. """Retries if all the retries condition are valid."""
  132. def __init__(self, *retries):
  133. self.retries = retries
  134. def __call__(self, retry_state):
  135. return all(r(retry_state) for r in self.retries)