| """Utilities for providing backward compatibility.""" |
| |
| import inspect |
| from fractions import Fraction |
| from warnings import warn |
| |
| import six |
| |
| from tenacity import _utils |
| |
| |
| def warn_about_non_retry_state_deprecation(cbname, func, stacklevel): |
| msg = ( |
| '"%s" function must accept single "retry_state" parameter,' |
| ' please update %s' % (cbname, _utils.get_callback_name(func))) |
| warn(msg, DeprecationWarning, stacklevel=stacklevel + 1) |
| |
| |
| def warn_about_dunder_non_retry_state_deprecation(fn, stacklevel): |
| msg = ( |
| '"%s" method must be called with' |
| ' single "retry_state" parameter' % (_utils.get_callback_name(fn))) |
| warn(msg, DeprecationWarning, stacklevel=stacklevel + 1) |
| |
| |
| def func_takes_retry_state(func): |
| if not six.callable(func): |
| raise Exception(func) |
| return False |
| if not inspect.isfunction(func) and not inspect.ismethod(func): |
| # func is a callable object rather than a function/method |
| func = func.__call__ |
| func_spec = _utils.getargspec(func) |
| return 'retry_state' in func_spec.args |
| |
| |
| _unset = object() |
| |
| |
| def _make_unset_exception(func_name, **kwargs): |
| missing = [] |
| for k, v in six.iteritems(kwargs): |
| if v is _unset: |
| missing.append(k) |
| missing_str = ', '.join(repr(s) for s in missing) |
| return TypeError(func_name + ' func missing parameters: ' + missing_str) |
| |
| |
| def _set_delay_since_start(retry_state, delay): |
| # Ensure outcome_timestamp - start_time is *exactly* equal to the delay to |
| # avoid complexity in test code. |
| retry_state.start_time = Fraction(retry_state.start_time) |
| retry_state.outcome_timestamp = (retry_state.start_time + Fraction(delay)) |
| assert retry_state.seconds_since_start == delay |
| |
| |
| def make_retry_state(previous_attempt_number, delay_since_first_attempt, |
| last_result=None): |
| """Construct RetryCallState for given attempt number & delay. |
| |
| Only used in testing and thus is extra careful about timestamp arithmetics. |
| """ |
| required_parameter_unset = (previous_attempt_number is _unset or |
| delay_since_first_attempt is _unset) |
| if required_parameter_unset: |
| raise _make_unset_exception( |
| 'wait/stop', |
| previous_attempt_number=previous_attempt_number, |
| delay_since_first_attempt=delay_since_first_attempt) |
| |
| from tenacity import RetryCallState |
| retry_state = RetryCallState(None, None, (), {}) |
| retry_state.attempt_number = previous_attempt_number |
| if last_result is not None: |
| retry_state.outcome = last_result |
| else: |
| retry_state.set_result(None) |
| _set_delay_since_start(retry_state, delay_since_first_attempt) |
| return retry_state |
| |
| |
| def func_takes_last_result(waiter): |
| """Check if function has a "last_result" parameter. |
| |
| Needed to provide backward compatibility for wait functions that didn't |
| take "last_result" in the beginning. |
| """ |
| if not six.callable(waiter): |
| return False |
| if not inspect.isfunction(waiter) and not inspect.ismethod(waiter): |
| # waiter is a class, check dunder-call rather than dunder-init. |
| waiter = waiter.__call__ |
| waiter_spec = _utils.getargspec(waiter) |
| return 'last_result' in waiter_spec.args |
| |
| |
| def stop_dunder_call_accept_old_params(fn): |
| """Decorate cls.__call__ method to accept old "stop" signature.""" |
| @_utils.wraps(fn) |
| def new_fn(self, |
| previous_attempt_number=_unset, |
| delay_since_first_attempt=_unset, |
| retry_state=None): |
| if retry_state is None: |
| from tenacity import RetryCallState |
| retry_state_passed_as_non_kwarg = ( |
| previous_attempt_number is not _unset and |
| isinstance(previous_attempt_number, RetryCallState)) |
| if retry_state_passed_as_non_kwarg: |
| retry_state = previous_attempt_number |
| else: |
| warn_about_dunder_non_retry_state_deprecation(fn, stacklevel=2) |
| retry_state = make_retry_state( |
| previous_attempt_number=previous_attempt_number, |
| delay_since_first_attempt=delay_since_first_attempt) |
| return fn(self, retry_state=retry_state) |
| return new_fn |
| |
| |
| def stop_func_accept_retry_state(stop_func): |
| """Wrap "stop" function to accept "retry_state" parameter.""" |
| if not six.callable(stop_func): |
| return stop_func |
| |
| if func_takes_retry_state(stop_func): |
| return stop_func |
| |
| @_utils.wraps(stop_func) |
| def wrapped_stop_func(retry_state): |
| warn_about_non_retry_state_deprecation( |
| 'stop', stop_func, stacklevel=4) |
| return stop_func( |
| retry_state.attempt_number, |
| retry_state.seconds_since_start, |
| ) |
| return wrapped_stop_func |
| |
| |
| def wait_dunder_call_accept_old_params(fn): |
| """Decorate cls.__call__ method to accept old "wait" signature.""" |
| @_utils.wraps(fn) |
| def new_fn(self, |
| previous_attempt_number=_unset, |
| delay_since_first_attempt=_unset, |
| last_result=None, |
| retry_state=None): |
| if retry_state is None: |
| from tenacity import RetryCallState |
| retry_state_passed_as_non_kwarg = ( |
| previous_attempt_number is not _unset and |
| isinstance(previous_attempt_number, RetryCallState)) |
| if retry_state_passed_as_non_kwarg: |
| retry_state = previous_attempt_number |
| else: |
| warn_about_dunder_non_retry_state_deprecation(fn, stacklevel=2) |
| retry_state = make_retry_state( |
| previous_attempt_number=previous_attempt_number, |
| delay_since_first_attempt=delay_since_first_attempt, |
| last_result=last_result) |
| return fn(self, retry_state=retry_state) |
| return new_fn |
| |
| |
| def wait_func_accept_retry_state(wait_func): |
| """Wrap wait function to accept "retry_state" parameter.""" |
| if not six.callable(wait_func): |
| return wait_func |
| |
| if func_takes_retry_state(wait_func): |
| return wait_func |
| |
| if func_takes_last_result(wait_func): |
| @_utils.wraps(wait_func) |
| def wrapped_wait_func(retry_state): |
| warn_about_non_retry_state_deprecation( |
| 'wait', wait_func, stacklevel=4) |
| return wait_func( |
| retry_state.attempt_number, |
| retry_state.seconds_since_start, |
| last_result=retry_state.outcome, |
| ) |
| else: |
| @_utils.wraps(wait_func) |
| def wrapped_wait_func(retry_state): |
| warn_about_non_retry_state_deprecation( |
| 'wait', wait_func, stacklevel=4) |
| return wait_func( |
| retry_state.attempt_number, |
| retry_state.seconds_since_start, |
| ) |
| return wrapped_wait_func |
| |
| |
| def retry_dunder_call_accept_old_params(fn): |
| """Decorate cls.__call__ method to accept old "retry" signature.""" |
| @_utils.wraps(fn) |
| def new_fn(self, attempt=_unset, retry_state=None): |
| if retry_state is None: |
| from tenacity import RetryCallState |
| if attempt is _unset: |
| raise _make_unset_exception('retry', attempt=attempt) |
| retry_state_passed_as_non_kwarg = ( |
| attempt is not _unset and |
| isinstance(attempt, RetryCallState)) |
| if retry_state_passed_as_non_kwarg: |
| retry_state = attempt |
| else: |
| warn_about_dunder_non_retry_state_deprecation(fn, stacklevel=2) |
| retry_state = RetryCallState(None, None, (), {}) |
| retry_state.outcome = attempt |
| return fn(self, retry_state=retry_state) |
| return new_fn |
| |
| |
| def retry_func_accept_retry_state(retry_func): |
| """Wrap "retry" function to accept "retry_state" parameter.""" |
| if not six.callable(retry_func): |
| return retry_func |
| |
| if func_takes_retry_state(retry_func): |
| return retry_func |
| |
| @_utils.wraps(retry_func) |
| def wrapped_retry_func(retry_state): |
| warn_about_non_retry_state_deprecation( |
| 'retry', retry_func, stacklevel=4) |
| return retry_func(retry_state.outcome) |
| return wrapped_retry_func |
| |
| |
| def before_func_accept_retry_state(fn): |
| """Wrap "before" function to accept "retry_state".""" |
| if not six.callable(fn): |
| return fn |
| |
| if func_takes_retry_state(fn): |
| return fn |
| |
| @_utils.wraps(fn) |
| def wrapped_before_func(retry_state): |
| # func, trial_number, trial_time_taken |
| warn_about_non_retry_state_deprecation('before', fn, stacklevel=4) |
| return fn( |
| retry_state.fn, |
| retry_state.attempt_number, |
| ) |
| return wrapped_before_func |
| |
| |
| def after_func_accept_retry_state(fn): |
| """Wrap "after" function to accept "retry_state".""" |
| if not six.callable(fn): |
| return fn |
| |
| if func_takes_retry_state(fn): |
| return fn |
| |
| @_utils.wraps(fn) |
| def wrapped_after_sleep_func(retry_state): |
| # func, trial_number, trial_time_taken |
| warn_about_non_retry_state_deprecation('after', fn, stacklevel=4) |
| return fn( |
| retry_state.fn, |
| retry_state.attempt_number, |
| retry_state.seconds_since_start) |
| return wrapped_after_sleep_func |
| |
| |
| def before_sleep_func_accept_retry_state(fn): |
| """Wrap "before_sleep" function to accept "retry_state".""" |
| if not six.callable(fn): |
| return fn |
| |
| if func_takes_retry_state(fn): |
| return fn |
| |
| @_utils.wraps(fn) |
| def wrapped_before_sleep_func(retry_state): |
| # retry_object, sleep, last_result |
| warn_about_non_retry_state_deprecation( |
| 'before_sleep', fn, stacklevel=4) |
| return fn( |
| retry_state.retry_object, |
| sleep=getattr(retry_state.next_action, 'sleep'), |
| last_result=retry_state.outcome) |
| return wrapped_before_sleep_func |
| |
| |
| def retry_error_callback_accept_retry_state(fn): |
| if not six.callable(fn): |
| return fn |
| |
| if func_takes_retry_state(fn): |
| return fn |
| |
| @_utils.wraps(fn) |
| def wrapped_retry_error_callback(retry_state): |
| warn_about_non_retry_state_deprecation( |
| 'retry_error_callback', fn, stacklevel=4) |
| return fn(retry_state.outcome) |
| return wrapped_retry_error_callback |
| |
| |
| def get_exc_info_from_future(future): |
| """ |
| Get an exc_info value from a Future. |
| |
| Given a a Future instance, retrieve an exc_info value suitable for passing |
| in as the exc_info parameter to logging.Logger.log() and related methods. |
| |
| On Python 2, this will be a (type, value, traceback) triple. |
| On Python 3, this will be an exception instance (with embedded traceback). |
| |
| If there was no exception, None is returned on both versions of Python. |
| """ |
| if six.PY3: |
| return future.exception() |
| else: |
| ex, tb = future.exception_info() |
| if ex is None: |
| return None |
| return type(ex), ex, tb |