From 8ff6340a37abc7856853aedf924c979daa0b41aa Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Fri, 4 Nov 2022 22:32:07 +0100 Subject: [PATCH] Move exceptions (#1246) Move exceptions into their own python module SUMMARY Move base/core exceptions into their own python module ISSUE TYPE Feature Pull Request COMPONENT NAME module_utils ADDITIONAL INFORMATION Reviewed-by: Alina Buzachis --- changelogs/fragments/20221104-exceptions.yml | 2 + plugins/module_utils/core.py | 7 +- plugins/module_utils/exceptions.py | 38 +++++++ .../exceptions/test_exceptions.py | 101 ++++++++++++++++++ 4 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 changelogs/fragments/20221104-exceptions.yml create mode 100644 plugins/module_utils/exceptions.py create mode 100644 tests/unit/module_utils/exceptions/test_exceptions.py diff --git a/changelogs/fragments/20221104-exceptions.yml b/changelogs/fragments/20221104-exceptions.yml new file mode 100644 index 00000000000..5d132303371 --- /dev/null +++ b/changelogs/fragments/20221104-exceptions.yml @@ -0,0 +1,2 @@ +minor_changes: +- module_utils - move exceptions into dedicated python module (https://github.com/ansible-collections/amazon.aws/pull/1246). diff --git a/plugins/module_utils/core.py b/plugins/module_utils/core.py index bfd7fe10187..9fd4d420f3b 100644 --- a/plugins/module_utils/core.py +++ b/plugins/module_utils/core.py @@ -63,6 +63,9 @@ from .botocore import get_boto3_client_method_parameters # pylint: disable=unused-import from .botocore import normalize_boto3_result # pylint: disable=unused-import +# Used to live here, moved into ansible_collections.amazon.aws.plugins.module_utils.exceptions +from .exceptions import AnsibleAWSError # pylint: disable=unused-import + # Used to live here, moved into ansible_collections.amazon.aws.plugins.module_utils.modules from .modules import AnsibleAWSModule # pylint: disable=unused-import @@ -71,7 +74,3 @@ # We will also export HAS_BOTO3 so end user modules can use it. __all__ = ('AnsibleAWSModule', 'HAS_BOTO3', 'is_boto3_error_code', 'is_boto3_error_message') - - -class AnsibleAWSError(Exception): - pass diff --git a/plugins/module_utils/exceptions.py b/plugins/module_utils/exceptions.py new file mode 100644 index 00000000000..e8a9de2ea72 --- /dev/null +++ b/plugins/module_utils/exceptions.py @@ -0,0 +1,38 @@ +# (c) 2022 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils._text import to_native + + +class AnsibleAWSError(Exception): + + def __str__(self): + if self.exception and self.message: + return "{0}: {1}".format(self.message, to_native(self.exception)) + + return super().__str__() + + def __init__(self, message=None, exception=None, **kwargs): + if not message and not exception: + super().__init__() + elif not message: + super().__init__(exception) + else: + super().__init__(message) + + self.exception = exception + self.message = message + + # In places where passing more information to module.fail_json would be helpful + # store the extra info. Other plugin types have to raise the correct exception + # such as AnsibleLookupError, so can't easily consume this. + self.kwargs = kwargs or {} + + +class AnsibleBotocoreError(AnsibleAWSError): + pass diff --git a/tests/unit/module_utils/exceptions/test_exceptions.py b/tests/unit/module_utils/exceptions/test_exceptions.py new file mode 100644 index 00000000000..e2dc6510947 --- /dev/null +++ b/tests/unit/module_utils/exceptions/test_exceptions.py @@ -0,0 +1,101 @@ +# (c) 2022 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest +from unittest.mock import sentinel + +import ansible_collections.amazon.aws.plugins.module_utils.exceptions as aws_exceptions + + +@pytest.fixture +def utils_exceptions(): + return aws_exceptions + + +def test_with_kwargs(utils_exceptions): + nested_exception = Exception(sentinel.EXCEPTION) + with pytest.raises(utils_exceptions.AnsibleAWSError) as e: + raise utils_exceptions.AnsibleAWSError(kw1=sentinel.KW1, kw2=sentinel.KW2) + assert str(e.value) == '' + assert e.value.exception is None + assert e.value.message is None + assert e.value.kwargs == dict(kw1=sentinel.KW1, kw2=sentinel.KW2) + + with pytest.raises(utils_exceptions.AnsibleAWSError) as e: + raise utils_exceptions.AnsibleAWSError(message=sentinel.MESSAGE, exception=nested_exception, kw1=sentinel.KW1, kw2=sentinel.KW2) + assert str(e.value) == 'sentinel.MESSAGE: sentinel.EXCEPTION' + assert e.value.exception is nested_exception + assert e.value.message is sentinel.MESSAGE + assert e.value.kwargs == dict(kw1=sentinel.KW1, kw2=sentinel.KW2) + + +def test_with_both(utils_exceptions): + nested_exception = Exception(sentinel.EXCEPTION) + + with pytest.raises(utils_exceptions.AnsibleAWSError) as e: + raise utils_exceptions.AnsibleAWSError(message=sentinel.MESSAGE, exception=nested_exception) + assert str(e.value) == 'sentinel.MESSAGE: sentinel.EXCEPTION' + assert e.value.exception is nested_exception + assert e.value.message is sentinel.MESSAGE + assert e.value.kwargs == {} + + with pytest.raises(utils_exceptions.AnsibleAWSError) as e: + raise utils_exceptions.AnsibleAWSError(sentinel.MESSAGE, exception=nested_exception) + assert str(e.value) == 'sentinel.MESSAGE: sentinel.EXCEPTION' + assert e.value.exception is nested_exception + assert e.value.message is sentinel.MESSAGE + assert e.value.kwargs == {} + + +def test_with_exception(utils_exceptions): + nested_exception = Exception(sentinel.EXCEPTION) + + with pytest.raises(utils_exceptions.AnsibleAWSError) as e: + raise utils_exceptions.AnsibleAWSError(exception=nested_exception) + assert str(e.value) == 'sentinel.EXCEPTION' + assert e.value.exception is nested_exception + assert e.value.message is None + assert e.value.kwargs == {} + + +def test_with_message(utils_exceptions): + with pytest.raises(utils_exceptions.AnsibleAWSError) as e: + raise utils_exceptions.AnsibleAWSError(message=sentinel.MESSAGE) + assert str(e.value) == 'sentinel.MESSAGE' + assert e.value.exception is None + assert e.value.message is sentinel.MESSAGE + assert e.value.kwargs == {} + + with pytest.raises(utils_exceptions.AnsibleAWSError) as e: + raise utils_exceptions.AnsibleAWSError(sentinel.MESSAGE) + assert str(e.value) == 'sentinel.MESSAGE' + assert e.value.exception is None + assert e.value.message is sentinel.MESSAGE + assert e.value.kwargs == {} + + +def test_empty(utils_exceptions): + with pytest.raises(utils_exceptions.AnsibleAWSError) as e: + raise utils_exceptions.AnsibleAWSError() + assert str(e.value) == '' + assert e.value.exception is None + assert e.value.message is None + assert e.value.kwargs == {} + + +def test_inheritence(utils_exceptions): + aws_exception = utils_exceptions.AnsibleAWSError() + + assert isinstance(aws_exception, Exception) + assert isinstance(aws_exception, utils_exceptions.AnsibleAWSError) + + botocore_exception = utils_exceptions.AnsibleBotocoreError() + + assert isinstance(botocore_exception, Exception) + assert isinstance(botocore_exception, utils_exceptions.AnsibleAWSError) + assert isinstance(botocore_exception, utils_exceptions.AnsibleBotocoreError)