Skip to content

Commit

Permalink
Add last_response attr to WaiterError
Browse files Browse the repository at this point in the history
The rationale behind just adding a last_response instead of exposing some sort
of error_code and error_message is that a WaiterError can also be thrown in
non-error states (transitioning to a failure state, hitting max-attempts), and
while we can always guarantee a last response, we can't necessarily guarantee
that response is a "error" response.

Closes #957
  • Loading branch information
jamesls committed Aug 29, 2016
1 parent cb8e16f commit 3d620ad
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 10 deletions.
4 changes: 4 additions & 0 deletions botocore/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ class WaiterError(BotoCoreError):
"""Waiter failed to reach desired state."""
fmt = 'Waiter {name} failed: {reason}'

def __init__(self, name, reason, last_response):
super(WaiterError, self).__init__(name=name, reason=reason)
self.last_response = last_response


class IncompleteReadError(BotoCoreError):
"""HTTP response did not return expected number of bytes."""
Expand Down
23 changes: 15 additions & 8 deletions botocore/waiter.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,20 +303,27 @@ def wait(self, **kwargs):
# transition to the failure state if an error
# response was received.
if 'Error' in response:
# This was a ClientError that was not handled in an
# acceptor. Raise a ClientError again
raise ClientError(
error_response=response,
operation_name=self.config.operation)
# Transition to a failure state, which we
# can just handle here by raising an exception.
raise WaiterError(
name=self.name,
reason=response['Error'].get('Message', 'Unknown'),
last_response=response
)
if current_state == 'success':
logger.debug("Waiting complete, waiter matched the "
"success state.")
return
if current_state == 'failure':
raise WaiterError(
name=self.name,
reason='Waiter encountered a terminal failure state')
reason='Waiter encountered a terminal failure state',
last_response=response,
)
if num_attempts >= max_attempts:
raise WaiterError(name=self.name,
reason='Max attempts exceeded')
raise WaiterError(
name=self.name,
reason='Max attempts exceeded',
last_response=response
)
time.sleep(sleep_amount)
15 changes: 13 additions & 2 deletions tests/unit/test_waiters.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,19 @@ def test_unspecified_errors_stops_waiter(self):
for_operation=operation_method
)
waiter = Waiter('MyWaiter', config, operation_method)
with self.assertRaises(ClientError):
with self.assertRaises(WaiterError):
waiter.wait()

def test_last_response_available_on_waiter_error(self):
last_response = {'Error': {'Code': 'UnknownError', 'Message': 'bad error'}}
config = self.create_waiter_config()
operation_method = mock.Mock()
self.client_responses_are(last_response,
for_operation=operation_method)
waiter = Waiter('MyWaiter', config, operation_method)
with self.assertRaises(WaiterError) as e:
waiter.wait()
self.assertEqual(e.exception.last_response, last_response)

def test_unspecified_errors_propagate_error_code(self):
# If a waiter receives an error response, then the
Expand All @@ -378,7 +389,7 @@ def test_unspecified_errors_propagate_error_code(self):
)
waiter = Waiter('MyWaiter', config, operation_method)

with self.assertRaisesRegexp(ClientError, error_message):
with self.assertRaisesRegexp(WaiterError, error_message):
waiter.wait()

def test_waiter_transitions_to_failure_state(self):
Expand Down

0 comments on commit 3d620ad

Please sign in to comment.