From 0f0861db40fe55bd70027f560b5128afb6d84f37 Mon Sep 17 00:00:00 2001 From: Alessandra Romero Date: Wed, 4 Sep 2024 15:04:58 -0400 Subject: [PATCH] implement feedback and move test_only_s3_targets_expires_header() to test_s3.py --- botocore/parsers.py | 21 +++++---- tests/functional/test_s3.py | 40 ++++++++++++++++++ tests/functional/test_service_headers.py | 54 ------------------------ tests/unit/test_handlers.py | 31 ++++---------- 4 files changed, 59 insertions(+), 87 deletions(-) delete mode 100644 tests/functional/test_service_headers.py diff --git a/botocore/parsers.py b/botocore/parsers.py index e6933975b6..9b80cfde2f 100644 --- a/botocore/parsers.py +++ b/botocore/parsers.py @@ -536,17 +536,6 @@ def _handle_float(self, shape, text): def _handle_timestamp(self, shape, text): return self._timestamp_parser(text) - @_text_content - def _handle_expires_timestamp(self, shape, text): - try: - return self._handle_timestamp(shape, text) - except (ValueError, RuntimeError): - LOG.warning( - f'Failed to parse the "Expires" member as a timestamp: {text}. ' - f'The unparsed value is available in the response under "ExpiresString".' - ) - return None - @_text_content def _handle_integer(self, shape, text): return int(text) @@ -1023,6 +1012,16 @@ def _handle_list(self, shape, node): node = [e.strip() for e in node.split(',')] return super()._handle_list(shape, node) + def _handle_expires_timestamp(self, shape, timestamp): + try: + return self._handle_timestamp(shape, timestamp) + except (ValueError, RuntimeError): + LOG.warning( + f'Failed to parse the "Expires" member as a timestamp: {timestamp}. ' + f'The unparsed value is available in the response under "ExpiresString".' + ) + return None + class RestJSONParser(BaseRestParser, BaseJSONParser): EVENT_STREAM_PARSER_CLS = EventStreamJSONParser diff --git a/tests/functional/test_s3.py b/tests/functional/test_s3.py index f250c95f24..8138bb4f41 100644 --- a/tests/functional/test_s3.py +++ b/tests/functional/test_s3.py @@ -3718,3 +3718,43 @@ def _verify_bucket_and_key_in_context(self, request, **kwargs): request.context['input_params']['Bucket'], self.BUCKET ) self.assertEqual(request.context['input_params']['Key'], self.KEY) + + +@pytest.mark.validates_models +def test_only_s3_targets_expires_header(): + """ + This test verifies that S3 is the only service that uses the 'expires' header + in their response. If any other service is found to target this header in any + of their output shapes, the test will fail and alert us of the change. + """ + session = botocore.session.get_session() + loader = session.get_component('data_loader') + services = loader.list_available_services('service-2') + services_that_target_expires_header = set() + + for service in services: + service_model = session.get_service_model(service) + for operation in service_model.operation_names: + operation_model = service_model.operation_model(operation) + if output_shape := operation_model.output_shape: + if _shape_targets_expires_header(output_shape): + services_that_target_expires_header.add(service) + assert services_that_target_expires_header == {'s3'}, ( + f"Expected only 's3' to target the 'Expires' header.\n" + f"Actual services that target the 'Expires' header: {services_that_target_expires_header}\n" + f"Please review the service models to verify this change." + ) + + +def _shape_targets_expires_header(shape): + for member_shape in shape.members.values(): + location = member_shape.serialization.get('location') + location_name = member_shape.serialization.get('name') + if ( + location + and location.lower() == 'header' + and location_name + and location_name.lower() == 'expires' + ): + return True + return False diff --git a/tests/functional/test_service_headers.py b/tests/functional/test_service_headers.py deleted file mode 100644 index 1bfa629e9a..0000000000 --- a/tests/functional/test_service_headers.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You -# may not use this file except in compliance with the License. A copy of -# the License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is -# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -# ANY KIND, either express or implied. See the License for the specific -# language governing permissions and limitations under the License. -import pytest - -from botocore.session import get_session - -# This test verifies that S3 is the only service that uses the 'expires' header -# in their response. If any other service is found to target this header in any -# of their output shapes, the test will fail and alert us of the change. - - -@pytest.mark.validates_models -def test_only_s3_targets_expires_header(): - session = get_session() - loader = session.get_component('data_loader') - services = loader.list_available_services('service-2') - services_that_target_expires_header = set() - - for service in services: - service_model = session.get_service_model(service) - for operation in service_model.operation_names: - operation_model = service_model.operation_model(operation) - if output_shape := operation_model.output_shape: - if _shape_targets_expires_header(output_shape): - services_that_target_expires_header.add(service) - assert services_that_target_expires_header == {'s3'}, ( - f"Expected only 's3' to target the 'Expires' header.\n" - f"Actual services that target the 'Expires' header: {services_that_target_expires_header}\n" - f"Please review the service models to verify this change." - ) - - -def _shape_targets_expires_header(shape): - for member_shape in shape.members.values(): - location = member_shape.serialization.get('location') - location_name = member_shape.serialization.get('name') - if ( - location - and location.lower() == 'header' - and location_name - and location_name.lower() == 'expires' - ): - return True - return False diff --git a/tests/unit/test_handlers.py b/tests/unit/test_handlers.py index e6e8953a13..0f17b7e6d2 100644 --- a/tests/unit/test_handlers.py +++ b/tests/unit/test_handlers.py @@ -1784,29 +1784,16 @@ def test_remove_bucket_from_url_paths_from_model( @pytest.fixture() def document_s3_expires_mocks(): - section = mock.Mock() - parent = mock.Mock() - param_line = mock.Mock() - param_section = mock.Mock() - doc_section = mock.Mock() - new_param_line = mock.Mock() - new_param_section = mock.Mock() - response_example_event = ( - 'docs.response-example.s3.TestOperation.complete-section' - ) - response_params_event = ( - 'docs.response-params.s3.TestOperation.complete-section' - ) return { - 'section': section, - 'parent': parent, - 'param_line': param_line, - 'param_section': param_section, - 'doc_section': doc_section, - 'new_param_line': new_param_line, - 'new_param_section': new_param_section, - 'response_example_event': response_example_event, - 'response_params_event': response_params_event, + 'section': mock.Mock(), + 'parent': mock.Mock(), + 'param_line': mock.Mock(), + 'param_section': mock.Mock(), + 'doc_section': mock.Mock(), + 'new_param_line': mock.Mock(), + 'new_param_section': mock.Mock(), + 'response_example_event': 'docs.response-example.s3.TestOperation.complete-section', + 'response_params_event': 'docs.response-params.s3.TestOperation.complete-section', }