Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9c4144b

Browse filesBrowse files
authored
Add 'on_error' argument to 'retry.retry_target' and 'Retry'. (#3891)
Permit application code to reset / fix-up state before retrying.
1 parent 29654a6 commit 9c4144b
Copy full SHA for 9c4144b

File tree

Expand file treeCollapse file tree

2 files changed

+51
-6
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

+51
-6
lines changed

‎core/google/api/core/retry.py

Copy file name to clipboardExpand all lines: core/google/api/core/retry.py
+13-3Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def exponential_sleep_generator(
135135
delay = delay * multiplier
136136

137137

138-
def retry_target(target, predicate, sleep_generator, deadline):
138+
def retry_target(target, predicate, sleep_generator, deadline, on_error=None):
139139
"""Call a function and retry if it fails.
140140
141141
This is the lowest-level retry helper. Generally, you'll use the
@@ -150,6 +150,9 @@ def retry_target(target, predicate, sleep_generator, deadline):
150150
sleep_generator (Iterator[float]): An infinite iterator that determines
151151
how long to sleep between retries.
152152
deadline (float): How long to keep retrying the target.
153+
on_error (Callable): A function to call while processing a retryable
154+
exception. Any error raised by this function will *not* be
155+
caught.
153156
154157
Returns:
155158
Any: the return value of the target function.
@@ -177,6 +180,8 @@ def retry_target(target, predicate, sleep_generator, deadline):
177180
if not predicate(exc):
178181
raise
179182
last_exc = exc
183+
if on_error is not None:
184+
on_error(exc)
180185

181186
now = datetime_helpers.utcnow()
182187
if deadline_datetime is not None and deadline_datetime < now:
@@ -226,11 +231,14 @@ def __init__(
226231
self._maximum = maximum
227232
self._deadline = deadline
228233

229-
def __call__(self, func):
234+
def __call__(self, func, on_error=None):
230235
"""Wrap a callable with retry behavior.
231236
232237
Args:
233238
func (Callable): The callable to add retry behavior to.
239+
on_error (Callable): A function to call while processing a
240+
retryable exception. Any error raised by this function will
241+
*not* be caught.
234242
235243
Returns:
236244
Callable: A callable that will invoke ``func`` with retry
@@ -246,7 +254,9 @@ def retry_wrapped_func(*args, **kwargs):
246254
target,
247255
self._predicate,
248256
sleep_generator,
249-
self._deadline)
257+
self._deadline,
258+
on_error=on_error,
259+
)
250260

251261
return retry_wrapped_func
252262

‎core/tests/unit/api_core/test_retry.py

Copy file name to clipboardExpand all lines: core/tests/unit/api_core/test_retry.py
+38-3Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,34 @@ def target():
7777
sleep.assert_has_calls([mock.call(0), mock.call(1)])
7878

7979

80+
@mock.patch('time.sleep', autospec=True)
81+
@mock.patch(
82+
'google.api.core.helpers.datetime_helpers.utcnow',
83+
return_value=datetime.datetime.min,
84+
autospec=True)
85+
def test_retry_target_w_on_error(utcnow, sleep):
86+
predicate = retry.if_exception_type(ValueError)
87+
call_count = {'target': 0}
88+
to_raise = ValueError()
89+
90+
def target():
91+
call_count['target'] += 1
92+
if call_count['target'] < 3:
93+
raise to_raise
94+
return 42
95+
96+
on_error = mock.Mock()
97+
98+
result = retry.retry_target(
99+
target, predicate, range(10), None, on_error=on_error)
100+
101+
assert result == 42
102+
assert call_count['target'] == 3
103+
104+
on_error.assert_has_calls([mock.call(to_raise), mock.call(to_raise)])
105+
sleep.assert_has_calls([mock.call(0), mock.call(1)])
106+
107+
80108
@mock.patch('time.sleep', autospec=True)
81109
@mock.patch(
82110
'google.api.core.helpers.datetime_helpers.utcnow',
@@ -139,7 +167,8 @@ def test_constructor_options(self):
139167
initial=1,
140168
maximum=2,
141169
multiplier=3,
142-
deadline=4)
170+
deadline=4,
171+
)
143172
assert retry_._predicate == mock.sentinel.predicate
144173
assert retry_._initial == 1
145174
assert retry_._maximum == 2
@@ -204,12 +233,17 @@ def test___call___and_execute_success(self, sleep):
204233
'random.uniform', autospec=True, side_effect=lambda m, n: n/2.0)
205234
@mock.patch('time.sleep', autospec=True)
206235
def test___call___and_execute_retry(self, sleep, uniform):
207-
retry_ = retry.Retry(predicate=retry.if_exception_type(ValueError))
236+
237+
on_error = mock.Mock(spec=['__call__'], side_effect=[None])
238+
retry_ = retry.Retry(
239+
predicate=retry.if_exception_type(ValueError),
240+
)
241+
208242
target = mock.Mock(spec=['__call__'], side_effect=[ValueError(), 42])
209243
# __name__ is needed by functools.partial.
210244
target.__name__ = 'target'
211245

212-
decorated = retry_(target)
246+
decorated = retry_(target, on_error=on_error)
213247
target.assert_not_called()
214248

215249
result = decorated('meep')
@@ -218,3 +252,4 @@ def test___call___and_execute_retry(self, sleep, uniform):
218252
assert target.call_count == 2
219253
target.assert_has_calls([mock.call('meep'), mock.call('meep')])
220254
sleep.assert_called_once_with(retry_._initial)
255+
assert on_error.call_count == 1

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.