37
37
KinesisStreamRecord ,
38
38
)
39
39
from aws_lambda_powertools .utilities .data_classes .sqs_event import SQSRecord
40
+ from aws_lambda_powertools .utilities .parser import ValidationError
40
41
from aws_lambda_powertools .utilities .typing import LambdaContext
41
42
42
43
logger = logging .getLogger (__name__ )
@@ -316,21 +317,36 @@ def _get_messages_to_report(self) -> List[Dict[str, str]]:
316
317
def _collect_sqs_failures (self ):
317
318
failures = []
318
319
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
320
328
failures .append ({"itemIdentifier" : msg_id })
321
329
return failures
322
330
323
331
def _collect_kinesis_failures (self ):
324
332
failures = []
325
333
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
327
339
failures .append ({"itemIdentifier" : msg_id })
328
340
return failures
329
341
330
342
def _collect_dynamodb_failures (self ):
331
343
failures = []
332
344
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
334
350
failures .append ({"itemIdentifier" : msg_id })
335
351
return failures
336
352
@@ -347,6 +363,17 @@ def _to_batch_type(self, record: dict, event_type: EventType, model: Optional["B
347
363
return model .parse_obj (record )
348
364
return self ._DATA_CLASS_MAPPING [event_type ](record )
349
365
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
+
350
377
351
378
class BatchProcessor (BasePartialBatchProcessor ): # Keep old name for compatibility
352
379
"""Process native partial responses from SQS, Kinesis Data Streams, and DynamoDB.
@@ -471,14 +498,17 @@ def _process_record(self, record: dict) -> Union[SuccessResponse, FailureRespons
471
498
record: dict
472
499
A batch record to be processed.
473
500
"""
474
- data = self . _to_batch_type ( record = record , event_type = self . event_type , model = self . model )
501
+ data : Optional [ "BatchTypeModels" ] = None
475
502
try :
503
+ data = self ._to_batch_type (record = record , event_type = self .event_type , model = self .model )
476
504
if self ._handler_accepts_lambda_context :
477
505
result = self .handler (record = data , lambda_context = self .lambda_context )
478
506
else :
479
507
result = self .handler (record = data )
480
508
481
509
return self .success_handler (record = record , result = result )
510
+ except ValidationError :
511
+ return self ._register_model_validation_error_record (record )
482
512
except Exception :
483
513
return self .failure_handler (record = data , exception = sys .exc_info ())
484
514
@@ -651,14 +681,17 @@ async def _async_process_record(self, record: dict) -> Union[SuccessResponse, Fa
651
681
record: dict
652
682
A batch record to be processed.
653
683
"""
654
- data = self . _to_batch_type ( record = record , event_type = self . event_type , model = self . model )
684
+ data : Optional [ "BatchTypeModels" ] = None
655
685
try :
686
+ data = self ._to_batch_type (record = record , event_type = self .event_type , model = self .model )
656
687
if self ._handler_accepts_lambda_context :
657
688
result = await self .handler (record = data , lambda_context = self .lambda_context )
658
689
else :
659
690
result = await self .handler (record = data )
660
691
661
692
return self .success_handler (record = record , result = result )
693
+ except ValidationError :
694
+ return self ._register_model_validation_error_record (record )
662
695
except Exception :
663
696
return self .failure_handler (record = data , exception = sys .exc_info ())
664
697
0 commit comments