3147: Add retry_method to the Python SDK.
authorBrett Smith <brett@curoverse.com>
Fri, 12 Sep 2014 12:49:21 +0000 (08:49 -0400)
committerBrett Smith <brett@curoverse.com>
Thu, 18 Sep 2014 19:51:47 +0000 (15:51 -0400)
This gives us a way to make retry support flexible and consistent
across SDK classes.  Any class that has retryable operations should
take a num_retries argument at initialization.  Then, the specific
methods that implement those operations should also take a num_retries
argument, which will fall back to the instance's setting by default.
This lets SDK users express their retry preferences wherever it's most
convenient for them.

sdk/python/arvados/retry.py
sdk/python/tests/test_retry.py

index 5dc31aefa74919e165ba0b725c53debb0d5fbb4b..3d2fc48e31fafbe93e1cddfcd8703dbf982bd2cd 100644 (file)
@@ -1,5 +1,7 @@
 #!/usr/bin/env python
 
+import functools
+import inspect
 import time
 
 from collections import deque
@@ -138,3 +140,19 @@ def check_http_response_success(result):
         return False
     else:
         return None  # Get well soon, server.
+
+def retry_method(orig_func):
+    """Provide a default value for a method's num_retries argument.
+
+    This is a decorator for instance and class methods that accept a
+    num_retries argument, with a None default.  When the method is called
+    without a value for num_retries, it will be set from the underlying
+    instance or class' num_retries attribute.
+    """
+    @functools.wraps(orig_func)
+    def num_retries_setter(self, *args, **kwargs):
+        arg_vals = inspect.getcallargs(orig_func, self, *args, **kwargs)
+        if arg_vals['num_retries'] is None:
+            kwargs['num_retries'] = self.num_retries
+        return orig_func(self, *args, **kwargs)
+    return num_retries_setter
index ed0a406e8523536c82ea34201b2fd657f345a365..8c3916b009db2c2f076d64720f6ea135084a357f 100644 (file)
@@ -194,5 +194,32 @@ class CheckHTTPResponseSuccessTestCase(unittest.TestCase):
         self.check_is(None, 0, 99, 600, -200)
 
 
+class RetryMethodTestCase(unittest.TestCase):
+    class Tester(object):
+        def __init__(self):
+            self.num_retries = 1
+
+        @arv_retry.retry_method
+        def check(self, a, num_retries=None, z=0):
+            return (a, num_retries, z)
+
+
+    def test_positional_arg_passed(self):
+        self.assertEqual((3, 2, 0), self.Tester().check(3, 2))
+
+    def test_keyword_arg_passed(self):
+        self.assertEqual((4, 3, 0), self.Tester().check(num_retries=3, a=4))
+
+    def test_not_specified(self):
+        self.assertEqual((0, 1, 0), self.Tester().check(0))
+
+    def test_not_specified_with_other_kwargs(self):
+        self.assertEqual((1, 1, 1), self.Tester().check(1, z=1))
+
+    def test_bad_call(self):
+        with self.assertRaises(TypeError):
+            self.Tester().check(num_retries=2)
+
+
 if __name__ == '__main__':
     unittest.main()