'DynamoDBNumberError on trying to insert floating point number using python boto library

Code snippet :

conn = dynamo_connect()

company = Table("companydb",connection=conn)

companyrecord = {'company-slug':'www-google-com12','founding-year':1991, 'randomlist' :[1,2,3,4,5], 'randomdict' : {'a':[1,2,3],'b':'something','randomnumber':10.55} }

company.put_item(data=companyrecord)

I am getting the following error:

File "C:\Python27\lib\site-packages\boto\dynamodb2\items.py", line 329, in prepare_full
    final_data[key] = self._dynamizer.encode(value)
  File "C:\Python27\lib\site-packages\boto\dynamodb\types.py", line 279, in encode
    return {dynamodb_type: encoder(attr)}
  File "C:\Python27\lib\site-packages\boto\dynamodb\types.py", line 335, in _encode_m
    return dict([(k, self.encode(v)) for k, v in attr.items()])
  File "C:\Python27\lib\site-packages\boto\dynamodb\types.py", line 279, in encode
    return {dynamodb_type: encoder(attr)}
  File "C:\Python27\lib\site-packages\boto\dynamodb\types.py", line 305, in _encode_n
    raise DynamoDBNumberError(msg)
boto.dynamodb.exceptions.DynamoDBNumberError: BotoClientError: Inexact numeric for `10.55`


Solution 1:[1]

Yes There are Known issues on GitHub related to floating numbers, There may be 2 workarounds , First if you are comfortable to store 10.5 instead of 10.55, then it will just work fine I guess, The another is to store the floating value as String or integer and later modulate it while accessing.

So of you chose the string part then you could store it as '10.55' instead of 10.55 and later when you access the values from the table then you could simply use float("10.55") and you will be done.

Another method is to store it as an integer , First choose a precision value (say 2 decimal values) then you will store 10.55 as 1055(multiplied by 100, since 2 decimal precision), and while accessing it you could have simply used 1055/100.0 and you will get 10.55.

Solution 2:[2]

If you are working on a larger set and want to avoid record-wise processing to convert decimal, this would help.

from decimal import Decimal

import json

changed_data = json.loads(json.dumps(data), parse_float=Decimal)

Solution 3:[3]

Use Decimal(str(your_number)) instead. See https://github.com/boto/boto3/issues/665

Solution 4:[4]

here is my solution for inserting the float number through override the typeSerializer.

from boto3.dynamodb import types
from decimal import Decimal, localcontext
import re

class CustomSerializer(types.TypeSerializer):

    def _is_number(self, value):
        if isinstance(value, (int, Decimal, float)):
            return True
      return False

# Add float-specific serialization code
def _serialize_n(self, value):
    if isinstance(value, float):
        with localcontext(types.DYNAMODB_CONTEXT) as context:
            stringify_val = str(value)
            number = str(context.create_decimal(stringify_val))
            return number

    number = super(CustomSerializer, self)._serialize_n(value)
    return number

serializer = CustomSerializer()
serialized_data = serializer.serialize(data)

Solution 5:[5]

Python3 offers float.hex() / .fromhex() to store floats as string without loosing precision:

Two methods support conversion to and from hexadecimal strings. Since Python’s floats are stored internally as binary numbers, converting a float to or from a decimal string usually involves a small rounding error. In contrast, hexadecimal strings allow exact representation and specification of floating-point numbers. This can be useful when debugging, and in numerical work.

If you don't want to loose any precision, this might be an alternative to @ZdaR's solution using str() and float() for conversion.

[Note: I'm a new user and couldn't comment on ZdaR's solution]

Solution 6:[6]

Just in case that it works for somebody.

I had a structure like this:

{
    "hash_key": 1,
    "range_key": 2,
    "values": {
        "valueA": 1.2,
        "valueB": 2.1,
        "valueC": 3.1,
        "valueD": 4.1,
        "valueE": 5.1,
        "valueF": 6.1
    }
}

I had this dictionary in an object called parameters Extra note; I had this validation because not all the time I receive the values key:

values = parameters['values'] if 'values' in parameters else {}

if values: # So... this made the trick:
    parameters['values'] = [{key: str(value)} for key, value in values.items()]

This transformation made the work.

Also I'm using

boto3.resource('dynamodb')
table = dynamo.Table(TABLE_NAME)
table.put_item(Item=parameters, ReturnValues='ALL_OLD')

The full function looks like this:

def create_item(parameters):
    dynamo = boto3.resource('dynamodb')
    table = dynamo.Table('your_table_name')

    parameters['hash_key'] = str(parameters['hash_key'])
    parameters['range_key'] = str(parameters['range_key'])
    values = parameters['values'] if 'values' in parameters else {}

    if values:
        parameters['values'] = [{key: str(value)} for key, value in values.items()]

    try:
        response = table.put_item(Item=parameters, ReturnValues='ALL_OLD')
    except ClientError as e:
        response = e.response['Error']
        # this is a local function to give format to the error
        return create_response(response['Message'], response)
    return response

Not sure if this is a tldr case, hope it helps c:

Solution 7:[7]

You need to parse your float value into Decimal

from datetime import datetime
from decimal import Decimal

decimal_value = Decimal(datetime.utcnow().timestamp())

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 ZdaR
Solution 2 Unterbelichtet
Solution 3 kk1957
Solution 4
Solution 5 Community
Solution 6 Jesus Walker
Solution 7 Dev Sareno