'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 |