3737 KinesisStreamRecord ,
3838)
3939from aws_lambda_powertools .utilities .data_classes .sqs_event import SQSRecord
40+ from aws_lambda_powertools .utilities .parser import ValidationError
4041from aws_lambda_powertools .utilities .typing import LambdaContext
4142
4243logger = logging .getLogger (__name__ )
@@ -316,21 +317,36 @@ def _get_messages_to_report(self) -> List[Dict[str, str]]:
316317 def _collect_sqs_failures (self ):
317318 failures = []
318319 for msg in self .fail_messages :
319- msg_id = msg .messageId if self .model else msg .message_id
320+ # If a message failed due to model validation (e.g., poison pill)
321+ # we convert to an event source data class...but self.model is still true
322+ # therefore, we do an additional check on whether the failed message is still a model
323+ # see https://github.com/awslabs/aws-lambda-powertools-python/issues/2091
324+ if self .model and getattr (msg , "parse_obj" , None ):
325+ msg_id = msg .messageId
326+ else :
327+ msg_id = msg .message_id
320328 failures .append ({"itemIdentifier" : msg_id })
321329 return failures
322330
323331 def _collect_kinesis_failures (self ):
324332 failures = []
325333 for msg in self .fail_messages :
326- msg_id = msg .kinesis .sequenceNumber if self .model else msg .kinesis .sequence_number
334+ # # see https://github.com/awslabs/aws-lambda-powertools-python/issues/2091
335+ if self .model and getattr (msg , "parse_obj" , None ):
336+ msg_id = msg .kinesis .sequenceNumber
337+ else :
338+ msg_id = msg .kinesis .sequence_number
327339 failures .append ({"itemIdentifier" : msg_id })
328340 return failures
329341
330342 def _collect_dynamodb_failures (self ):
331343 failures = []
332344 for msg in self .fail_messages :
333- msg_id = msg .dynamodb .SequenceNumber if self .model else msg .dynamodb .sequence_number
345+ # see https://github.com/awslabs/aws-lambda-powertools-python/issues/2091
346+ if self .model and getattr (msg , "parse_obj" , None ):
347+ msg_id = msg .dynamodb .SequenceNumber
348+ else :
349+ msg_id = msg .dynamodb .sequence_number
334350 failures .append ({"itemIdentifier" : msg_id })
335351 return failures
336352
@@ -347,6 +363,17 @@ def _to_batch_type(self, record: dict, event_type: EventType, model: Optional["B
347363 return model .parse_obj (record )
348364 return self ._DATA_CLASS_MAPPING [event_type ](record )
349365
366+ def _register_model_validation_error_record (self , record : dict ):
367+ """Convert and register failure due to poison pills where model failed validation early"""
368+ # Parser will fail validation if record is a poison pill (malformed input)
369+ # this means we can't collect the message id if we try transforming again
370+ # so we convert into to the equivalent batch type model (e.g., SQS, Kinesis, DynamoDB Stream)
371+ # and downstream we can correctly collect the correct message id identifier and make the failed record available
372+ # see https://github.com/awslabs/aws-lambda-powertools-python/issues/2091
373+ logger .debug ("Record cannot be converted to customer's model; converting without model" )
374+ failed_record : "EventSourceDataClassTypes" = self ._to_batch_type (record = record , event_type = self .event_type )
375+ return self .failure_handler (record = failed_record , exception = sys .exc_info ())
376+
350377
351378class BatchProcessor (BasePartialBatchProcessor ): # Keep old name for compatibility
352379 """Process native partial responses from SQS, Kinesis Data Streams, and DynamoDB.
@@ -471,14 +498,17 @@ def _process_record(self, record: dict) -> Union[SuccessResponse, FailureRespons
471498 record: dict
472499 A batch record to be processed.
473500 """
474- data = self . _to_batch_type ( record = record , event_type = self . event_type , model = self . model )
501+ data : Optional [ "BatchTypeModels" ] = None
475502 try :
503+ data = self ._to_batch_type (record = record , event_type = self .event_type , model = self .model )
476504 if self ._handler_accepts_lambda_context :
477505 result = self .handler (record = data , lambda_context = self .lambda_context )
478506 else :
479507 result = self .handler (record = data )
480508
481509 return self .success_handler (record = record , result = result )
510+ except ValidationError :
511+ return self ._register_model_validation_error_record (record )
482512 except Exception :
483513 return self .failure_handler (record = data , exception = sys .exc_info ())
484514
@@ -651,14 +681,17 @@ async def _async_process_record(self, record: dict) -> Union[SuccessResponse, Fa
651681 record: dict
652682 A batch record to be processed.
653683 """
654- data = self . _to_batch_type ( record = record , event_type = self . event_type , model = self . model )
684+ data : Optional [ "BatchTypeModels" ] = None
655685 try :
686+ data = self ._to_batch_type (record = record , event_type = self .event_type , model = self .model )
656687 if self ._handler_accepts_lambda_context :
657688 result = await self .handler (record = data , lambda_context = self .lambda_context )
658689 else :
659690 result = await self .handler (record = data )
660691
661692 return self .success_handler (record = record , result = result )
693+ except ValidationError :
694+ return self ._register_model_validation_error_record (record )
662695 except Exception :
663696 return self .failure_handler (record = data , exception = sys .exc_info ())
664697
0 commit comments