From 7dd8a81f13afae4085e0666ff027c9ca4e91cc6f Mon Sep 17 00:00:00 2001 From: Andrew Asseily Date: Tue, 17 Mar 2026 14:24:23 -0400 Subject: [PATCH 1/5] Filter unmodeled fields from error output --- awscli/botocore/client.py | 7 +++ awscli/errorhandler.py | 42 +++++++++++++---- tests/unit/test_structured_error.py | 73 +++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 9 deletions(-) diff --git a/awscli/botocore/client.py b/awscli/botocore/client.py index b04b3f5c70c5..dc645df85dbc 100644 --- a/awscli/botocore/client.py +++ b/awscli/botocore/client.py @@ -903,6 +903,13 @@ def _make_api_call(self, operation_name, api_params): if http.status_code >= 300: error_code = parsed_response.get("Error", {}).get("Code") + error_shape = self._service_model.shape_for_error_code( + error_code + ) + modeled_fields = {'Code', 'Message'} + if error_shape: + modeled_fields |= set(error_shape.members.keys()) + parsed_response['ModeledErrorFields'] = modeled_fields error_class = self.exceptions.from_code(error_code) raise error_class(parsed_response, operation_name) else: diff --git a/awscli/errorhandler.py b/awscli/errorhandler.py index 5453d1194deb..e23c2e07da5e 100644 --- a/awscli/errorhandler.py +++ b/awscli/errorhandler.py @@ -49,8 +49,10 @@ class EnhancedErrorFormatter: - def format_error(self, error_info, stream): - additional_fields = self._get_additional_fields(error_info) + def format_error(self, error_info, stream, modeled_fields=None): + additional_fields = self._get_additional_fields( + error_info, modeled_fields + ) if not additional_fields: return @@ -94,9 +96,19 @@ def _format_inline(self, value): return f'{{{items}}}' return str(value) - def _get_additional_fields(self, error_info): - standard_keys = {'Code', 'Message'} - return {k: v for k, v in error_info.items() if k not in standard_keys} + def _get_additional_fields(self, error_info, modeled_fields=None): + standard_keys = {'code', 'message'} + if modeled_fields is not None: + return { + k: v + for k, v in error_info.items() + if k.lower() not in standard_keys and k in modeled_fields + } + return { + k: v + for k, v in error_info.items() + if k.lower() not in standard_keys + } def construct_entry_point_handlers_chain(): @@ -200,6 +212,7 @@ def _display_structured_error( ): try: error_format = self._resolve_error_format(parsed_globals) + modeled_fields = error_info.pop('_modeled_fields', None) if error_format == 'legacy': return False @@ -213,7 +226,9 @@ def _display_structured_error( if error_format == 'enhanced': write_error(stderr, formatted_message) - EnhancedErrorFormatter().format_error(error_info, stderr) + EnhancedErrorFormatter().format_error( + error_info, stderr, modeled_fields + ) return True color = getattr(parsed_globals, 'color', 'auto') @@ -275,7 +290,10 @@ def _get_formatted_message(self, error_info, exception): def _extract_error_info(self, exception): error_response = self._extract_error_response(exception) if error_response and 'Error' in error_response: - return error_response['Error'] + error_info = error_response['Error'] + modeled_fields = error_response.get('ModeledErrorFields') + error_info['_modeled_fields'] = modeled_fields + return error_info return None @staticmethod @@ -291,12 +309,18 @@ def _extract_error_response(exception): # not nested under an Error key. Botocore preserves this structure. # Include these fields to provide complete error information. # Exclude response metadata and avoid duplicates. - excluded_keys = {'Error', 'ResponseMetadata', 'Code', 'Message'} + excluded_keys = {'Error', 'ResponseMetadata', 'Code', 'Message', + 'ModeledErrorFields'} for key, value in exception.response.items(): if key not in excluded_keys and key not in error_dict: error_dict[key] = value - return {'Error': error_dict} + result = {'Error': error_dict} + if 'ModeledErrorFields' in exception.response: + result['ModeledErrorFields'] = exception.response[ + 'ModeledErrorFields' + ] + return result return None diff --git a/tests/unit/test_structured_error.py b/tests/unit/test_structured_error.py index edd1241c4674..9bc7a5464950 100644 --- a/tests/unit/test_structured_error.py +++ b/tests/unit/test_structured_error.py @@ -150,6 +150,47 @@ def test_error_format_case_insensitive(self): ) assert stderr.getvalue() == expected + def test_modeled_fields_filters_unmodeled_from_display(self): + error_response = { + 'Error': { + 'Code': 'ExpiredToken', + 'Message': 'Token expired', + 'Token-0': 'AQoDYXdzEJr...sensitive...', + }, + 'ResponseMetadata': {'RequestId': '123'}, + 'ModeledErrorFields': {'Code', 'Message'}, + } + client_error = ClientError(error_response, 'ListBuckets') + + stdout = io.StringIO() + stderr = io.StringIO() + + rc = self.handler.handle_exception(client_error, stdout, stderr) + + assert rc == CLIENT_ERROR_RC + assert 'Token-0' not in stderr.getvalue() + assert 'sensitive' not in stderr.getvalue() + + def test_modeled_fields_not_leaked_in_json_format(self): + error_response = { + 'Error': { + 'Code': 'ExpiredToken', + 'Message': 'Token expired', + }, + 'ResponseMetadata': {'RequestId': '123'}, + 'ModeledErrorFields': {'Code', 'Message'}, + } + client_error = ClientError(error_response, 'ListBuckets') + + self.session.session_vars['cli_error_format'] = 'json' + + stdout = io.StringIO() + stderr = io.StringIO() + + self.handler.handle_exception(client_error, stdout, stderr) + + assert '_modeled_fields' not in stderr.getvalue() + class TestEnhancedErrorFormatter: def setup_method(self): @@ -343,6 +384,38 @@ def test_format_error_with_unicode_and_special_chars(self): ) assert output == expected + def test_format_error_hides_unmodeled_fields(self): + error_info = { + 'Code': 'ExpiredToken', + 'Message': 'Token expired', + 'Token-0': 'AQoDYXdzEJr...sensitive...', + } + + stream = io.StringIO() + self.formatter.format_error( + error_info, stream, modeled_fields={'Code', 'Message'} + ) + + assert stream.getvalue() == '' + + def test_format_error_shows_modeled_fields(self): + error_info = { + 'Code': 'FileSystemNotFound', + 'Message': 'Not found', + 'ErrorCode': 'FileSystemNotFound', + 'UnmodeledField': 'should be hidden', + } + + stream = io.StringIO() + self.formatter.format_error( + error_info, stream, + modeled_fields={'Code', 'Message', 'ErrorCode'}, + ) + + output = stream.getvalue() + assert 'ErrorCode: FileSystemNotFound' in output + assert 'UnmodeledField' not in output + def test_format_error_with_large_list(self): error_info = { 'Code': 'LargeList', From 0958880b25e628c1fe2a634191abccf531a7e2a7 Mon Sep 17 00:00:00 2001 From: Andrew Asseily Date: Tue, 17 Mar 2026 14:31:45 -0400 Subject: [PATCH 2/5] Add changelog entry --- .changes/next-release/bugfix-errorformatting-83208.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changes/next-release/bugfix-errorformatting-83208.json diff --git a/.changes/next-release/bugfix-errorformatting-83208.json b/.changes/next-release/bugfix-errorformatting-83208.json new file mode 100644 index 000000000000..7225afce4b5d --- /dev/null +++ b/.changes/next-release/bugfix-errorformatting-83208.json @@ -0,0 +1,5 @@ +{ + "type": "bugfix", + "category": "error formatting", + "description": "Error output now only displays modeled error fields in the 'Additional error details' section." +} From b6a951e06eedf68f313182d3fd6c62234e3614ed Mon Sep 17 00:00:00 2001 From: Andrew Asseily Date: Tue, 17 Mar 2026 15:15:02 -0400 Subject: [PATCH 3/5] move modeled_fields to exception attribute, case-insensitive filtering, return empty dict for unknown modeled fields --- awscli/botocore/client.py | 5 +- awscli/errorhandler.py | 22 +++------ tests/unit/test_structured_error.py | 72 ++++++++++++++++++++++++----- 3 files changed, 70 insertions(+), 29 deletions(-) diff --git a/awscli/botocore/client.py b/awscli/botocore/client.py index dc645df85dbc..c67485d62266 100644 --- a/awscli/botocore/client.py +++ b/awscli/botocore/client.py @@ -909,9 +909,10 @@ def _make_api_call(self, operation_name, api_params): modeled_fields = {'Code', 'Message'} if error_shape: modeled_fields |= set(error_shape.members.keys()) - parsed_response['ModeledErrorFields'] = modeled_fields error_class = self.exceptions.from_code(error_code) - raise error_class(parsed_response, operation_name) + error = error_class(parsed_response, operation_name) + error.modeled_fields = modeled_fields + raise error else: return parsed_response diff --git a/awscli/errorhandler.py b/awscli/errorhandler.py index e23c2e07da5e..fefecb75b239 100644 --- a/awscli/errorhandler.py +++ b/awscli/errorhandler.py @@ -99,16 +99,14 @@ def _format_inline(self, value): def _get_additional_fields(self, error_info, modeled_fields=None): standard_keys = {'code', 'message'} if modeled_fields is not None: + modeled_lower = {f.lower() for f in modeled_fields} return { k: v for k, v in error_info.items() - if k.lower() not in standard_keys and k in modeled_fields + if k.lower() not in standard_keys + and k.lower() in modeled_lower } - return { - k: v - for k, v in error_info.items() - if k.lower() not in standard_keys - } + return {} def construct_entry_point_handlers_chain(): @@ -291,7 +289,7 @@ def _extract_error_info(self, exception): error_response = self._extract_error_response(exception) if error_response and 'Error' in error_response: error_info = error_response['Error'] - modeled_fields = error_response.get('ModeledErrorFields') + modeled_fields = getattr(exception, 'modeled_fields', None) error_info['_modeled_fields'] = modeled_fields return error_info return None @@ -309,18 +307,12 @@ def _extract_error_response(exception): # not nested under an Error key. Botocore preserves this structure. # Include these fields to provide complete error information. # Exclude response metadata and avoid duplicates. - excluded_keys = {'Error', 'ResponseMetadata', 'Code', 'Message', - 'ModeledErrorFields'} + excluded_keys = {'Error', 'ResponseMetadata', 'Code', 'Message'} for key, value in exception.response.items(): if key not in excluded_keys and key not in error_dict: error_dict[key] = value - result = {'Error': error_dict} - if 'ModeledErrorFields' in exception.response: - result['ModeledErrorFields'] = exception.response[ - 'ModeledErrorFields' - ] - return result + return {'Error': error_dict} return None diff --git a/tests/unit/test_structured_error.py b/tests/unit/test_structured_error.py index 9bc7a5464950..6410597da7c3 100644 --- a/tests/unit/test_structured_error.py +++ b/tests/unit/test_structured_error.py @@ -53,6 +53,7 @@ def test_displays_structured_error_with_additional_members(self): 'ResponseMetadata': {'RequestId': '123'}, } client_error = ClientError(error_response, 'GetObject') + client_error.modeled_fields = {'Code', 'Message', 'BucketName'} stdout = io.StringIO() stderr = io.StringIO() @@ -129,6 +130,7 @@ def test_error_format_case_insensitive(self): 'ResponseMetadata': {'RequestId': '123'}, } client_error = ClientError(error_response, 'GetObject') + client_error.modeled_fields = {'Code', 'Message', 'BucketName'} self.session.config_store.set_config_provider( 'cli_error_format', mock.Mock(provide=lambda: 'Enhanced') @@ -158,9 +160,9 @@ def test_modeled_fields_filters_unmodeled_from_display(self): 'Token-0': 'AQoDYXdzEJr...sensitive...', }, 'ResponseMetadata': {'RequestId': '123'}, - 'ModeledErrorFields': {'Code', 'Message'}, } client_error = ClientError(error_response, 'ListBuckets') + client_error.modeled_fields = {'Code', 'Message'} stdout = io.StringIO() stderr = io.StringIO() @@ -178,9 +180,9 @@ def test_modeled_fields_not_leaked_in_json_format(self): 'Message': 'Token expired', }, 'ResponseMetadata': {'RequestId': '123'}, - 'ModeledErrorFields': {'Code', 'Message'}, } client_error = ClientError(error_response, 'ListBuckets') + client_error.modeled_fields = {'Code', 'Message'} self.session.session_vars['cli_error_format'] = 'json' @@ -190,6 +192,47 @@ def test_modeled_fields_not_leaked_in_json_format(self): self.handler.handle_exception(client_error, stdout, stderr) assert '_modeled_fields' not in stderr.getvalue() + assert 'modeled_fields' not in stderr.getvalue() + + def test_no_modeled_fields_hides_additional_fields(self): + # ClientError without modeled_fields attribute (e.g. manually + # constructed in customizations) should not show additional fields. + error_response = { + 'Error': { + 'Code': 'CustomError', + 'Message': 'Something broke', + 'Detail': 'extra info', + }, + 'ResponseMetadata': {'RequestId': '123'}, + } + client_error = ClientError(error_response, 'DoThing') + + stdout = io.StringIO() + stderr = io.StringIO() + + self.handler.handle_exception(client_error, stdout, stderr) + + assert 'Detail' not in stderr.getvalue() + + def test_modeled_fields_case_insensitive_match(self): + # Model has 'ErrorCode' but response has 'errorCode' (or vice versa) + error_response = { + 'Error': { + 'Code': 'SomeError', + 'Message': 'msg', + 'errorcode': 'detail', + }, + 'ResponseMetadata': {'RequestId': '123'}, + } + client_error = ClientError(error_response, 'Op') + client_error.modeled_fields = {'Code', 'Message', 'ErrorCode'} + + stdout = io.StringIO() + stderr = io.StringIO() + + self.handler.handle_exception(client_error, stdout, stderr) + + assert 'errorcode: detail' in stderr.getvalue() class TestEnhancedErrorFormatter: @@ -203,7 +246,7 @@ def test_format_error_with_no_additional_fields(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream) + self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) output = stream.getvalue() assert output == '' @@ -217,7 +260,7 @@ def test_format_error_with_simple_fields(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream) + self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) output = stream.getvalue() expected = ( @@ -236,7 +279,7 @@ def test_format_error_with_small_list(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream) + self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) output = stream.getvalue() expected = ( @@ -254,7 +297,7 @@ def test_format_error_with_small_dict(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream) + self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) output = stream.getvalue() expected = ( @@ -272,7 +315,7 @@ def test_format_error_with_complex_object(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream) + self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) output = stream.getvalue() expected = ( @@ -295,7 +338,7 @@ def test_format_error_with_nested_dict(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream) + self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) output = stream.getvalue() expected = ( @@ -324,7 +367,7 @@ def test_format_error_with_list_of_dicts(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream) + self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) output = stream.getvalue() expected = ( @@ -348,7 +391,7 @@ def test_format_error_with_mixed_types(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream) + self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) output = stream.getvalue() expected = ( @@ -372,7 +415,7 @@ def test_format_error_with_unicode_and_special_chars(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream) + self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) output = stream.getvalue() expected = ( @@ -424,7 +467,7 @@ def test_format_error_with_large_list(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream) + self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) output = stream.getvalue() expected = ( @@ -471,6 +514,9 @@ def test_dynamodb_transaction_cancelled_error(self): }, } client_error = ClientError(error_response, 'TransactWriteItems') + client_error.modeled_fields = { + 'Code', 'Message', 'CancellationReasons', + } stdout = io.StringIO() stderr = io.StringIO() @@ -514,6 +560,7 @@ def test_error_handler_receives_parsed_globals_from_clidriver(self): 'ResponseMetadata': {'RequestId': '123'}, } client_error = ClientError(error_response, 'GetObject') + client_error.modeled_fields = {'Code', 'Message', 'BucketName'} stdout = io.StringIO() stderr = io.StringIO() @@ -544,6 +591,7 @@ def test_error_handler_without_parsed_globals_uses_default(self): 'ResponseMetadata': {'RequestId': '123'}, } client_error = ClientError(error_response, 'GetObject') + client_error.modeled_fields = {'Code', 'Message', 'BucketName'} stdout = io.StringIO() stderr = io.StringIO() From 25cfb4026244991b1796ad5f79bb50654d55c0da Mon Sep 17 00:00:00 2001 From: Andrew Asseily Date: Tue, 17 Mar 2026 15:30:44 -0400 Subject: [PATCH 4/5] Filter unmodeled error fields from all CLI output formats --- awscli/botocore/client.py | 1 + awscli/errorhandler.py | 37 ++++++++++++++----------- tests/unit/test_structured_error.py | 42 ++++++++++++++--------------- 3 files changed, 43 insertions(+), 37 deletions(-) diff --git a/awscli/botocore/client.py b/awscli/botocore/client.py index c67485d62266..ed78f75661af 100644 --- a/awscli/botocore/client.py +++ b/awscli/botocore/client.py @@ -903,6 +903,7 @@ def _make_api_call(self, operation_name, api_params): if http.status_code >= 300: error_code = parsed_response.get("Error", {}).get("Code") + # Lookup is a cached dict access; only runs on error path. error_shape = self._service_model.shape_for_error_code( error_code ) diff --git a/awscli/errorhandler.py b/awscli/errorhandler.py index fefecb75b239..a13daa528663 100644 --- a/awscli/errorhandler.py +++ b/awscli/errorhandler.py @@ -49,10 +49,8 @@ class EnhancedErrorFormatter: - def format_error(self, error_info, stream, modeled_fields=None): - additional_fields = self._get_additional_fields( - error_info, modeled_fields - ) + def format_error(self, error_info, stream): + additional_fields = self._get_additional_fields(error_info) if not additional_fields: return @@ -96,17 +94,13 @@ def _format_inline(self, value): return f'{{{items}}}' return str(value) - def _get_additional_fields(self, error_info, modeled_fields=None): + def _get_additional_fields(self, error_info): standard_keys = {'code', 'message'} - if modeled_fields is not None: - modeled_lower = {f.lower() for f in modeled_fields} - return { - k: v - for k, v in error_info.items() - if k.lower() not in standard_keys - and k.lower() in modeled_lower - } - return {} + return { + k: v + for k, v in error_info.items() + if k.lower() not in standard_keys + } def construct_entry_point_handlers_chain(): @@ -212,6 +206,19 @@ def _display_structured_error( error_format = self._resolve_error_format(parsed_globals) modeled_fields = error_info.pop('_modeled_fields', None) + if modeled_fields is not None: + modeled_lower = {f.lower() for f in modeled_fields} + for key in list(error_info.keys()): + if key.lower() not in modeled_lower: + del error_info[key] + else: + # No model info available (e.g. manually constructed + # ClientError). Remove all non-standard fields. + standard = {'code', 'message'} + for key in list(error_info.keys()): + if key.lower() not in standard: + del error_info[key] + if error_format == 'legacy': return False @@ -225,7 +232,7 @@ def _display_structured_error( if error_format == 'enhanced': write_error(stderr, formatted_message) EnhancedErrorFormatter().format_error( - error_info, stderr, modeled_fields + error_info, stderr ) return True diff --git a/tests/unit/test_structured_error.py b/tests/unit/test_structured_error.py index 6410597da7c3..cfede75aedce 100644 --- a/tests/unit/test_structured_error.py +++ b/tests/unit/test_structured_error.py @@ -178,6 +178,7 @@ def test_modeled_fields_not_leaked_in_json_format(self): 'Error': { 'Code': 'ExpiredToken', 'Message': 'Token expired', + 'Token-0': 'AQoDYXdzEJr...sensitive...', }, 'ResponseMetadata': {'RequestId': '123'}, } @@ -191,8 +192,10 @@ def test_modeled_fields_not_leaked_in_json_format(self): self.handler.handle_exception(client_error, stdout, stderr) - assert '_modeled_fields' not in stderr.getvalue() - assert 'modeled_fields' not in stderr.getvalue() + parsed = json.loads(stderr.getvalue()) + assert 'Token-0' not in parsed + assert '_modeled_fields' not in parsed + assert 'modeled_fields' not in parsed def test_no_modeled_fields_hides_additional_fields(self): # ClientError without modeled_fields attribute (e.g. manually @@ -246,7 +249,7 @@ def test_format_error_with_no_additional_fields(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) + self.formatter.format_error(error_info, stream) output = stream.getvalue() assert output == '' @@ -260,7 +263,7 @@ def test_format_error_with_simple_fields(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) + self.formatter.format_error(error_info, stream) output = stream.getvalue() expected = ( @@ -279,7 +282,7 @@ def test_format_error_with_small_list(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) + self.formatter.format_error(error_info, stream) output = stream.getvalue() expected = ( @@ -297,7 +300,7 @@ def test_format_error_with_small_dict(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) + self.formatter.format_error(error_info, stream) output = stream.getvalue() expected = ( @@ -315,7 +318,7 @@ def test_format_error_with_complex_object(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) + self.formatter.format_error(error_info, stream) output = stream.getvalue() expected = ( @@ -338,7 +341,7 @@ def test_format_error_with_nested_dict(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) + self.formatter.format_error(error_info, stream) output = stream.getvalue() expected = ( @@ -367,7 +370,7 @@ def test_format_error_with_list_of_dicts(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) + self.formatter.format_error(error_info, stream) output = stream.getvalue() expected = ( @@ -391,7 +394,7 @@ def test_format_error_with_mixed_types(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) + self.formatter.format_error(error_info, stream) output = stream.getvalue() expected = ( @@ -415,7 +418,7 @@ def test_format_error_with_unicode_and_special_chars(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) + self.formatter.format_error(error_info, stream) output = stream.getvalue() expected = ( @@ -428,36 +431,31 @@ def test_format_error_with_unicode_and_special_chars(self): assert output == expected def test_format_error_hides_unmodeled_fields(self): + # Unmodeled fields are now filtered before reaching the formatter. + # Formatter receives only modeled fields. error_info = { 'Code': 'ExpiredToken', 'Message': 'Token expired', - 'Token-0': 'AQoDYXdzEJr...sensitive...', } stream = io.StringIO() - self.formatter.format_error( - error_info, stream, modeled_fields={'Code', 'Message'} - ) + self.formatter.format_error(error_info, stream) assert stream.getvalue() == '' def test_format_error_shows_modeled_fields(self): + # Unmodeled fields are filtered before reaching the formatter. error_info = { 'Code': 'FileSystemNotFound', 'Message': 'Not found', 'ErrorCode': 'FileSystemNotFound', - 'UnmodeledField': 'should be hidden', } stream = io.StringIO() - self.formatter.format_error( - error_info, stream, - modeled_fields={'Code', 'Message', 'ErrorCode'}, - ) + self.formatter.format_error(error_info, stream) output = stream.getvalue() assert 'ErrorCode: FileSystemNotFound' in output - assert 'UnmodeledField' not in output def test_format_error_with_large_list(self): error_info = { @@ -467,7 +465,7 @@ def test_format_error_with_large_list(self): } stream = io.StringIO() - self.formatter.format_error(error_info, stream, modeled_fields=set(error_info.keys())) + self.formatter.format_error(error_info, stream) output = stream.getvalue() expected = ( From 183ba88da17d9d1f39005001d73ba6764e1f077e Mon Sep 17 00:00:00 2001 From: Andrew Asseily Date: Wed, 18 Mar 2026 12:51:03 -0400 Subject: [PATCH 5/5] Formatting --- awscli/errorhandler.py | 4 +--- tests/unit/test_structured_error.py | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/awscli/errorhandler.py b/awscli/errorhandler.py index a13daa528663..f22fbf671df6 100644 --- a/awscli/errorhandler.py +++ b/awscli/errorhandler.py @@ -231,9 +231,7 @@ def _display_structured_error( if error_format == 'enhanced': write_error(stderr, formatted_message) - EnhancedErrorFormatter().format_error( - error_info, stderr - ) + EnhancedErrorFormatter().format_error(error_info, stderr) return True color = getattr(parsed_globals, 'color', 'auto') diff --git a/tests/unit/test_structured_error.py b/tests/unit/test_structured_error.py index cfede75aedce..6b113ca1b56c 100644 --- a/tests/unit/test_structured_error.py +++ b/tests/unit/test_structured_error.py @@ -513,7 +513,9 @@ def test_dynamodb_transaction_cancelled_error(self): } client_error = ClientError(error_response, 'TransactWriteItems') client_error.modeled_fields = { - 'Code', 'Message', 'CancellationReasons', + 'Code', + 'Message', + 'CancellationReasons', } stdout = io.StringIO()