Skip to content

Commit a65e55c

Browse files
author
Michael Brewer
authored
feat(data-classes): add AttributeValueType to DynamoDBStreamEvent (#462)
1 parent b029b5c commit a65e55c

File tree

2 files changed

+165
-2
lines changed

2 files changed

+165
-2
lines changed

aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py

+75-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,42 @@
11
from enum import Enum
2-
from typing import Dict, Iterator, List, Optional
2+
from typing import Any, Dict, Iterator, List, Optional, Union
33

44
from aws_lambda_powertools.utilities.data_classes.common import DictWrapper
55

66

7+
class AttributeValueType(Enum):
8+
Binary = "B"
9+
BinarySet = "BS"
10+
Boolean = "BOOL"
11+
List = "L"
12+
Map = "M"
13+
Number = "N"
14+
NumberSet = "NS"
15+
Null = "NULL"
16+
String = "S"
17+
StringSet = "SS"
18+
19+
720
class AttributeValue(DictWrapper):
821
"""Represents the data for an attribute
922
10-
Documentation: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_streams_AttributeValue.html
23+
Documentation:
24+
--------------
25+
- https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_streams_AttributeValue.html
26+
- https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html
1127
"""
1228

29+
def __init__(self, data: Dict[str, Any]):
30+
"""AttributeValue constructor
31+
32+
Parameters
33+
----------
34+
data: Dict[str, Any]
35+
Raw lambda event dict
36+
"""
37+
super().__init__(data)
38+
self.dynamodb_type = list(data.keys())[0]
39+
1340
@property
1441
def b_value(self) -> Optional[str]:
1542
"""An attribute of type Base64-encoded binary data object
@@ -106,6 +133,29 @@ def ss_value(self) -> Optional[List[str]]:
106133
"""
107134
return self.get("SS")
108135

136+
@property
137+
def get_type(self) -> AttributeValueType:
138+
"""Get the attribute value type based on the contained data"""
139+
return AttributeValueType(self.dynamodb_type)
140+
141+
@property
142+
def l_value(self) -> Optional[List["AttributeValue"]]:
143+
"""Alias of list_value"""
144+
return self.list_value
145+
146+
@property
147+
def m_value(self) -> Optional[Dict[str, "AttributeValue"]]:
148+
"""Alias of map_value"""
149+
return self.map_value
150+
151+
@property
152+
def get_value(self) -> Union[Optional[bool], Optional[str], Optional[List], Optional[Dict]]:
153+
"""Get the attribute value"""
154+
try:
155+
return getattr(self, f"{self.dynamodb_type.lower()}_value")
156+
except AttributeError:
157+
raise TypeError(f"Dynamodb type {self.dynamodb_type} is not supported")
158+
109159

110160
def _attribute_value_dict(attr_values: Dict[str, dict], key: str) -> Optional[Dict[str, AttributeValue]]:
111161
"""A dict of type String to AttributeValue object map
@@ -224,6 +274,29 @@ class DynamoDBStreamEvent(DictWrapper):
224274
Documentation:
225275
-------------
226276
- https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html
277+
278+
Example
279+
-------
280+
**Process dynamodb stream events and use get_type and get_value for handling conversions**
281+
282+
from aws_lambda_powertools.utilities.data_classes import event_source, DynamoDBStreamEvent
283+
from aws_lambda_powertools.utilities.data_classes.dynamo_db_stream_event import (
284+
AttributeValueType,
285+
AttributeValue,
286+
)
287+
from aws_lambda_powertools.utilities.typing import LambdaContext
288+
289+
290+
@event_source(data_class=DynamoDBStreamEvent)
291+
def lambda_handler(event: DynamoDBStreamEvent, context: LambdaContext):
292+
for record in event.records:
293+
key: AttributeValue = record.dynamodb.keys["id"]
294+
if key == AttributeValueType.Number:
295+
assert key.get_value == key.n_value
296+
print(key.get_value)
297+
elif key == AttributeValueType.Map:
298+
assert key.get_value == key.map_value
299+
print(key.get_value)
227300
"""
228301

229302
@property

tests/functional/test_data_classes.py

+90
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
)
5959
from aws_lambda_powertools.utilities.data_classes.dynamo_db_stream_event import (
6060
AttributeValue,
61+
AttributeValueType,
6162
DynamoDBRecordEventName,
6263
DynamoDBStreamEvent,
6364
StreamViewType,
@@ -443,13 +444,43 @@ def test_dynamo_db_stream_trigger_event():
443444
assert record.user_identity is None
444445

445446

447+
def test_dynamo_attribute_value_b_value():
448+
example_attribute_value = {"B": "dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk"}
449+
450+
attribute_value = AttributeValue(example_attribute_value)
451+
452+
assert attribute_value.get_type == AttributeValueType.Binary
453+
assert attribute_value.b_value == attribute_value.get_value
454+
455+
456+
def test_dynamo_attribute_value_bs_value():
457+
example_attribute_value = {"BS": ["U3Vubnk=", "UmFpbnk=", "U25vd3k="]}
458+
459+
attribute_value = AttributeValue(example_attribute_value)
460+
461+
assert attribute_value.get_type == AttributeValueType.BinarySet
462+
assert attribute_value.bs_value == attribute_value.get_value
463+
464+
465+
def test_dynamo_attribute_value_bool_value():
466+
example_attribute_value = {"BOOL": True}
467+
468+
attribute_value = AttributeValue(example_attribute_value)
469+
470+
assert attribute_value.get_type == AttributeValueType.Boolean
471+
assert attribute_value.bool_value == attribute_value.get_value
472+
473+
446474
def test_dynamo_attribute_value_list_value():
447475
example_attribute_value = {"L": [{"S": "Cookies"}, {"S": "Coffee"}, {"N": "3.14159"}]}
448476
attribute_value = AttributeValue(example_attribute_value)
449477
list_value = attribute_value.list_value
450478
assert list_value is not None
451479
item = list_value[0]
452480
assert item.s_value == "Cookies"
481+
assert attribute_value.get_type == AttributeValueType.List
482+
assert attribute_value.l_value == attribute_value.list_value
483+
assert attribute_value.list_value == attribute_value.get_value
453484

454485

455486
def test_dynamo_attribute_value_map_value():
@@ -461,6 +492,65 @@ def test_dynamo_attribute_value_map_value():
461492
assert map_value is not None
462493
item = map_value["Name"]
463494
assert item.s_value == "Joe"
495+
assert attribute_value.get_type == AttributeValueType.Map
496+
assert attribute_value.m_value == attribute_value.map_value
497+
assert attribute_value.map_value == attribute_value.get_value
498+
499+
500+
def test_dynamo_attribute_value_n_value():
501+
example_attribute_value = {"N": "123.45"}
502+
503+
attribute_value = AttributeValue(example_attribute_value)
504+
505+
assert attribute_value.get_type == AttributeValueType.Number
506+
assert attribute_value.n_value == attribute_value.get_value
507+
508+
509+
def test_dynamo_attribute_value_ns_value():
510+
example_attribute_value = {"NS": ["42.2", "-19", "7.5", "3.14"]}
511+
512+
attribute_value = AttributeValue(example_attribute_value)
513+
514+
assert attribute_value.get_type == AttributeValueType.NumberSet
515+
assert attribute_value.ns_value == attribute_value.get_value
516+
517+
518+
def test_dynamo_attribute_value_null_value():
519+
example_attribute_value = {"NULL": True}
520+
521+
attribute_value = AttributeValue(example_attribute_value)
522+
523+
assert attribute_value.get_type == AttributeValueType.Null
524+
assert attribute_value.null_value == attribute_value.get_value
525+
526+
527+
def test_dynamo_attribute_value_s_value():
528+
example_attribute_value = {"S": "Hello"}
529+
530+
attribute_value = AttributeValue(example_attribute_value)
531+
532+
assert attribute_value.get_type == AttributeValueType.String
533+
assert attribute_value.s_value == attribute_value.get_value
534+
535+
536+
def test_dynamo_attribute_value_ss_value():
537+
example_attribute_value = {"SS": ["Giraffe", "Hippo", "Zebra"]}
538+
539+
attribute_value = AttributeValue(example_attribute_value)
540+
541+
assert attribute_value.get_type == AttributeValueType.StringSet
542+
assert attribute_value.ss_value == attribute_value.get_value
543+
544+
545+
def test_dynamo_attribute_value_type_error():
546+
example_attribute_value = {"UNSUPPORTED": "'value' should raise a type error"}
547+
548+
attribute_value = AttributeValue(example_attribute_value)
549+
550+
with pytest.raises(TypeError):
551+
print(attribute_value.get_value)
552+
with pytest.raises(ValueError):
553+
print(attribute_value.get_type)
464554

465555

466556
def test_event_bridge_event():

0 commit comments

Comments
 (0)