From bd30e96d1b7de8409a446d175e6cde90d2703f41 Mon Sep 17 00:00:00 2001 From: Ana Falcao Date: Wed, 12 Feb 2025 17:17:13 -0300 Subject: [PATCH 1/2] feat(batch): raise exception for invalid batch event --- .../utilities/batch/decorators.py | 7 ++ .../utilities/batch/exceptions.py | 9 +++ .../test_utilities_batch.py | 68 ++++++++++++++++++- 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/aws_lambda_powertools/utilities/batch/decorators.py b/aws_lambda_powertools/utilities/batch/decorators.py index 0cba41f98fe..274b19468b9 100644 --- a/aws_lambda_powertools/utilities/batch/decorators.py +++ b/aws_lambda_powertools/utilities/batch/decorators.py @@ -12,6 +12,7 @@ BatchProcessor, EventType, ) +from aws_lambda_powertools.utilities.batch.exceptions import UnexpectedBatchTypeError from aws_lambda_powertools.warnings import PowertoolsDeprecationWarning if TYPE_CHECKING: @@ -204,6 +205,9 @@ def handler(event, context): """ try: records: list[dict] = event.get("Records", []) + if not records or not isinstance(records, list): + raise UnexpectedBatchTypeError + except AttributeError: event_types = ", ".join(list(EventType.__members__)) docs = "https://docs.powertools.aws.dev/lambda/python/latest/utilities/batch/#processing-messages-from-sqs" # noqa: E501 # long-line @@ -268,6 +272,9 @@ def handler(event, context): """ try: records: list[dict] = event.get("Records", []) + if not records or not isinstance(records, list): + raise UnexpectedBatchTypeError + except AttributeError: event_types = ", ".join(list(EventType.__members__)) docs = "https://docs.powertools.aws.dev/lambda/python/latest/utilities/batch/#processing-messages-from-sqs" # noqa: E501 # long-line diff --git a/aws_lambda_powertools/utilities/batch/exceptions.py b/aws_lambda_powertools/utilities/batch/exceptions.py index c93b96a8f34..78b95cb0002 100644 --- a/aws_lambda_powertools/utilities/batch/exceptions.py +++ b/aws_lambda_powertools/utilities/batch/exceptions.py @@ -38,6 +38,15 @@ def __str__(self): return self.format_exceptions(parent_exception_str) +class UnexpectedBatchTypeError(BatchProcessingError): + """Error thrown by the Batch Processing utility when a partial processor receives an unexpected batch type""" + + def __init__(self): + msg = "Unexpected batch type. Possible values are: SQS, KinesisDataStreams, DynamoDBStreams" + super().__init__(msg) + self.name = "UnexpectedBatchTypeError" + + class SQSFifoCircuitBreakerError(Exception): """ Signals a record not processed due to the SQS FIFO processing being interrupted diff --git a/tests/functional/batch/required_dependencies/test_utilities_batch.py b/tests/functional/batch/required_dependencies/test_utilities_batch.py index 9327a7d70fc..5936837cee0 100644 --- a/tests/functional/batch/required_dependencies/test_utilities_batch.py +++ b/tests/functional/batch/required_dependencies/test_utilities_batch.py @@ -15,7 +15,7 @@ batch_processor, process_partial_response, ) -from aws_lambda_powertools.utilities.batch.exceptions import BatchProcessingError +from aws_lambda_powertools.utilities.batch.exceptions import BatchProcessingError, UnexpectedBatchTypeError from aws_lambda_powertools.utilities.data_classes.dynamo_db_stream_event import ( DynamoDBRecord, ) @@ -708,3 +708,69 @@ def test_async_process_partial_response_invalid_input(async_record_handler: Call # WHEN/THEN with pytest.raises(ValueError): async_process_partial_response(batch, record_handler, processor) + + +@pytest.mark.parametrize( + "event", + [ + {}, + {"Records": None}, + {"Records": "not a list"}, + ], +) +def test_process_partial_response_raises_unexpected_batch_type(event, record_handler): + # GIVEN a batch processor configured for SQS events + processor = BatchProcessor(event_type=EventType.SQS) + + # WHEN processing an event with invalid Records + with pytest.raises(UnexpectedBatchTypeError) as exc_info: + process_partial_response( + event=event, + record_handler=record_handler, + processor=processor, + ) + + # THEN the correct error message is raised + assert "Unexpected batch type. Possible values are: SQS, KinesisDataStreams, DynamoDBStreams" in str(exc_info.value) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "event", + [ + {}, + {"Records": None}, + {"Records": "not a list"}, + ], +) +async def test_async_process_partial_response_raises_unexpected_batch_type(event, async_record_handler): + # GIVEN a batch processor configured for SQS events + processor = BatchProcessor(event_type=EventType.SQS) + + # WHEN processing an event with invalid Records asynchronously + with pytest.raises(UnexpectedBatchTypeError) as exc_info: + await async_process_partial_response( + event=event, + record_handler=async_record_handler, + processor=processor, + ) + + # THEN the correct error message is raised + assert "Unexpected batch type. Possible values are: SQS, KinesisDataStreams, DynamoDBStreams" in str(exc_info.value) + + +def test_process_partial_response_should_not_raise_unexpected_batch_type(record_handler): + # GIVEN a valid SQS event structure + event = {"Records": [{"messageId": "1", "body": "test"}]} + processor = BatchProcessor(event_type=EventType.SQS) + + # WHEN processing the event + try: + process_partial_response( + event=event, + record_handler=record_handler, + processor=processor, + ) + except UnexpectedBatchTypeError: + # THEN no UnexpectedBatchTypeError should be raised + pytest.fail("UnexpectedBatchTypeError was raised with a valid event structure!") From ae9b97a4375ae00c01896e35366304e19ea4fdac Mon Sep 17 00:00:00 2001 From: Ana Falcao Date: Thu, 13 Feb 2025 09:21:39 -0300 Subject: [PATCH 2/2] change msg error --- .../utilities/batch/decorators.py | 8 ++++-- .../utilities/batch/exceptions.py | 5 +--- .../test_utilities_batch.py | 25 +++++-------------- 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/aws_lambda_powertools/utilities/batch/decorators.py b/aws_lambda_powertools/utilities/batch/decorators.py index 274b19468b9..2b9f5433e70 100644 --- a/aws_lambda_powertools/utilities/batch/decorators.py +++ b/aws_lambda_powertools/utilities/batch/decorators.py @@ -206,7 +206,9 @@ def handler(event, context): try: records: list[dict] = event.get("Records", []) if not records or not isinstance(records, list): - raise UnexpectedBatchTypeError + raise UnexpectedBatchTypeError( + "Unexpected batch event type. Possible values are: SQS, KinesisDataStreams, DynamoDBStreams", + ) except AttributeError: event_types = ", ".join(list(EventType.__members__)) @@ -273,7 +275,9 @@ def handler(event, context): try: records: list[dict] = event.get("Records", []) if not records or not isinstance(records, list): - raise UnexpectedBatchTypeError + raise UnexpectedBatchTypeError( + "Unexpected batch event type. Possible values are: SQS, KinesisDataStreams, DynamoDBStreams", + ) except AttributeError: event_types = ", ".join(list(EventType.__members__)) diff --git a/aws_lambda_powertools/utilities/batch/exceptions.py b/aws_lambda_powertools/utilities/batch/exceptions.py index 78b95cb0002..87a2df22d6d 100644 --- a/aws_lambda_powertools/utilities/batch/exceptions.py +++ b/aws_lambda_powertools/utilities/batch/exceptions.py @@ -41,10 +41,7 @@ def __str__(self): class UnexpectedBatchTypeError(BatchProcessingError): """Error thrown by the Batch Processing utility when a partial processor receives an unexpected batch type""" - def __init__(self): - msg = "Unexpected batch type. Possible values are: SQS, KinesisDataStreams, DynamoDBStreams" - super().__init__(msg) - self.name = "UnexpectedBatchTypeError" + pass class SQSFifoCircuitBreakerError(Exception): diff --git a/tests/functional/batch/required_dependencies/test_utilities_batch.py b/tests/functional/batch/required_dependencies/test_utilities_batch.py index 5936837cee0..4c91dd54a1e 100644 --- a/tests/functional/batch/required_dependencies/test_utilities_batch.py +++ b/tests/functional/batch/required_dependencies/test_utilities_batch.py @@ -731,7 +731,9 @@ def test_process_partial_response_raises_unexpected_batch_type(event, record_han ) # THEN the correct error message is raised - assert "Unexpected batch type. Possible values are: SQS, KinesisDataStreams, DynamoDBStreams" in str(exc_info.value) + assert "Unexpected batch event type. Possible values are: SQS, KinesisDataStreams, DynamoDBStreams" in str( + exc_info.value, + ) @pytest.mark.asyncio @@ -756,21 +758,6 @@ async def test_async_process_partial_response_raises_unexpected_batch_type(event ) # THEN the correct error message is raised - assert "Unexpected batch type. Possible values are: SQS, KinesisDataStreams, DynamoDBStreams" in str(exc_info.value) - - -def test_process_partial_response_should_not_raise_unexpected_batch_type(record_handler): - # GIVEN a valid SQS event structure - event = {"Records": [{"messageId": "1", "body": "test"}]} - processor = BatchProcessor(event_type=EventType.SQS) - - # WHEN processing the event - try: - process_partial_response( - event=event, - record_handler=record_handler, - processor=processor, - ) - except UnexpectedBatchTypeError: - # THEN no UnexpectedBatchTypeError should be raised - pytest.fail("UnexpectedBatchTypeError was raised with a valid event structure!") + assert "Unexpected batch event type. Possible values are: SQS, KinesisDataStreams, DynamoDBStreams" in str( + exc_info.value, + )