|  | 
| 2 | 2 | from datetime import datetime | 
| 3 | 3 | from typing import Any, Dict, List, Literal, Optional, Type, Union | 
| 4 | 4 | 
 | 
| 5 |  | -from pydantic import BaseModel, field_validator | 
|  | 5 | +from pydantic import BaseModel, Field, field_validator | 
| 6 | 6 | 
 | 
| 7 | 7 | from aws_lambda_powertools.shared.dynamodb_deserializer import TypeDeserializer | 
| 8 | 8 | 
 | 
| 9 | 9 | _DESERIALIZER = TypeDeserializer() | 
| 10 | 10 | 
 | 
| 11 | 11 | 
 | 
| 12 | 12 | class DynamoDBStreamChangedRecordModel(BaseModel): | 
| 13 |  | -    ApproximateCreationDateTime: Optional[datetime] = None | 
| 14 |  | -    Keys: Dict[str, Any] | 
| 15 |  | -    NewImage: Optional[Union[Dict[str, Any], Type[BaseModel], BaseModel]] = None | 
| 16 |  | -    OldImage: Optional[Union[Dict[str, Any], Type[BaseModel], BaseModel]] = None | 
| 17 |  | -    SequenceNumber: str | 
| 18 |  | -    SizeBytes: int | 
| 19 |  | -    StreamViewType: Literal["NEW_AND_OLD_IMAGES", "KEYS_ONLY", "NEW_IMAGE", "OLD_IMAGE"] | 
| 20 |  | - | 
| 21 |  | -    # context on why it's commented: https://github.com/aws-powertools/powertools-lambda-python/pull/118 | 
| 22 |  | -    # since both images are optional, they can both be None. However, at least one must | 
| 23 |  | -    # exist in a legal model of NEW_AND_OLD_IMAGES type | 
| 24 |  | -    # @root_validator | 
| 25 |  | -    # def check_one_image_exists(cls, values): # noqa: ERA001 | 
| 26 |  | -    #     new_img, old_img = values.get("NewImage"), values.get("OldImage") # noqa: ERA001 | 
| 27 |  | -    #     stream_type = values.get("StreamViewType") # noqa: ERA001 | 
| 28 |  | -    #     if stream_type == "NEW_AND_OLD_IMAGES" and not new_img and not old_img: # noqa: ERA001 | 
| 29 |  | -    #         raise TypeError("DynamoDB streams model failed validation, missing both new & old stream images") # noqa: ERA001,E501 | 
| 30 |  | -    #     return values # noqa: ERA001 | 
|  | 13 | +    ApproximateCreationDateTime: Optional[datetime] = Field(  # AWS sends this as Unix epoch float | 
|  | 14 | +        default=None, | 
|  | 15 | +        description="The approximate date and time when the stream record was created (Unix epoch time).", | 
|  | 16 | +        examples=[1693997155.0], | 
|  | 17 | +    ) | 
|  | 18 | +    Keys: Dict[str, Any] = Field(description="Primary key attributes for the item.", examples=[{"Id": {"N": "101"}}]) | 
|  | 19 | +    NewImage: Optional[Union[Dict[str, Any], Type[BaseModel], BaseModel]] = Field( | 
|  | 20 | +        default=None, | 
|  | 21 | +        description="The item after modifications, in DynamoDB attribute-value format.", | 
|  | 22 | +        examples=[{"Message": {"S": "New item!"}, "Id": {"N": "101"}}], | 
|  | 23 | +    ) | 
|  | 24 | +    OldImage: Optional[Union[Dict[str, Any], Type[BaseModel], BaseModel]] = Field( | 
|  | 25 | +        default=None, | 
|  | 26 | +        description="The item before modifications, in DynamoDB attribute-value format.", | 
|  | 27 | +        examples=[{"Message": {"S": "Old item!"}, "Id": {"N": "100"}}], | 
|  | 28 | +    ) | 
|  | 29 | +    SequenceNumber: str = Field(description="A unique identifier for the stream record.", examples=["222"]) | 
|  | 30 | +    SizeBytes: int = Field(description="The size of the stream record, in bytes.", examples=[26]) | 
|  | 31 | +    StreamViewType: Literal["NEW_AND_OLD_IMAGES", "KEYS_ONLY", "NEW_IMAGE", "OLD_IMAGE"] = Field( | 
|  | 32 | +        description="The type of data included in the stream record.", | 
|  | 33 | +        examples=["NEW_AND_OLD_IMAGES"], | 
|  | 34 | +    ) | 
| 31 | 35 | 
 | 
| 32 | 36 |     @field_validator("Keys", "NewImage", "OldImage", mode="before") | 
| 33 | 37 |     def deserialize_field(cls, value): | 
| 34 | 38 |         return {k: _DESERIALIZER.deserialize(v) for k, v in value.items()} | 
| 35 | 39 | 
 | 
| 36 | 40 | 
 | 
| 37 | 41 | class UserIdentity(BaseModel): | 
| 38 |  | -    type: Literal["Service"]  # noqa: VNE003, A003 | 
| 39 |  | -    principalId: Literal["dynamodb.amazonaws.com"] | 
|  | 42 | +    type: Literal["Service"] = Field( | 
|  | 43 | +        description="The type of identity that made the request, which is always 'Service' for DynamoDB streams.", | 
|  | 44 | +        examples=["Service"], | 
|  | 45 | +    ) | 
|  | 46 | +    principalId: Literal["dynamodb.amazonaws.com"] = Field( | 
|  | 47 | +        description="The unique identifier for the principal that made the request.", | 
|  | 48 | +        examples=["dynamodb.amazonaws.com"], | 
|  | 49 | +    ) | 
| 40 | 50 | 
 | 
| 41 | 51 | 
 | 
| 42 | 52 | class DynamoDBStreamRecordModel(BaseModel): | 
| 43 |  | -    eventID: str | 
| 44 |  | -    eventName: Literal["INSERT", "MODIFY", "REMOVE"] | 
| 45 |  | -    eventVersion: float | 
| 46 |  | -    eventSource: Literal["aws:dynamodb"] | 
| 47 |  | -    awsRegion: str | 
| 48 |  | -    eventSourceARN: str | 
| 49 |  | -    dynamodb: DynamoDBStreamChangedRecordModel | 
| 50 |  | -    userIdentity: Optional[UserIdentity] = None | 
|  | 53 | +    eventID: str = Field(description="A unique identifier for the event.", examples=["1"]) | 
|  | 54 | +    eventName: Literal["INSERT", "MODIFY", "REMOVE"] = Field( | 
|  | 55 | +        description="The type of operation that was performed on the item.", | 
|  | 56 | +        examples=["INSERT"], | 
|  | 57 | +    ) | 
|  | 58 | +    eventVersion: float = Field(description="The version of the stream record format.", examples=["1.0"]) | 
|  | 59 | +    eventSource: Literal["aws:dynamodb"] = Field( | 
|  | 60 | +        description="The source of the event, which is always 'aws:dynamodb' for DynamoDB streams.", | 
|  | 61 | +        examples=["aws:dynamodb"], | 
|  | 62 | +    ) | 
|  | 63 | +    awsRegion: str = Field(description="The AWS region where the stream record was generated.", examples=["us-west-2"]) | 
|  | 64 | +    eventSourceARN: str = Field( | 
|  | 65 | +        description="The Amazon Resource Name (ARN) of the DynamoDB stream.", | 
|  | 66 | +        examples=["arn:aws:dynamodb:us-west-2:123456789012:table/ExampleTable/stream/2021-01-01T00:00:00.000"], | 
|  | 67 | +    ) | 
|  | 68 | +    dynamodb: DynamoDBStreamChangedRecordModel = Field( | 
|  | 69 | +        description="Contains the details of the DynamoDB stream record.", | 
|  | 70 | +        examples=[ | 
|  | 71 | +            { | 
|  | 72 | +                "ApproximateCreationDateTime": 1693997155.0, | 
|  | 73 | +                "Keys": {"Id": {"N": "101"}}, | 
|  | 74 | +                "NewImage": {"Message": {"S": "New item!"}, "Id": {"N": "101"}}, | 
|  | 75 | +                "OldImage": {"Message": {"S": "Old item!"}, "Id": {"N": "100"}}, | 
|  | 76 | +                "SequenceNumber": "222", | 
|  | 77 | +                "SizeBytes": 26, | 
|  | 78 | +                "StreamViewType": "NEW_AND_OLD_IMAGES", | 
|  | 79 | +            }, | 
|  | 80 | +        ], | 
|  | 81 | +    ) | 
|  | 82 | +    userIdentity: Optional[UserIdentity] = Field( | 
|  | 83 | +        default=None, | 
|  | 84 | +        description="Information about the identity that made the request.", | 
|  | 85 | +        examples=[{"type": "Service", "principalId": "dynamodb.amazonaws.com"}], | 
|  | 86 | +    ) | 
| 51 | 87 | 
 | 
| 52 | 88 | 
 | 
| 53 | 89 | class DynamoDBStreamModel(BaseModel): | 
| 54 |  | -    Records: List[DynamoDBStreamRecordModel] | 
|  | 90 | +    Records: List[DynamoDBStreamRecordModel] = Field( | 
|  | 91 | +        description="A list of records that contain the details of the DynamoDB stream events.", | 
|  | 92 | +        examples=[ | 
|  | 93 | +            { | 
|  | 94 | +                "eventID": "1", | 
|  | 95 | +                "eventName": "INSERT", | 
|  | 96 | +                "eventVersion": "1.0", | 
|  | 97 | +                "eventSource": "aws:dynamodb", | 
|  | 98 | +                "awsRegion": "us-west-2", | 
|  | 99 | +                "eventSourceARN": "arn:aws:dynamodb:us-west-2:123456789012:table/ExampleTable/stream/2021-01-01T00:00:00.000",  # noqa E501 | 
|  | 100 | +                "dynamodb": { | 
|  | 101 | +                    "ApproximateCreationDateTime": 1693997155.0, | 
|  | 102 | +                    "Keys": {"Id": {"N": "101"}}, | 
|  | 103 | +                    "NewImage": {"Message": {"S": "New item!"}, "Id": {"N": "101"}}, | 
|  | 104 | +                    "OldImage": {"Message": {"S": "Old item!"}, "Id": {"N": "100"}}, | 
|  | 105 | +                    "SequenceNumber": "222", | 
|  | 106 | +                    "SizeBytes": 26, | 
|  | 107 | +                    "StreamViewType": "NEW_AND_OLD_IMAGES", | 
|  | 108 | +                }, | 
|  | 109 | +                "userIdentity": {"type": "Service", "principalId": "dynamodb.amazonaws.com"}, | 
|  | 110 | +            }, | 
|  | 111 | +        ], | 
|  | 112 | +    ) | 
0 commit comments