From b7c46ae7bfc30f460c5db10d23f1051bf82c54b8 Mon Sep 17 00:00:00 2001 From: Steve Pulec Date: Fri, 29 Mar 2013 17:45:33 -0400 Subject: [PATCH] fix S3 last_modified. Closes #8 --- moto/s3/models.py | 22 ++++++++++++++++++++-- moto/s3/responses.py | 12 ++++++------ tests/test_s3/test_s3.py | 18 ++++++++++++++++++ 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/moto/s3/models.py b/moto/s3/models.py index d80eec417..d8cd06103 100644 --- a/moto/s3/models.py +++ b/moto/s3/models.py @@ -1,5 +1,4 @@ -# from boto.s3.bucket import Bucket -# from boto.s3.key import Key +import datetime import md5 from moto.core import BaseBackend @@ -9,6 +8,7 @@ class FakeKey(object): def __init__(self, name, value): self.name = name self.value = value + self.last_modified = datetime.datetime.now() @property def etag(self): @@ -16,6 +16,24 @@ class FakeKey(object): value_md5.update(self.value) return '"{0}"'.format(value_md5.hexdigest()) + @property + def last_modified_ISO8601(self): + return self.last_modified.strftime("%Y-%m-%dT%H:%M:%SZ") + + @property + def last_modified_RFC1123(self): + # Different datetime formats depending on how the key is obtained + # https://github.com/boto/boto/issues/466 + RFC1123 = '%a, %d %b %Y %H:%M:%S GMT' + return self.last_modified.strftime(RFC1123) + + @property + def response_dict(self): + return { + 'etag': self.etag, + 'last-modified': self.last_modified_RFC1123, + } + @property def size(self): return len(self.value) diff --git a/moto/s3/responses.py b/moto/s3/responses.py index 5f1be0fbb..bc3669bc7 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -90,14 +90,14 @@ def key_response(uri_info, method, body, headers): # empty string as part of closing the connection. new_key = s3_backend.set_key(bucket_name, key_name, body) template = Template(S3_OBJECT_RESPONSE) - return template.render(key=new_key), dict(etag=new_key.etag) + return template.render(key=new_key), new_key.response_dict key = s3_backend.get_key(bucket_name, key_name) if key: - return "", dict(etag=key.etag) + return "", key.response_dict elif method == 'HEAD': key = s3_backend.get_key(bucket_name, key_name) if key: - return S3_OBJECT_RESPONSE, dict(etag=key.etag) + return S3_OBJECT_RESPONSE, key.response_dict else: return "", dict(status=404) elif method == 'DELETE': @@ -133,7 +133,7 @@ S3_BUCKET_GET_RESPONSE = """ {% for key in result_keys %} {{ key.name }} - 2006-01-01T12:00:00.000Z + {{ key.last_modified_ISO8601 }} {{ key.etag }} {{ key.size }} STANDARD @@ -190,13 +190,13 @@ S3_DELETE_OBJECT_SUCCESS = """ {{ key.etag }} - 2006-03-01T12:00:00.183Z + {{ key.last_modified_ISO8601 }} """ S3_OBJECT_COPY_RESPONSE = """ {{ key.etag }} - 2008-02-18T13:54:10.183Z + {{ key.last_modified_ISO8601 }} """ diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index 278ce9e2f..1c8c4c8a8 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -1,8 +1,10 @@ +import datetime import urllib2 import boto from boto.exception import S3ResponseError from boto.s3.key import Key +from freezegun import freeze_time import requests import sure # flake8: noqa @@ -90,6 +92,22 @@ def test_copy_key(): bucket.get_key("new-key").get_contents_as_string().should.equal("some value") +@freeze_time("2012-01-01 12:00:00") +@mock_s3 +def test_last_modified(): + # See https://github.com/boto/boto/issues/466 + conn = boto.connect_s3() + bucket = conn.create_bucket("foobar") + key = Key(bucket) + key.key = "the-key" + key.set_contents_from_string("some value") + + rs = bucket.get_all_keys() + rs[0].last_modified.should.equal('2012-01-01T12:00:00Z') + + bucket.get_key("the-key").last_modified.should.equal('Sun, 01 Jan 2012 12:00:00 GMT') + + @mock_s3 def test_get_all_keys(): conn = boto.connect_s3('the_key', 'the_secret')