Skip to content
Snippets Groups Projects
Commit b0112aba authored by Piotr Maślanka's avatar Piotr Maślanka
Browse files

added `call_on_failure` and `call_on_success` to `retry`

parent 716410cf
No related branches found
No related tags found
No related merge requests found
# v2.14.37
* added `call_on_failure` and `call_on_success` to `retry`
__version__ = '2.14.37a1'
__version__ = '2.14.37'
......@@ -8,7 +8,9 @@ from satella.coding.typing import ExceptionClassType
def retry(times: tp.Optional[int] = None,
exc_classes: tp.Union[ExceptionClassType, tp.Tuple[ExceptionClassType, ...]] = Exception,
on_failure: tp.Callable[[Exception], None] = lambda e: None,
swallow_exception: bool = True):
swallow_exception: bool = True,
call_on_failure: tp.Optional[tp.Callable[[Exception], None]] = None,
call_on_success: tp.Optional[tp.Callable[[int], None]] = None):
"""
A decorator retrying given operation, failing it when an exception shows up.
......@@ -34,6 +36,10 @@ def retry(times: tp.Optional[int] = None,
with a single argument, exception instance that was raised last. That exception will
be swallowed, unless swallow_exception is set to False
:param swallow_exception: the last exception will be swallowed, unless this is set to False
:param call_on_failure: a callable that will be called upon failing to do this, with an
exception as it's sole argument. It's result will be discarded.
:param call_on_success: a callable that will be called with a single argument: the number
of retries that it took to finish the job. It's result will be discarded.
:return: function result
"""
def outer(fun):
......@@ -43,14 +49,19 @@ def retry(times: tp.Optional[int] = None,
iterator = itertools.count()
else:
iterator = range(times)
for _ in iterator:
for i in iterator:
try:
return fun(*args, **kwargs)
y = fun(*args, **kwargs)
if call_on_success is not None:
call_on_success(i)
return y
except exc_classes as e:
f = e
continue
else:
on_failure(f)
if call_on_failure is not None:
call_on_failure(f)
if not swallow_exception:
raise f
return inner
......
......@@ -77,17 +77,30 @@ class TestDecorators(unittest.TestCase):
self.assertEqual(test(), [2, 3, None, 4])
def test_retry(self):
a = {'test': 0, 'limit': 2}
a = {'test': 0, 'limit': 2, 'true': False, 'false': False}
@retry(3, ValueError, swallow_exception=False)
def on_failure(e):
nonlocal a
a['true'] = True
def on_success(retries):
nonlocal a
a['false'] = True
@retry(3, ValueError, swallow_exception=False, call_on_failure=on_failure,
call_on_success=on_success)
def do_op():
a['test'] += 1
if a['test'] < a['limit']:
raise ValueError()
do_op()
self.assertTrue(a['false'])
a['limit'] = 10
a['false'] = False
self.assertRaises(ValueError, do_op)
self.assertTrue(a['true'])
self.assertFalse(a['false'])
def test_replace_argument_if(self):
@replace_argument_if('y', x.int(), str)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment