Skip to content

Commit

Permalink
fix(data_class): ensure DynamoDBStreamEvent conforms to decimal limits (
Browse files Browse the repository at this point in the history
#4863)

* fix(utilities): DDB Large numbers

Signed-off-by: Simon Thulbourn <sthulb@users.noreply.github.com>

* rename var

* add unit test for large numbers

* remove leading 0s too

* Small refactor

---------

Signed-off-by: Simon Thulbourn <sthulb@users.noreply.github.com>
Co-authored-by: Leandro Damascena <lcdama@amazon.pt>
  • Loading branch information
sthulb and leandrodamascena authored Aug 2, 2024
1 parent 4d249ac commit f763d0f
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ def _deserialize_bool(self, value: bool) -> bool:
return value

def _deserialize_n(self, value: str) -> Decimal:
value = value.lstrip("0")

This comment has been minimized.

Copy link
@arielbeckjit

arielbeckjit Aug 13, 2024

@sthulb are you sure this did not introduce a bug?
i'm seeing { "N": "0"} parsed as NaN because the 0 is removed to a {"N": ""}

This comment has been minimized.

Copy link
@arielbeckjit

arielbeckjit Aug 13, 2024

Yes it did
(downgraded to 2.41.0 and zeros are parsed well)

This comment has been minimized.

Copy link
@leandrodamascena

leandrodamascena Aug 13, 2024

Author Contributor

Hey @arielbeckjit! We release a patch version yesterday to fix it.

v2.43.1 fix this problem.
https://pypi.org/project/aws-lambda-powertools/

This comment has been minimized.

Copy link
@arielbeckjit

arielbeckjit Aug 13, 2024

Ah great, Thanks!

This comment has been minimized.

if len(value) > 38:
# See: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Number
# Calculate the number of trailing zeros after the 38th character
tail = len(value[38:]) - len(value[38:].rstrip("0"))
# Trim the value: remove trailing zeros if any, or just take the first 38 characters
value = value[:-tail] if tail > 0 else value[:38]

return DYNAMODB_CONTEXT.create_decimal(value)

def _deserialize_s(self, value: str) -> str:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
)
from tests.functional.utils import load_event

DECIMAL_CONTEXT = Context(
Emin=-128,
Emax=126,
prec=38,
traps=[Clamped, Overflow, Inexact, Rounded, Underflow],
)


def test_dynamodb_stream_trigger_event():
decimal_context = Context(
Emin=-128,
Emax=126,
prec=38,
traps=[Clamped, Overflow, Inexact, Rounded, Underflow],
)

raw_event = load_event("dynamoStreamEvent.json")
parsed_event = DynamoDBStreamEvent(raw_event)
Expand All @@ -36,14 +37,44 @@ def test_dynamodb_stream_trigger_event():
assert dynamodb.approximate_creation_date_time == record_raw["dynamodb"]["ApproximateCreationDateTime"]
keys = dynamodb.keys
assert keys is not None
assert keys["Id"] == decimal_context.create_decimal(101)
assert keys["Id"] == DECIMAL_CONTEXT.create_decimal(101)
assert dynamodb.new_image.get("Message") == record_raw["dynamodb"]["NewImage"]["Message"]["S"]
assert dynamodb.old_image is None
assert dynamodb.sequence_number == record_raw["dynamodb"]["SequenceNumber"]
assert dynamodb.size_bytes == record_raw["dynamodb"]["SizeBytes"]
assert dynamodb.stream_view_type == StreamViewType.NEW_AND_OLD_IMAGES


def test_dynamodb_stream_record_deserialization_large_int():
data = {
"Keys": {"key1": {"attr1": "value1"}},
"NewImage": {
"Name": {"S": "Joe"},
"Age": {"N": "000000011011111111111111000000000000000000000000000000"},
},
}
record = StreamRecord(data)
assert record.new_image == {
"Name": "Joe",
"Age": DECIMAL_CONTEXT.create_decimal("11011111111111111000000000000000000000"),
}


def test_dynamodb_stream_record_deserialization_large_int_without_trailing_zeros():
data = {
"Keys": {"key1": {"attr1": "value1"}},
"NewImage": {
"Name": {"S": "Joe"},
"Age": {"N": "000000011011111111111112222222222221111111111111111111111"},
},
}
record = StreamRecord(data)
assert record.new_image == {
"Name": "Joe",
"Age": DECIMAL_CONTEXT.create_decimal("11011111111111112222222222221111111111"),
}


def test_dynamodb_stream_record_deserialization():
byte_list = [s.encode("utf-8") for s in ["item1", "item2"]]
decimal_context = Context(
Expand Down

0 comments on commit f763d0f

Please sign in to comment.