diff --git a/botocore/waiter.py b/botocore/waiter.py index b49c9a19b0..fbc425ecf9 100644 --- a/botocore/waiter.py +++ b/botocore/waiter.py @@ -96,10 +96,28 @@ def __init__(self, operation_object, endpoint): self._endpoint = endpoint def __call__(self, **kwargs): - http, parsed = self._operation_object.call( - self._endpoint, **kwargs) + try: + http, parsed = self._operation_object.call( + self._endpoint, **kwargs) + except Exception as e: + # In theory, a handler can raise an type of exception. + # We're going to make a best effort attempt to handle + # the ClientError attributes, but not require that + # the exception is an instance of ClientError. + if self._looks_like_client_error(e): + return { + 'Error': { + 'Code': e.error_code, + 'Message': e.error_message, + } + } + else: + raise return parsed + def _looks_like_client_error(self, e): + return hasattr(e, 'error_code') and hasattr(e, 'error_message') + class WaiterModel(object): SUPPORTED_VERSION = 2 @@ -317,6 +335,8 @@ def wait(self, **kwargs): raise WaiterError(name=self.name, reason='Unexpected error encountered.') if current_state == 'success': + logger.debug("Waiting complete, waiter matched the " + "success state.") return if current_state == 'failure': raise WaiterError( diff --git a/tests/unit/test_waiters.py b/tests/unit/test_waiters.py index a1a74468dd..4d11a33f04 100644 --- a/tests/unit/test_waiters.py +++ b/tests/unit/test_waiters.py @@ -508,6 +508,28 @@ def test_legacy_op_method_makes_call(self): operation_object.call.assert_called_with( endpoint, Foo='a', Bar='b') + def test_legacy_method_handles_exceptions(self): + operation_object = mock.Mock() + exception = Exception() + exception.error_message = 'Foo' + exception.error_code = 'MyCode' + operation_object.call.side_effect = exception + endpoint = mock.Mock() + op = LegacyOperationMethod(operation_object, endpoint) + response = op(Foo='a', Bar='b') + self.assertEqual(response, + {'Error': {'Code': 'MyCode', 'Message': 'Foo'}}) + + def test_legacy_method_with_unknown_exception(self): + operation_object = mock.Mock() + # A generic exception missing the error_message and error_code + # attrs will just be reraised. + operation_object.call.side_effect = ValueError + endpoint = mock.Mock() + op = LegacyOperationMethod(operation_object, endpoint) + with self.assertRaises(ValueError): + op(Foo='a', Bar='b') + class ServiceWaiterFunctionalTest(BaseEnvVar): """