compat.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. """Utilities for providing backward compatibility."""
  2. import inspect
  3. from fractions import Fraction
  4. from warnings import warn
  5. import six
  6. from tenacity import _utils
  7. def warn_about_non_retry_state_deprecation(cbname, func, stacklevel):
  8. msg = (
  9. '"%s" function must accept single "retry_state" parameter,'
  10. ' please update %s' % (cbname, _utils.get_callback_name(func)))
  11. warn(msg, DeprecationWarning, stacklevel=stacklevel + 1)
  12. def warn_about_dunder_non_retry_state_deprecation(fn, stacklevel):
  13. msg = (
  14. '"%s" method must be called with'
  15. ' single "retry_state" parameter' % (_utils.get_callback_name(fn)))
  16. warn(msg, DeprecationWarning, stacklevel=stacklevel + 1)
  17. def func_takes_retry_state(func):
  18. if not six.callable(func):
  19. return False
  20. if not inspect.isfunction(func):
  21. # func is a callable object rather than a function
  22. func = func.__call__
  23. func_spec = _utils.getargspec(func)
  24. return 'retry_state' in func_spec.args
  25. _unset = object()
  26. def _make_unset_exception(func_name, **kwargs):
  27. missing = []
  28. for k, v in kwargs.iteritems():
  29. if v is _unset:
  30. missing.append(k)
  31. missing_str = ', '.join(repr(s) for s in missing)
  32. return TypeError(func_name + ' func missing parameters: ' + missing_str)
  33. def _set_delay_since_start(retry_state, delay):
  34. # Ensure outcome_timestamp - start_time is *exactly* equal to the delay to
  35. # avoid complexity in test code.
  36. retry_state.start_time = Fraction(retry_state.start_time)
  37. retry_state.outcome_timestamp = (retry_state.start_time + Fraction(delay))
  38. assert retry_state.seconds_since_start == delay
  39. def make_retry_state(previous_attempt_number, delay_since_first_attempt,
  40. last_result=None):
  41. """Construct RetryCallState for given attempt number & delay.
  42. Only used in testing and thus is extra careful about timestamp arithmetics.
  43. """
  44. required_parameter_unset = (previous_attempt_number is _unset or
  45. delay_since_first_attempt is _unset)
  46. if required_parameter_unset:
  47. raise _make_unset_exception(
  48. 'wait/stop',
  49. previous_attempt_number=previous_attempt_number,
  50. delay_since_first_attempt=delay_since_first_attempt)
  51. from tenacity import RetryCallState
  52. retry_state = RetryCallState(None, None, (), {})
  53. retry_state.attempt_number = previous_attempt_number
  54. if last_result is not None:
  55. retry_state.outcome = last_result
  56. else:
  57. retry_state.set_result(None)
  58. _set_delay_since_start(retry_state, delay_since_first_attempt)
  59. return retry_state
  60. def func_takes_last_result(waiter):
  61. """Check if function has a "last_result" parameter.
  62. Needed to provide backward compatibility for wait functions that didn't
  63. take "last_result" in the beginning.
  64. """
  65. if not six.callable(waiter):
  66. return False
  67. if not inspect.isfunction(waiter):
  68. # waiter is a class, check dunder-call rather than dunder-init.
  69. waiter = waiter.__call__
  70. waiter_spec = _utils.getargspec(waiter)
  71. return 'last_result' in waiter_spec.args
  72. def stop_dunder_call_accept_old_params(fn):
  73. """Decorate cls.__call__ method to accept old "stop" signature."""
  74. @_utils.wraps(fn)
  75. def new_fn(self,
  76. previous_attempt_number=_unset,
  77. delay_since_first_attempt=_unset,
  78. retry_state=None):
  79. if retry_state is None:
  80. from tenacity import RetryCallState
  81. retry_state_passed_as_non_kwarg = (
  82. previous_attempt_number is not _unset and
  83. isinstance(previous_attempt_number, RetryCallState))
  84. if retry_state_passed_as_non_kwarg:
  85. retry_state = previous_attempt_number
  86. else:
  87. warn_about_dunder_non_retry_state_deprecation(fn, stacklevel=2)
  88. retry_state = make_retry_state(
  89. previous_attempt_number=previous_attempt_number,
  90. delay_since_first_attempt=delay_since_first_attempt)
  91. return fn(self, retry_state=retry_state)
  92. return new_fn
  93. def stop_func_accept_retry_state(stop_func):
  94. """Wrap "stop" function to accept "retry_state" parameter."""
  95. if not six.callable(stop_func):
  96. return stop_func
  97. if func_takes_retry_state(stop_func):
  98. return stop_func
  99. @_utils.wraps(stop_func)
  100. def wrapped_stop_func(retry_state):
  101. warn_about_non_retry_state_deprecation(
  102. 'stop', stop_func, stacklevel=4)
  103. return stop_func(
  104. retry_state.attempt_number,
  105. retry_state.seconds_since_start,
  106. )
  107. return wrapped_stop_func
  108. def wait_dunder_call_accept_old_params(fn):
  109. """Decorate cls.__call__ method to accept old "wait" signature."""
  110. @_utils.wraps(fn)
  111. def new_fn(self,
  112. previous_attempt_number=_unset,
  113. delay_since_first_attempt=_unset,
  114. last_result=None,
  115. retry_state=None):
  116. if retry_state is None:
  117. from tenacity import RetryCallState
  118. retry_state_passed_as_non_kwarg = (
  119. previous_attempt_number is not _unset and
  120. isinstance(previous_attempt_number, RetryCallState))
  121. if retry_state_passed_as_non_kwarg:
  122. retry_state = previous_attempt_number
  123. else:
  124. warn_about_dunder_non_retry_state_deprecation(fn, stacklevel=2)
  125. retry_state = make_retry_state(
  126. previous_attempt_number=previous_attempt_number,
  127. delay_since_first_attempt=delay_since_first_attempt,
  128. last_result=last_result)
  129. return fn(self, retry_state=retry_state)
  130. return new_fn
  131. def wait_func_accept_retry_state(wait_func):
  132. """Wrap wait function to accept "retry_state" parameter."""
  133. if not six.callable(wait_func):
  134. return wait_func
  135. if func_takes_retry_state(wait_func):
  136. return wait_func
  137. if func_takes_last_result(wait_func):
  138. @_utils.wraps(wait_func)
  139. def wrapped_wait_func(retry_state):
  140. warn_about_non_retry_state_deprecation(
  141. 'wait', wait_func, stacklevel=4)
  142. return wait_func(
  143. retry_state.attempt_number,
  144. retry_state.seconds_since_start,
  145. last_result=retry_state.outcome,
  146. )
  147. else:
  148. @_utils.wraps(wait_func)
  149. def wrapped_wait_func(retry_state):
  150. warn_about_non_retry_state_deprecation(
  151. 'wait', wait_func, stacklevel=4)
  152. return wait_func(
  153. retry_state.attempt_number,
  154. retry_state.seconds_since_start,
  155. )
  156. return wrapped_wait_func
  157. def retry_dunder_call_accept_old_params(fn):
  158. """Decorate cls.__call__ method to accept old "retry" signature."""
  159. @_utils.wraps(fn)
  160. def new_fn(self, attempt=_unset, retry_state=None):
  161. if retry_state is None:
  162. from tenacity import RetryCallState
  163. if attempt is _unset:
  164. raise _make_unset_exception('retry', attempt=attempt)
  165. retry_state_passed_as_non_kwarg = (
  166. attempt is not _unset and
  167. isinstance(attempt, RetryCallState))
  168. if retry_state_passed_as_non_kwarg:
  169. retry_state = attempt
  170. else:
  171. warn_about_dunder_non_retry_state_deprecation(fn, stacklevel=2)
  172. retry_state = RetryCallState(None, None, (), {})
  173. retry_state.outcome = attempt
  174. return fn(self, retry_state=retry_state)
  175. return new_fn
  176. def retry_func_accept_retry_state(retry_func):
  177. """Wrap "retry" function to accept "retry_state" parameter."""
  178. if not six.callable(retry_func):
  179. return retry_func
  180. if func_takes_retry_state(retry_func):
  181. return retry_func
  182. @_utils.wraps(retry_func)
  183. def wrapped_retry_func(retry_state):
  184. warn_about_non_retry_state_deprecation(
  185. 'retry', retry_func, stacklevel=4)
  186. return retry_func(retry_state.outcome)
  187. return wrapped_retry_func
  188. def before_func_accept_retry_state(fn):
  189. """Wrap "before" function to accept "retry_state"."""
  190. if not six.callable(fn):
  191. return fn
  192. if func_takes_retry_state(fn):
  193. return fn
  194. @_utils.wraps(fn)
  195. def wrapped_before_func(retry_state):
  196. # func, trial_number, trial_time_taken
  197. warn_about_non_retry_state_deprecation('before', fn, stacklevel=4)
  198. return fn(
  199. retry_state.fn,
  200. retry_state.attempt_number,
  201. )
  202. return wrapped_before_func
  203. def after_func_accept_retry_state(fn):
  204. """Wrap "after" function to accept "retry_state"."""
  205. if not six.callable(fn):
  206. return fn
  207. if func_takes_retry_state(fn):
  208. return fn
  209. @_utils.wraps(fn)
  210. def wrapped_after_sleep_func(retry_state):
  211. # func, trial_number, trial_time_taken
  212. warn_about_non_retry_state_deprecation('after', fn, stacklevel=4)
  213. return fn(
  214. retry_state.fn,
  215. retry_state.attempt_number,
  216. retry_state.seconds_since_start)
  217. return wrapped_after_sleep_func
  218. def before_sleep_func_accept_retry_state(fn):
  219. """Wrap "before_sleep" function to accept "retry_state"."""
  220. if not six.callable(fn):
  221. return fn
  222. if func_takes_retry_state(fn):
  223. return fn
  224. @_utils.wraps(fn)
  225. def wrapped_before_sleep_func(retry_state):
  226. # retry_object, sleep, last_result
  227. warn_about_non_retry_state_deprecation(
  228. 'before_sleep', fn, stacklevel=4)
  229. return fn(
  230. retry_state.retry_object,
  231. sleep=getattr(retry_state.next_action, 'sleep'),
  232. last_result=retry_state.outcome)
  233. return wrapped_before_sleep_func
  234. def retry_error_callback_accept_retry_state(fn):
  235. if not six.callable(fn):
  236. return fn
  237. if func_takes_retry_state(fn):
  238. return fn
  239. @_utils.wraps(fn)
  240. def wrapped_retry_error_callback(retry_state):
  241. warn_about_non_retry_state_deprecation(
  242. 'retry_error_callback', fn, stacklevel=4)
  243. return fn(retry_state.outcome)
  244. return wrapped_retry_error_callback