Repository: jd/tenacity

Issue:
retry doesn't pass retry_state to method
When using a bound method as a 'before' function (possibly others) that takes retry_state causes an error:


```    @_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,
        )
E       TypeError: _retry_refresh_token() takes 2 positional arguments but 3 were given
```

2 positional arguments above are self, retry_state

calling ```inspect.isfunction()``` on a bound method returns false, but then the code assumes it's a callable object. In result the compatibility wrapper is used which tries to call the method with parameters for the old-style before function.

https://github.com/jd/tenacity/blob/5f3d822c22ef435a57081a314bcb61b1c9f7b326/tenacity/compat.py#L29


Discussion:

Patch:
diff --git a/tenacity/compat.py b/tenacity/compat.py
index 2d3e301..0451b8c 100644
--- a/tenacity/compat.py
+++ b/tenacity/compat.py
@@ -25,9 +25,10 @@ def warn_about_dunder_non_retry_state_deprecation(fn, stacklevel):
 
 def func_takes_retry_state(func):
     if not six.callable(func):
+        raise Exception(func)
         return False
-    if not inspect.isfunction(func):
-        # func is a callable object rather than a function
+    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
@@ -86,7 +87,7 @@ def func_takes_last_result(waiter):
     """
     if not six.callable(waiter):
         return False
-    if not inspect.isfunction(waiter):
+    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)


Test patch:
diff --git a/tenacity/tests/test_tenacity.py b/tenacity/tests/test_tenacity.py
index d3090fa..af96f3a 100644
--- a/tenacity/tests/test_tenacity.py
+++ b/tenacity/tests/test_tenacity.py
@@ -1107,6 +1107,25 @@ class TestBeforeAfterAttempts(unittest.TestCase):
             _test_before_sleep()
         self.assertEqual(_before_sleep.attempt_number, 2)
 
+    def _before_sleep(self, retry_state):
+        self.slept += 1
+
+    def test_before_sleep_backward_compat_method(self):
+        self.slept = 0
+
+        @retry(wait=tenacity.wait_fixed(0.01),
+               stop=tenacity.stop_after_attempt(3),
+               before_sleep=self._before_sleep)
+        def _test_before_sleep():
+            raise Exception("testing before_sleep_attempts handler")
+
+        try:
+            _test_before_sleep()
+        except tenacity.RetryError:
+            pass
+
+        self.assertEqual(self.slept, 2)
+
     def test_before_sleep_log_raises(self):
         thing = NoIOErrorAfterCount(2)
         logger = logging.getLogger(self.id())