DynamoDB: boto3 doesn't work with the local server: timestamp parsing problems
See original GitHub issueSoftware versions:
Python 2.7.15 boto3 1.7.41 botocore 1.10.41 Windows 10
Using the local server, as obtained from here.
The problem appears to be that the local server returns zero timestamps for the “LastIncreaseDateTime” and “LastDecreaseDateTime” properties of the “ProvisionedThroughput” property in the response, and boto3 chokes on it. I have tracked the problem as far as the botocore parse_timestamp function. It wants to convert to the local time zone. I am on the US eastern time zone. I think those AWS timestamps are to be interpreted as epoch seconds, i.e. seconds since 1/1/1970 00:00:00 UTC. That means a zero timestamp converts to a local date/time before the epoch. I.e. at 1/1/1970 00:00:00 UTC, in my timezone, it’s still 1969, and some internal values in the dateutil implementation go negative. Some time-related functions don’t like being passed a negative number.
So here’s a proposed fix: don’t try to convert to the local time zone. Leave them as UTC. I tried changing those tzlocal()
calls to tzutc()
, and voila, problem solved.
Here’s a simple demo to show the problem:
import boto3
import logging
logging.basicConfig(
level="DEBUG"
)
aws_auth = {
"aws_access_key_id": "a_secret_key_id",
"aws_secret_access_key": "a_secret_access_key",
"endpoint_url": "http://localhost:8000",
"region_name": "elbonia-1"
}
ddb = boto3.resource(
"dynamodb",
**aws_auth
)
my_table = ddb.create_table(
TableName="MyTable",
AttributeDefinitions=[
{
"AttributeName": "id",
"AttributeType": "S"
}
],
KeySchema=[
{
"AttributeName": "id",
"KeyType": "HASH"
}
],
ProvisionedThroughput={
"ReadCapacityUnits": 10,
"WriteCapacityUnits": 10
}
)
The stack trace:
Traceback (most recent call last):
File "C:/Programming/python/test/boto_bug/demo_bug.py", line 36, in <module>
"WriteCapacityUnits": 10
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\boto3\resources\factory.py", line 520, in do_action
response = action(self, *args, **kwargs)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\boto3\resources\action.py", line 83, in __call__
response = getattr(parent.meta.client, operation_name)(**params)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\botocore\client.py", line 314, in _api_call
return self._make_api_call(operation_name, kwargs)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\botocore\client.py", line 599, in _make_api_call
operation_model, request_dict)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\botocore\endpoint.py", line 148, in make_request
return self._send_request(request_dict, operation_model)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\botocore\endpoint.py", line 175, in _send_request
request, operation_model, attempts)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\botocore\endpoint.py", line 256, in _get_response
response_dict, operation_model.output_shape)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\botocore\parsers.py", line 242, in parse
parsed = self._do_parse(response, shape)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\botocore\parsers.py", line 633, in _do_parse
parsed = self._parse_shape(shape, original_parsed)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\botocore\parsers.py", line 295, in _parse_shape
return handler(shape, node)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\botocore\parsers.py", line 560, in _handle_structure
raw_value)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\botocore\parsers.py", line 295, in _parse_shape
return handler(shape, node)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\botocore\parsers.py", line 560, in _handle_structure
raw_value)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\botocore\parsers.py", line 295, in _parse_shape
return handler(shape, node)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\botocore\parsers.py", line 560, in _handle_structure
raw_value)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\botocore\parsers.py", line 295, in _parse_shape
return handler(shape, node)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\botocore\parsers.py", line 577, in _handle_timestamp
return self._timestamp_parser(value)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\botocore\utils.py", line 362, in parse_timestamp
return datetime.datetime.fromtimestamp(value, tzlocal())
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\dateutil\tz\_common.py", line 140, in fromutc
return f(self, dt)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\dateutil\tz\_common.py", line 254, in fromutc
dt_wall = self._fromutc(dt)
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\dateutil\tz\_common.py", line 234, in _fromutc
dtdst = enfold(dt, fold=1).dst()
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\dateutil\tz\_common.py", line 119, in enfold
args = dt.timetuple()[:6]
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\dateutil\tz\tz.py", line 219, in dst
if self._isdst(dt):
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\dateutil\tz\tz.py", line 282, in _isdst
if self.is_ambiguous(dt):
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\dateutil\tz\tz.py", line 244, in is_ambiguous
(naive_dst != self._naive_is_dst(dt - self._dst_saved)))
File "C:\Programming\python\test\boto_bug_venv_py2\lib\site-packages\dateutil\tz\tz.py", line 248, in _naive_is_dst
return time.localtime(timestamp + time.timezone).tm_isdst
ValueError: (22, 'Invalid argument')
Here is the actual response I got from the local Dynamo server
{
"TableDescription": {
"AttributeDefinitions": [
{
"AttributeName": "id",
"AttributeType": "S"
}
],
"TableName": "MyTable",
"KeySchema": [
{
"AttributeName": "id",
"KeyType": "HASH"
}
],
"TableStatus": "ACTIVE",
"CreationDateTime": 1529528737.279,
"ProvisionedThroughput": {
"LastIncreaseDateTime": 0.000,
"LastDecreaseDateTime": 0.000,
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 10,
"WriteCapacityUnits": 10
},
"TableSizeBytes": 0,
"ItemCount": 0,
"TableArn": "arn:aws:dynamodb:ddblocal:000000000000:table/MyTable"
}
}
Issue Analytics
- State:
- Created 5 years ago
- Reactions:4
- Comments:19 (4 by maintainers)
Top GitHub Comments
r4nyc’s solution doesn’t work in the UK (timezone 0). The catch all soln is a change in tz.py (I found returning False if time is the epoch in is_ambiguous() works well). However, for a botocore workaround, the following works: in Utils.py, parse_timestamp()
if isinstance(value, (int, float)): # Possibly an epoch time. if value + time.altzone < 0: return datetime.datetime.utcfromtimestamp(int(value)) else: return datetime.datetime.fromtimestamp(value, tzlocal())
AWS libraries often pass in the int 0 when the value isn’t set. That may not seem like a crazy value but it is enough to cause an exception on Windows if your timezone causes time.timezone tor return a positive value.
Internally fromtimestamp tries subtracting the time.timezone from the time when it is determining if daylight savings time is in effect. The library python calls on windows can’t handle an epoch value less than 0. Setting the min_time to time.timezone * 2 guarantees the value will not go negative as it tries variations,
You can see the error easily on a Windows machine with the following code import datetime from dateutil.tz import tzlocal datetime.datetime.fromtimestamp(0, tzlocal())
site-packages\dateutil\tz\tz.py", line 248, in _naive_is_dst return time.localtime(timestamp + time.timezone).tm_isdst OSError: [Errno 22] Invalid argument
Without a patch this makes many boto scripts unrunnable on windows machines unless you change yoru environment to UTC.