From bbd0320552bfd9a7e105f9111ef6505fd318694c Mon Sep 17 00:00:00 2001 From: jonathan343 Date: Tue, 13 Jan 2026 23:37:01 -0500 Subject: [PATCH 1/3] Fix DynamoDB number deserialization for large numbers with trailing zeros --- boto3/dynamodb/types.py | 2 +- tests/unit/dynamodb/test_types.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/boto3/dynamodb/types.py b/boto3/dynamodb/types.py index f358b12f55..88b5a61e1a 100644 --- a/boto3/dynamodb/types.py +++ b/boto3/dynamodb/types.py @@ -286,7 +286,7 @@ def _deserialize_bool(self, value): return value def _deserialize_n(self, value): - return DYNAMODB_CONTEXT.create_decimal(value) + return Decimal(value) def _deserialize_s(self, value): return value diff --git a/tests/unit/dynamodb/test_types.py b/tests/unit/dynamodb/test_types.py index 0a6c2a07f3..10731c8638 100644 --- a/tests/unit/dynamodb/test_types.py +++ b/tests/unit/dynamodb/test_types.py @@ -206,3 +206,10 @@ def test_deserialize_map(self): } } ) == {'foo': 'mystring', 'bar': {'baz': Decimal('1')}} + + def test_deserialize_large_number_with_trailing_zeros(self): + # Numbers with trailing zeros that exceed 38 total digits but have + # <=38 significant digits are valid in DynamoDB and must deserialize. + large_num = '1234567895171680000000000000000000000000' + result = self.deserializer.deserialize({'N': large_num}) + assert result == Decimal(large_num) From 1c8acb1269d328000efa17155f1c4458cec04d95 Mon Sep 17 00:00:00 2001 From: jonathan343 Date: Fri, 20 Mar 2026 15:56:28 -0400 Subject: [PATCH 2/3] Fix DynamoDB trailing-zero number handling in serializer --- boto3/dynamodb/types.py | 6 +++--- tests/unit/dynamodb/test_types.py | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/boto3/dynamodb/types.py b/boto3/dynamodb/types.py index 88b5a61e1a..85c7d44ce8 100644 --- a/boto3/dynamodb/types.py +++ b/boto3/dynamodb/types.py @@ -211,10 +211,10 @@ def _serialize_bool(self, value): return value def _serialize_n(self, value): - number = str(DYNAMODB_CONTEXT.create_decimal(value)) - if number in ['Infinity', 'NaN']: + decimal_value = Decimal(value) + if decimal_value.is_nan() or decimal_value.is_infinite(): raise TypeError('Infinity and NaN not supported') - return number + return str(decimal_value) def _serialize_s(self, value): return value diff --git a/tests/unit/dynamodb/test_types.py b/tests/unit/dynamodb/test_types.py index 10731c8638..903da07ee7 100644 --- a/tests/unit/dynamodb/test_types.py +++ b/tests/unit/dynamodb/test_types.py @@ -85,6 +85,10 @@ def test_serialize_NaN_error(self): with pytest.raises(TypeError, match=r'Infinity and NaN not supported'): self.serializer.serialize(Decimal('NaN')) + def test_serialize_infinity_error(self): + with pytest.raises(TypeError, match=r'Infinity and NaN not supported'): + self.serializer.serialize(Decimal('-Infinity')) + def test_serialize_string(self): assert self.serializer.serialize('foo') == {'S': 'foo'} @@ -143,6 +147,21 @@ def test_serialize_map(self): 'M': {'foo': {'S': 'bar'}, 'baz': {'M': {'biz': {'N': '1'}}}} } + def test_serialize_large_number_with_trailing_zeros(self): + assert self.serializer.serialize( + 1234567895171680000000000000000000000000 + ) == {'N': '1234567895171680000000000000000000000000'} + + def test_serialize_large_decimal_with_trailing_zeros(self): + assert self.serializer.serialize( + Decimal('1234567895171680000000000000000000000000') + ) == {'N': '1234567895171680000000000000000000000000'} + + def test_serialize_large_decimal_defers_validation_to_dynamodb(self): + assert self.serializer.serialize(Decimal('9' * 39)) == { + 'N': '999999999999999999999999999999999999999' + } + class TestDeserializer(unittest.TestCase): def setUp(self): @@ -213,3 +232,9 @@ def test_deserialize_large_number_with_trailing_zeros(self): large_num = '1234567895171680000000000000000000000000' result = self.deserializer.deserialize({'N': large_num}) assert result == Decimal(large_num) + assert str(result) == large_num + + def test_deserialize_and_serialize_large_number_with_trailing_zeros(self): + large_num = '1234567895171680000000000000000000000000' + result = self.deserializer.deserialize({'N': large_num}) + assert TypeSerializer().serialize(result) == {'N': large_num} From d27d2755c24581728c04279d4871eb1aa525d05e Mon Sep 17 00:00:00 2001 From: jonathan343 Date: Fri, 20 Mar 2026 16:05:53 -0400 Subject: [PATCH 3/3] add comment for DYNAMODB_CONTEXT --- boto3/dynamodb/types.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/boto3/dynamodb/types.py b/boto3/dynamodb/types.py index 85c7d44ce8..a3f989e8f6 100644 --- a/boto3/dynamodb/types.py +++ b/boto3/dynamodb/types.py @@ -33,7 +33,8 @@ MAP = 'M' LIST = 'L' - +# Retained for backwards compatibility; serializer/deserializer no longer use +# this context for number handling. DYNAMODB_CONTEXT = Context( Emin=-128, Emax=126,