diff --git a/boto3/dynamodb/types.py b/boto3/dynamodb/types.py index f358b12f55..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, @@ -211,10 +212,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 @@ -286,7 +287,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..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): @@ -206,3 +225,16 @@ 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) + 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}