Skip to content

Commit f763d0f

Browse files
fix(data_class): ensure DynamoDBStreamEvent conforms to decimal limits (#4863)
* fix(utilities): DDB Large numbers Signed-off-by: Simon Thulbourn <[email protected]> * rename var * add unit test for large numbers * remove leading 0s too * Small refactor --------- Signed-off-by: Simon Thulbourn <[email protected]> Co-authored-by: Leandro Damascena <[email protected]>
1 parent 4d249ac commit f763d0f

File tree

2 files changed

+46
-7
lines changed

2 files changed

+46
-7
lines changed

aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py

+8
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ def _deserialize_bool(self, value: bool) -> bool:
7373
return value
7474

7575
def _deserialize_n(self, value: str) -> Decimal:
76+
value = value.lstrip("0")
77+
if len(value) > 38:
78+
# See: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number
79+
# Calculate the number of trailing zeros after the 38th character
80+
tail = len(value[38:]) - len(value[38:].rstrip("0"))
81+
# Trim the value: remove trailing zeros if any, or just take the first 38 characters
82+
value = value[:-tail] if tail > 0 else value[:38]
83+
7684
return DYNAMODB_CONTEXT.create_decimal(value)
7785

7886
def _deserialize_s(self, value: str) -> str:

tests/unit/data_classes/required_dependencies/test_dynamo_db_stream_event.py

+38-7
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88
)
99
from tests.functional.utils import load_event
1010

11+
DECIMAL_CONTEXT = Context(
12+
Emin=-128,
13+
Emax=126,
14+
prec=38,
15+
traps=[Clamped, Overflow, Inexact, Rounded, Underflow],
16+
)
17+
1118

1219
def test_dynamodb_stream_trigger_event():
13-
decimal_context = Context(
14-
Emin=-128,
15-
Emax=126,
16-
prec=38,
17-
traps=[Clamped, Overflow, Inexact, Rounded, Underflow],
18-
)
1920

2021
raw_event = load_event("dynamoStreamEvent.json")
2122
parsed_event = DynamoDBStreamEvent(raw_event)
@@ -36,14 +37,44 @@ def test_dynamodb_stream_trigger_event():
3637
assert dynamodb.approximate_creation_date_time == record_raw["dynamodb"]["ApproximateCreationDateTime"]
3738
keys = dynamodb.keys
3839
assert keys is not None
39-
assert keys["Id"] == decimal_context.create_decimal(101)
40+
assert keys["Id"] == DECIMAL_CONTEXT.create_decimal(101)
4041
assert dynamodb.new_image.get("Message") == record_raw["dynamodb"]["NewImage"]["Message"]["S"]
4142
assert dynamodb.old_image is None
4243
assert dynamodb.sequence_number == record_raw["dynamodb"]["SequenceNumber"]
4344
assert dynamodb.size_bytes == record_raw["dynamodb"]["SizeBytes"]
4445
assert dynamodb.stream_view_type == StreamViewType.NEW_AND_OLD_IMAGES
4546

4647

48+
def test_dynamodb_stream_record_deserialization_large_int():
49+
data = {
50+
"Keys": {"key1": {"attr1": "value1"}},
51+
"NewImage": {
52+
"Name": {"S": "Joe"},
53+
"Age": {"N": "000000011011111111111111000000000000000000000000000000"},
54+
},
55+
}
56+
record = StreamRecord(data)
57+
assert record.new_image == {
58+
"Name": "Joe",
59+
"Age": DECIMAL_CONTEXT.create_decimal("11011111111111111000000000000000000000"),
60+
}
61+
62+
63+
def test_dynamodb_stream_record_deserialization_large_int_without_trailing_zeros():
64+
data = {
65+
"Keys": {"key1": {"attr1": "value1"}},
66+
"NewImage": {
67+
"Name": {"S": "Joe"},
68+
"Age": {"N": "000000011011111111111112222222222221111111111111111111111"},
69+
},
70+
}
71+
record = StreamRecord(data)
72+
assert record.new_image == {
73+
"Name": "Joe",
74+
"Age": DECIMAL_CONTEXT.create_decimal("11011111111111112222222222221111111111"),
75+
}
76+
77+
4778
def test_dynamodb_stream_record_deserialization():
4879
byte_list = [s.encode("utf-8") for s in ["item1", "item2"]]
4980
decimal_context = Context(

0 commit comments

Comments
 (0)