boto3: DynamoDB: boto3 doesn't work with the local server: timestamp parsing problems

Software 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"
    }
}

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 4
  • Comments: 19 (4 by maintainers)

Most upvoted 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.

Been experiencing the same issue following a local Dynamodb tutorial on my windows 10 machine. SimonFrost solution corrected the issue.

To fix this make the following change to parse_timestamp in botocore\utils.py

def parse_timestamp(value): “”“Parse a timestamp into a datetime object. Supported formats: * iso8601 * rfc822 * epoch (value is an integer) This will return a datetime.datetime object. “”” if isinstance(value, (int, float)): # Possibly an epoch time. #NEW CODE min_time = time.timezone * 2 value = min_time if value < min_time else value #END NEW CODE return datetime.datetime.fromtimestamp(value, tzlocal())

Unfortunately, we can’t remove converting to local time as that would be a breaking change.

As a workaround you can either set your localtime to UTC or add the following before importing boto3 in your script:

import os
os.environ["TZ"] = "UTC"

Let me know if that helps.