Skip to content

Commit 2645e1d

Browse files
committed
Returning native datetime objects for Bucket/Blob time properties.
1 parent 1cdcc6d commit 2645e1d

File tree

4 files changed

+58
-38
lines changed

4 files changed

+58
-38
lines changed

gcloud/storage/blob.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@
1515
"""Create / interact with Google Cloud Storage blobs."""
1616

1717
import copy
18+
import datetime
19+
from io import BytesIO
1820
import json
1921
import mimetypes
2022
import os
2123
import time
22-
import datetime
23-
from io import BytesIO
2424

2525
import six
2626
from six.moves.urllib.parse import quote # pylint: disable=F0401
@@ -37,6 +37,7 @@
3737

3838

3939
_API_ACCESS_ENDPOINT = 'https://storage.googleapis.com'
40+
_GOOGLE_TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
4041

4142

4243
class Blob(_PropertyMixin):
@@ -248,11 +249,7 @@ def download_to_filename(self, filename):
248249
with open(filename, 'wb') as file_obj:
249250
self.download_to_file(file_obj)
250251

251-
mtime = time.mktime(
252-
datetime.datetime.strptime(
253-
self._properties['updated'],
254-
'%Y-%m-%dT%H:%M:%S.%fz').timetuple()
255-
)
252+
mtime = time.mktime(self.updated.timetuple())
256253
os.utime(file_obj.name, (mtime, mtime))
257254

258255
def download_as_string(self):
@@ -660,24 +657,28 @@ def time_deleted(self):
660657
661658
See: https://cloud.google.com/storage/docs/json_api/v1/objects
662659
663-
:rtype: string or ``NoneType``
664-
:returns: RFC3339 valid timestamp, or ``None`` if the property is not
665-
set locally. If the blob has not been deleted, this will
666-
never be set.
660+
:rtype: :class:`datetime.datetime` or ``NoneType``
661+
:returns: Datetime object parsed from RFC3339 valid timestamp, or
662+
``None`` if the property is not set locally. If the blob has
663+
not been deleted, this will never be set.
667664
"""
668-
return self._properties.get('timeDeleted')
665+
value = self._properties.get('timeDeleted')
666+
if value is not None:
667+
return datetime.datetime.strptime(value, _GOOGLE_TIMESTAMP_FORMAT)
669668

670669
@property
671670
def updated(self):
672671
"""Retrieve the timestamp at which the object was updated.
673672
674673
See: https://cloud.google.com/storage/docs/json_api/v1/objects
675674
676-
:rtype: string or ``NoneType``
677-
:returns: RFC3339 valid timestamp, or ``None`` if the property is not
678-
set locally.
675+
:rtype: :class:`datetime.datetime` or ``NoneType``
676+
:returns: Datetime object parsed from RFC3339 valid timestamp, or
677+
``None`` if the property is not set locally.
679678
"""
680-
return self._properties.get('updated')
679+
value = self._properties.get('updated')
680+
if value is not None:
681+
return datetime.datetime.strptime(value, _GOOGLE_TIMESTAMP_FORMAT)
681682

682683

683684
class _UploadConfig(object):

gcloud/storage/bucket.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
... print blob
3434
"""
3535

36+
import datetime
3637
import copy
3738
import os
3839
import six
@@ -45,6 +46,7 @@
4546
from gcloud.storage.acl import DefaultObjectACL
4647
from gcloud.storage.iterator import Iterator
4748
from gcloud.storage.blob import Blob
49+
from gcloud.storage.blob import _GOOGLE_TIMESTAMP_FORMAT
4850

4951

5052
class _BlobIterator(Iterator):
@@ -684,11 +686,13 @@ def time_created(self):
684686
685687
See: https://cloud.google.com/storage/docs/json_api/v1/buckets
686688
687-
:rtype: string or ``NoneType``
688-
:returns: RFC3339 valid timestamp, or ``None`` if the property is not
689-
set locally.
689+
:rtype: :class:`datetime.datetime` or ``NoneType``
690+
:returns: Datetime object parsed from RFC3339 valid timestamp, or
691+
``None`` if the property is not set locally.
690692
"""
691-
return self._properties.get('timeCreated')
693+
value = self._properties.get('timeCreated')
694+
if value is not None:
695+
return datetime.datetime.strptime(value, _GOOGLE_TIMESTAMP_FORMAT)
692696

693697
@property
694698
def versioning_enabled(self):

gcloud/storage/test_blob.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,12 @@ def test_generate_signed_url_w_default_method(self):
125125
from gcloud.storage import blob as MUT
126126

127127
BLOB_NAME = 'blob-name'
128-
EXPIRATION = '2014-10-16T20:34:37Z'
128+
EXPIRATION = '2014-10-16T20:34:37.000Z'
129129
connection = _Connection()
130130
bucket = _Bucket(connection)
131131
blob = self._makeOne(BLOB_NAME, bucket=bucket)
132132
URI = ('http://example.com/abucket/a-blob-name?Signature=DEADBEEF'
133-
'&Expiration=2014-10-16T20:34:37Z')
133+
'&Expiration=2014-10-16T20:34:37.000Z')
134134

135135
SIGNER = _Signer()
136136
with _Monkey(MUT, generate_signed_url=SIGNER):
@@ -151,12 +151,12 @@ def test_generate_signed_url_w_slash_in_name(self):
151151
from gcloud.storage import blob as MUT
152152

153153
BLOB_NAME = 'parent/child'
154-
EXPIRATION = '2014-10-16T20:34:37Z'
154+
EXPIRATION = '2014-10-16T20:34:37.000Z'
155155
connection = _Connection()
156156
bucket = _Bucket(connection)
157157
blob = self._makeOne(BLOB_NAME, bucket=bucket)
158158
URI = ('http://example.com/abucket/a-blob-name?Signature=DEADBEEF'
159-
'&Expiration=2014-10-16T20:34:37Z')
159+
'&Expiration=2014-10-16T20:34:37.000Z')
160160

161161
SIGNER = _Signer()
162162
with _Monkey(MUT, generate_signed_url=SIGNER):
@@ -176,12 +176,12 @@ def test_generate_signed_url_w_explicit_method(self):
176176
from gcloud.storage import blob as MUT
177177

178178
BLOB_NAME = 'blob-name'
179-
EXPIRATION = '2014-10-16T20:34:37Z'
179+
EXPIRATION = '2014-10-16T20:34:37.000Z'
180180
connection = _Connection()
181181
bucket = _Bucket(connection)
182182
blob = self._makeOne(BLOB_NAME, bucket=bucket)
183183
URI = ('http://example.com/abucket/a-blob-name?Signature=DEADBEEF'
184-
'&Expiration=2014-10-16T20:34:37Z')
184+
'&Expiration=2014-10-16T20:34:37.000Z')
185185

186186
SIGNER = _Signer()
187187
with _Monkey(MUT, generate_signed_url=SIGNER):
@@ -267,7 +267,6 @@ def test_download_to_file(self):
267267
def test_download_to_filename(self):
268268
import os
269269
import time
270-
import datetime
271270
from six.moves.http_client import OK
272271
from six.moves.http_client import PARTIAL_CONTENT
273272
from tempfile import NamedTemporaryFile
@@ -292,11 +291,7 @@ def test_download_to_filename(self):
292291
with open(f.name, 'rb') as g:
293292
wrote = g.read()
294293
mtime = os.path.getmtime(f.name)
295-
updatedTime = time.mktime(
296-
datetime.datetime.strptime(
297-
blob._properties['updated'],
298-
'%Y-%m-%dT%H:%M:%S.%fz').timetuple()
299-
)
294+
updatedTime = time.mktime(blob.updated.timetuple())
300295
self.assertEqual(wrote, b'abcdef')
301296
self.assertEqual(mtime, updatedTime)
302297

@@ -990,22 +985,36 @@ def test_storage_class(self):
990985
self.assertEqual(blob.storage_class, STORAGE_CLASS)
991986

992987
def test_time_deleted(self):
988+
import datetime
993989
BLOB_NAME = 'blob-name'
994990
connection = _Connection()
995991
bucket = _Bucket(connection)
996-
TIME_DELETED = '2014-11-05T20:34:37Z'
992+
TIMESTAMP = datetime.datetime(2014, 11, 5, 20, 34, 37)
993+
TIME_DELETED = TIMESTAMP.isoformat() + '.000Z'
997994
properties = {'timeDeleted': TIME_DELETED}
998995
blob = self._makeOne(BLOB_NAME, bucket=bucket, properties=properties)
999-
self.assertEqual(blob.time_deleted, TIME_DELETED)
996+
self.assertEqual(blob.time_deleted, TIMESTAMP)
997+
998+
def test_time_deleted_unset(self):
999+
BUCKET = object()
1000+
blob = self._makeOne('blob-name', bucket=BUCKET)
1001+
self.assertEqual(blob.time_deleted, None)
10001002

10011003
def test_updated(self):
1004+
import datetime
10021005
BLOB_NAME = 'blob-name'
10031006
connection = _Connection()
10041007
bucket = _Bucket(connection)
1005-
UPDATED = '2014-11-05T20:34:37Z'
1008+
TIMESTAMP = datetime.datetime(2014, 11, 5, 20, 34, 37)
1009+
UPDATED = TIMESTAMP.isoformat() + '.000Z'
10061010
properties = {'updated': UPDATED}
10071011
blob = self._makeOne(BLOB_NAME, bucket=bucket, properties=properties)
1008-
self.assertEqual(blob.updated, UPDATED)
1012+
self.assertEqual(blob.updated, TIMESTAMP)
1013+
1014+
def test_updated_unset(self):
1015+
BUCKET = object()
1016+
blob = self._makeOne('blob-name', bucket=BUCKET)
1017+
self.assertEqual(blob.updated, None)
10091018

10101019

10111020
class _Responder(object):

gcloud/storage/test_bucket.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -856,10 +856,16 @@ def test_storage_class(self):
856856
self.assertEqual(bucket.storage_class, STORAGE_CLASS)
857857

858858
def test_time_created(self):
859-
TIME_CREATED = '2014-11-05T20:34:37Z'
859+
import datetime
860+
TIMESTAMP = datetime.datetime(2014, 11, 5, 20, 34, 37)
861+
TIME_CREATED = TIMESTAMP.isoformat() + '.000Z'
860862
properties = {'timeCreated': TIME_CREATED}
861863
bucket = self._makeOne(properties=properties)
862-
self.assertEqual(bucket.time_created, TIME_CREATED)
864+
self.assertEqual(bucket.time_created, TIMESTAMP)
865+
866+
def test_time_created_unset(self):
867+
bucket = self._makeOne()
868+
self.assertEqual(bucket.time_created, None)
863869

864870
def test_versioning_enabled_getter_missing(self):
865871
NAME = 'name'

0 commit comments

Comments
 (0)