diff --git a/aws_lambda_powertools/utilities/parser/models/__init__.py b/aws_lambda_powertools/utilities/parser/models/__init__.py index 7581d8d9eb3..ba18fd9f04a 100644 --- a/aws_lambda_powertools/utilities/parser/models/__init__.py +++ b/aws_lambda_powertools/utilities/parser/models/__init__.py @@ -66,6 +66,7 @@ ) from .dynamodb import ( DynamoDBStreamChangedRecordModel, + DynamoDBStreamLambdaOnFailureDestinationModel, DynamoDBStreamModel, DynamoDBStreamRecordModel, ) @@ -173,6 +174,7 @@ "DynamoDBStreamModel", "EventBridgeModel", "DynamoDBStreamChangedRecordModel", + "DynamoDBStreamLambdaOnFailureDestinationModel", "DynamoDBStreamRecordModel", "DynamoDBStreamChangedRecordModel", "KinesisDataStreamModel", diff --git a/aws_lambda_powertools/utilities/parser/models/dynamodb.py b/aws_lambda_powertools/utilities/parser/models/dynamodb.py index e3c3dd4544f..d6091d32d7a 100644 --- a/aws_lambda_powertools/utilities/parser/models/dynamodb.py +++ b/aws_lambda_powertools/utilities/parser/models/dynamodb.py @@ -2,7 +2,8 @@ from datetime import datetime from typing import Any, Dict, List, Literal, Optional, Type, Union -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, ConfigDict, Field, field_validator +from pydantic.alias_generators import to_camel from aws_lambda_powertools.shared.dynamodb_deserializer import TypeDeserializer @@ -110,3 +111,91 @@ class DynamoDBStreamModel(BaseModel): }, ], ) + + +class DDBStreamBatchInfo(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + approximate_arrival_of_first_record: datetime = Field( + description="The approximate date and time when the first stream record from the batch was created" + ", in ISO-8601 format.", + examples=["1970-01-01T00:00:00.000Z"], + ) + approximate_arrival_of_last_record: datetime = Field( + description="The approximate date and time when the last stream record from the batch was created" + ", in ISO-8601 format.", + examples=["1970-01-01T00:00:00.000Z"], + ) + batch_size: int = Field( + description="The size of the batch.", + examples=[1], + ) + end_sequence_number: str = Field( + description="The unique identifier of the last stream record from the batch.", + examples=["222"], + ) + shard_id: str = Field( + description="The unique identifier of the DynamoDB Stream shard that contains the records from the batch.", + examples=["shardId-00000000000000000000-00000000"], + ) + start_sequence_number: str = Field( + description="The unique identifier of the first stream record from the batch.", + examples=["222"], + ) + stream_arn: str = Field( + description="The Amazon Resource Name (ARN) of the DynamoDB stream.", + examples=["arn:aws:dynamodb:us-west-2:123456789012:table/ExampleTable/stream/2021-01-01T00:00:00.000"], + ) + + +class RequestContext(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + approximate_invoke_count: int = Field( + description="The number of Lambda invocations for the record.", + examples=[1], + ) + condition: str = Field( + description="The condition that caused the record to be discarded.", + examples=["RetryAttemptsExhausted"], + ) + function_arn: str = Field( + description="The Amazon Resource Name (ARN) of the Lambda.", + examples=["arn:aws:lambda:eu-west-1:809313241:function:test"], + ) + request_id: str = Field( + description="The unique identifier of the request.", + ) + + +class ResponseContext(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + executed_version: str = Field( + description="The version of the Lambda executed", + examples=["$LATEST"], + ) + function_error: str = Field( + description="", + examples=["Unhandled"], + ) + status_code: int = Field( + description="The status code returned by the Lambda", + ) + + +# https://docs.aws.amazon.com/lambda/latest/dg/services-dynamodb-errors.html +class DynamoDBStreamLambdaOnFailureDestinationModel(BaseModel): + model_config = ConfigDict(alias_generator=to_camel) + + ddb_stream_batch_info: DDBStreamBatchInfo = Field(alias="DDBStreamBatchInfo") + request_context: RequestContext + response_context: ResponseContext + timestamp: datetime = Field( + description="The record time, in ISO-8601 format.", + examples=["1970-01-01T00:00:00.000Z"], + ) + version: str = Field( + description="The version of the record format.", + examples=["1.0"], + ) diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index f2394d73680..b4c7ad749a9 100644 --- a/docs/utilities/parser.md +++ b/docs/utilities/parser.md @@ -100,63 +100,64 @@ You can use pre-built models to work events from AWS services, so you don’t ne The example above uses `SqsModel`. Other built-in models can be found below. -| Model name | Description | -| ------------------------------------------- | --------------------------------------------------------------------------------------------- | -| **AlbModel** | Lambda Event Source payload for Amazon Application Load Balancer | -| **APIGatewayProxyEventModel** | Lambda Event Source payload for Amazon API Gateway | -| **ApiGatewayAuthorizerToken** | Lambda Event Source payload for Amazon API Gateway Lambda Authorizer with Token | -| **ApiGatewayAuthorizerRequest** | Lambda Event Source payload for Amazon API Gateway Lambda Authorizer with Request | -| **APIGatewayProxyEventV2Model** | Lambda Event Source payload for Amazon API Gateway v2 payload | -| **ApiGatewayAuthorizerRequestV2** | Lambda Event Source payload for Amazon API Gateway v2 Lambda Authorizer | -| **APIGatewayWebSocketMessageEventModel** | Lambda Event Source payload for Amazon API Gateway WebSocket API message body | -| **APIGatewayWebSocketConnectEventModel** | Lambda Event Source payload for Amazon API Gateway WebSocket API $connect message | -| **APIGatewayWebSocketDisconnectEventModel** | Lambda Event Source payload for Amazon API Gateway WebSocket API $disconnect message | -| **AppSyncResolverEventModel** | Lambda Event Source payload for AWS AppSync Resolver | -| **AppSyncEventsModel** | Lambda Event Source payload for AWS AppSync Events | -| **BedrockAgentEventModel** | Lambda Event Source payload for Bedrock Agents - OpenAPI-based | -| **BedrockAgentFunctionEventModel** | Lambda Event Source payload for Bedrock Agents - Function-based | -| **CloudFormationCustomResourceCreateModel** | Lambda Event Source payload for AWS CloudFormation `CREATE` operation | -| **CloudFormationCustomResourceUpdateModel** | Lambda Event Source payload for AWS CloudFormation `UPDATE` operation | -| **CloudFormationCustomResourceDeleteModel** | Lambda Event Source payload for AWS CloudFormation `DELETE` operation | -| **CloudwatchLogsModel** | Lambda Event Source payload for Amazon CloudWatch Logs | -| **CognitoPreSignupTriggerModel** | Lambda User Pool Pre-Sign-Up trigger event | -| **CognitoPostConfirmationTriggerModel** | Lambda User Pool Post Confirmation trigger event | -| **CognitoPreAuthenticationTriggerModel** | Lambda User Pool Pre Authentication trigger event | -| **CognitoPostAuthenticationTriggerModel** | Lambda User Pool Post Authentication trigger event | -| **CognitoPreTokenGenerationTriggerModelV1** | Lambda User Pool Pre Token Generation V1 trigger event | -| **CognitoPreTokenGenerationTriggerModelV2AndV3** | Lambda User Pool Pre Token Generation V2 and V3 trigger event | -| **CognitoMigrateUserTriggerModel** | Lambda User Pool Migrate User trigger event | -| **CognitoCustomMessageTriggerModel** | Lambda User Pool Custom Message trigger event | -| **CognitoCustomEmailSenderTriggerModel** | Lambda User Pool Custom Email Sender trigger event | -| **CognitoCustomSMSSenderTriggerModel** | Lambda User Pool Custom SMS Sender trigger event | -| **CognitoDefineAuthChallengeTriggerModel** | Lambda User Pool Define Auth Challenge trigger event | -| **CognitoCreateAuthChallengeTriggerModel** | Lambda User Pool Create Auth Challenge trigger event | -| **CognitoVerifyAuthChallengeTriggerModel** | Lambda User Pool Verify Auth Challenge trigger event | -| **DynamoDBStreamModel** | Lambda Event Source payload for Amazon DynamoDB Streams | -| **EventBridgeModel** | Lambda Event Source payload for Amazon EventBridge | -| **IoTCoreThingEvent** | Lambda Event Source payload for IoT Core Thing created, updated, or deleted. | -| **IoTCoreThingTypeEvent** | Lambda Event Source payload for IoT Core Thing Type events. | -| **IoTCoreThingTypeAssociationEvent** | Lambda Event Source payload for IoT Core Thing Type associated or disassociated with a Thing. | -| **IoTCoreThingGroupEvent** | Lambda Event Source payload for IoT Core Thing Group created, updated, or deleted. | -| **IoTCoreAddOrRemoveFromThingGroupEvent** | Lambda Event Source payload for IoT Core Thing added to or removed from a Thing Group. | -| **IoTCoreAddOrDeleteFromThingGroupEvent** | Lambda Event Source payload for IoT Core Thing Group added to or deleted from a Thing Group. | -| **KafkaMskEventModel** | Lambda Event Source payload for AWS MSK payload | -| **KafkaSelfManagedEventModel** | Lambda Event Source payload for self managed Kafka payload | -| **KinesisDataStreamModel** | Lambda Event Source payload for Amazon Kinesis Data Streams | -| **KinesisFirehoseModel** | Lambda Event Source payload for Amazon Kinesis Firehose | -| **KinesisFirehoseSqsModel** | Lambda Event Source payload for SQS messages wrapped in Kinesis Firehose records | -| **LambdaFunctionUrlModel** | Lambda Event Source payload for Lambda Function URL payload | -| **S3BatchOperationModel** | Lambda Event Source payload for Amazon S3 Batch Operation | -| **S3EventNotificationEventBridgeModel** | Lambda Event Source payload for Amazon S3 Event Notification to EventBridge. | -| **S3Model** | Lambda Event Source payload for Amazon S3 | -| **S3ObjectLambdaEvent** | Lambda Event Source payload for Amazon S3 Object Lambda | -| **S3SqsEventNotificationModel** | Lambda Event Source payload for S3 event notifications wrapped in SQS event (S3->SQS) | -| **SesModel** | Lambda Event Source payload for Amazon Simple Email Service | -| **SnsModel** | Lambda Event Source payload for Amazon Simple Notification Service | -| **SqsModel** | Lambda Event Source payload for Amazon SQS | -| **TransferFamilyAuthorizer** | Lambda Event Source payload for AWS Transfer Family Lambda authorizer | -| **VpcLatticeModel** | Lambda Event Source payload for Amazon VPC Lattice | -| **VpcLatticeV2Model** | Lambda Event Source payload for Amazon VPC Lattice v2 payload | +| Model name | Description | +|---------------------------------------------------|-----------------------------------------------------------------------------------------------| +| **AlbModel** | Lambda Event Source payload for Amazon Application Load Balancer | +| **APIGatewayProxyEventModel** | Lambda Event Source payload for Amazon API Gateway | +| **ApiGatewayAuthorizerToken** | Lambda Event Source payload for Amazon API Gateway Lambda Authorizer with Token | +| **ApiGatewayAuthorizerRequest** | Lambda Event Source payload for Amazon API Gateway Lambda Authorizer with Request | +| **APIGatewayProxyEventV2Model** | Lambda Event Source payload for Amazon API Gateway v2 payload | +| **ApiGatewayAuthorizerRequestV2** | Lambda Event Source payload for Amazon API Gateway v2 Lambda Authorizer | +| **APIGatewayWebSocketMessageEventModel** | Lambda Event Source payload for Amazon API Gateway WebSocket API message body | +| **APIGatewayWebSocketConnectEventModel** | Lambda Event Source payload for Amazon API Gateway WebSocket API $connect message | +| **APIGatewayWebSocketDisconnectEventModel** | Lambda Event Source payload for Amazon API Gateway WebSocket API $disconnect message | +| **AppSyncResolverEventModel** | Lambda Event Source payload for AWS AppSync Resolver | +| **AppSyncEventsModel** | Lambda Event Source payload for AWS AppSync Events | +| **BedrockAgentEventModel** | Lambda Event Source payload for Bedrock Agents - OpenAPI-based | +| **BedrockAgentFunctionEventModel** | Lambda Event Source payload for Bedrock Agents - Function-based | +| **CloudFormationCustomResourceCreateModel** | Lambda Event Source payload for AWS CloudFormation `CREATE` operation | +| **CloudFormationCustomResourceUpdateModel** | Lambda Event Source payload for AWS CloudFormation `UPDATE` operation | +| **CloudFormationCustomResourceDeleteModel** | Lambda Event Source payload for AWS CloudFormation `DELETE` operation | +| **CloudwatchLogsModel** | Lambda Event Source payload for Amazon CloudWatch Logs | +| **CognitoPreSignupTriggerModel** | Lambda User Pool Pre-Sign-Up trigger event | +| **CognitoPostConfirmationTriggerModel** | Lambda User Pool Post Confirmation trigger event | +| **CognitoPreAuthenticationTriggerModel** | Lambda User Pool Pre Authentication trigger event | +| **CognitoPostAuthenticationTriggerModel** | Lambda User Pool Post Authentication trigger event | +| **CognitoPreTokenGenerationTriggerModelV1** | Lambda User Pool Pre Token Generation V1 trigger event | +| **CognitoPreTokenGenerationTriggerModelV2AndV3** | Lambda User Pool Pre Token Generation V2 and V3 trigger event | +| **CognitoMigrateUserTriggerModel** | Lambda User Pool Migrate User trigger event | +| **CognitoCustomMessageTriggerModel** | Lambda User Pool Custom Message trigger event | +| **CognitoCustomEmailSenderTriggerModel** | Lambda User Pool Custom Email Sender trigger event | +| **CognitoCustomSMSSenderTriggerModel** | Lambda User Pool Custom SMS Sender trigger event | +| **CognitoDefineAuthChallengeTriggerModel** | Lambda User Pool Define Auth Challenge trigger event | +| **CognitoCreateAuthChallengeTriggerModel** | Lambda User Pool Create Auth Challenge trigger event | +| **CognitoVerifyAuthChallengeTriggerModel** | Lambda User Pool Verify Auth Challenge trigger event | +| **DynamoDBStreamModel** | Lambda Event Source payload for Amazon DynamoDB Streams | +| **DynamoDBStreamLambdaOnFailureDestinationModel** | Lambda on-failure destination payload for Amazon DynamoDB Streams | +| **EventBridgeModel** | Lambda Event Source payload for Amazon EventBridge | +| **IoTCoreThingEvent** | Lambda Event Source payload for IoT Core Thing created, updated, or deleted. | +| **IoTCoreThingTypeEvent** | Lambda Event Source payload for IoT Core Thing Type events. | +| **IoTCoreThingTypeAssociationEvent** | Lambda Event Source payload for IoT Core Thing Type associated or disassociated with a Thing. | +| **IoTCoreThingGroupEvent** | Lambda Event Source payload for IoT Core Thing Group created, updated, or deleted. | +| **IoTCoreAddOrRemoveFromThingGroupEvent** | Lambda Event Source payload for IoT Core Thing added to or removed from a Thing Group. | +| **IoTCoreAddOrDeleteFromThingGroupEvent** | Lambda Event Source payload for IoT Core Thing Group added to or deleted from a Thing Group. | +| **KafkaMskEventModel** | Lambda Event Source payload for AWS MSK payload | +| **KafkaSelfManagedEventModel** | Lambda Event Source payload for self managed Kafka payload | +| **KinesisDataStreamModel** | Lambda Event Source payload for Amazon Kinesis Data Streams | +| **KinesisFirehoseModel** | Lambda Event Source payload for Amazon Kinesis Firehose | +| **KinesisFirehoseSqsModel** | Lambda Event Source payload for SQS messages wrapped in Kinesis Firehose records | +| **LambdaFunctionUrlModel** | Lambda Event Source payload for Lambda Function URL payload | +| **S3BatchOperationModel** | Lambda Event Source payload for Amazon S3 Batch Operation | +| **S3EventNotificationEventBridgeModel** | Lambda Event Source payload for Amazon S3 Event Notification to EventBridge. | +| **S3Model** | Lambda Event Source payload for Amazon S3 | +| **S3ObjectLambdaEvent** | Lambda Event Source payload for Amazon S3 Object Lambda | +| **S3SqsEventNotificationModel** | Lambda Event Source payload for S3 event notifications wrapped in SQS event (S3->SQS) | +| **SesModel** | Lambda Event Source payload for Amazon Simple Email Service | +| **SnsModel** | Lambda Event Source payload for Amazon Simple Notification Service | +| **SqsModel** | Lambda Event Source payload for Amazon SQS | +| **TransferFamilyAuthorizer** | Lambda Event Source payload for AWS Transfer Family Lambda authorizer | +| **VpcLatticeModel** | Lambda Event Source payload for Amazon VPC Lattice | +| **VpcLatticeV2Model** | Lambda Event Source payload for Amazon VPC Lattice v2 payload | #### Extending built-in models diff --git a/tests/events/dynamoStreamLambdaInvocationEvent.json b/tests/events/dynamoStreamLambdaInvocationEvent.json new file mode 100644 index 00000000000..7e6c6353f42 --- /dev/null +++ b/tests/events/dynamoStreamLambdaInvocationEvent.json @@ -0,0 +1,24 @@ +{ + "requestContext": { + "requestId": "316aa6d0-8154-xmpl-9af7-85d5f4a6bc81", + "functionArn": "arn:aws:lambda:us-east-2:123456789012:function:myfunction", + "condition": "RetryAttemptsExhausted", + "approximateInvokeCount": 1 + }, + "responseContext": { + "statusCode": 200, + "executedVersion": "$LATEST", + "functionError": "Unhandled" + }, + "version": "1.0", + "timestamp": "2019-11-14T00:13:49Z", + "DDBStreamBatchInfo": { + "shardId": "shardId-00000001573689847184-864758bb", + "startSequenceNumber": "800000000003126276362", + "endSequenceNumber": "800000000003126276362", + "approximateArrivalOfFirstRecord": "2019-11-14T00:13:19Z", + "approximateArrivalOfLastRecord": "2019-11-14T00:13:19Z", + "batchSize": 1, + "streamArn": "arn:aws:dynamodb:us-east-2:123456789012:table/mytable/stream/2019-11-14T00:04:06.388" + } +} diff --git a/tests/unit/parser/_pydantic/test_dynamodb.py b/tests/unit/parser/_pydantic/test_dynamodb.py index 940f7ad3776..13ee5610e6d 100644 --- a/tests/unit/parser/_pydantic/test_dynamodb.py +++ b/tests/unit/parser/_pydantic/test_dynamodb.py @@ -1,6 +1,7 @@ import pytest from aws_lambda_powertools.utilities.parser import ValidationError, envelopes, parse +from aws_lambda_powertools.utilities.parser.models import DynamoDBStreamLambdaOnFailureDestinationModel from tests.functional.utils import load_event from tests.unit.parser._pydantic.schemas import MyAdvancedDynamoBusiness, MyDynamoBusiness @@ -83,3 +84,33 @@ def test_validate_event_does_not_conform_with_model(): raw_event: dict = {"hello": "s"} with pytest.raises(ValidationError): parse(event=raw_event, model=MyDynamoBusiness, envelope=envelopes.DynamoDBStreamEnvelope) + + +def test_dynamo_db_stream_lambda_invocation_event(): + raw_event = load_event("dynamoStreamLambdaInvocationEvent.json") + parsed_event: DynamoDBStreamLambdaOnFailureDestinationModel = parse( + event=raw_event, + model=DynamoDBStreamLambdaOnFailureDestinationModel, + ) + assert ( + parsed_event.ddb_stream_batch_info.approximate_arrival_of_first_record.strftime("%Y-%m-%dT%H:%M:%SZ") + == raw_event["DDBStreamBatchInfo"]["approximateArrivalOfFirstRecord"] + ) + assert ( + parsed_event.ddb_stream_batch_info.approximate_arrival_of_last_record.strftime("%Y-%m-%dT%H:%M:%SZ") + == raw_event["DDBStreamBatchInfo"]["approximateArrivalOfLastRecord"] + ) + assert parsed_event.ddb_stream_batch_info.batch_size == raw_event["DDBStreamBatchInfo"]["batchSize"] + assert ( + parsed_event.ddb_stream_batch_info.end_sequence_number == raw_event["DDBStreamBatchInfo"]["endSequenceNumber"] + ) + assert parsed_event.ddb_stream_batch_info.shard_id == raw_event["DDBStreamBatchInfo"]["shardId"] + assert ( + parsed_event.ddb_stream_batch_info.start_sequence_number + == raw_event["DDBStreamBatchInfo"]["startSequenceNumber"] + ) + assert parsed_event.ddb_stream_batch_info.stream_arn == raw_event["DDBStreamBatchInfo"]["streamArn"] + assert parsed_event.request_context.model_dump(by_alias=True) == raw_event["requestContext"] + assert parsed_event.response_context.model_dump(by_alias=True) == raw_event["responseContext"] + assert parsed_event.timestamp.strftime("%Y-%m-%dT%H:%M:%SZ") == raw_event["timestamp"] + assert parsed_event.version == raw_event["version"]