Skip to content

Commit 9f7d6fc

Browse files
committed
Returning native datetime objects for Bucket/Blob time properties.
1 parent 96ee9d3 commit 9f7d6fc

4 files changed

Lines changed: 56 additions & 27 deletions

File tree

gcloud/storage/blob.py

Lines changed: 16 additions & 11 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):
@@ -660,24 +661,28 @@ def time_deleted(self):
660661
661662
See: https://cloud.google.com/storage/docs/json_api/v1/objects
662663
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.
664+
:rtype: :class:`datetime.datetime` or ``NoneType``
665+
:returns: Datetime object parsed from RFC3339 valid timestamp, or
666+
``None`` if the property is not set locally. If the blob has
667+
not been deleted, this will never be set.
667668
"""
668-
return self._properties.get('timeDeleted')
669+
value = self._properties.get('timeDeleted')
670+
if value is not None:
671+
return datetime.datetime.strptime(value, _GOOGLE_TIMESTAMP_FORMAT)
669672

670673
@property
671674
def updated(self):
672675
"""Retrieve the timestamp at which the object was updated.
673676
674677
See: https://cloud.google.com/storage/docs/json_api/v1/objects
675678
676-
:rtype: string or ``NoneType``
677-
:returns: RFC3339 valid timestamp, or ``None`` if the property is not
678-
set locally.
679+
:rtype: :class:`datetime.datetime` or ``NoneType``
680+
:returns: Datetime object parsed from RFC3339 valid timestamp, or
681+
``None`` if the property is not set locally.
679682
"""
680-
return self._properties.get('updated')
683+
value = self._properties.get('updated')
684+
if value is not None:
685+
return datetime.datetime.strptime(value, _GOOGLE_TIMESTAMP_FORMAT)
681686

682687

683688
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):
@@ -672,11 +674,13 @@ def time_created(self):
672674
673675
See: https://cloud.google.com/storage/docs/json_api/v1/buckets
674676
675-
:rtype: string or ``NoneType``
676-
:returns: RFC3339 valid timestamp, or ``None`` if the property is not
677-
set locally.
677+
:rtype: :class:`datetime.datetime` or ``NoneType``
678+
:returns: Datetime object parsed from RFC3339 valid timestamp, or
679+
``None`` if the property is not set locally.
678680
"""
679-
return self._properties.get('timeCreated')
681+
value = self._properties.get('timeCreated')
682+
if value is not None:
683+
return datetime.datetime.strptime(value, _GOOGLE_TIMESTAMP_FORMAT)
680684

681685
@property
682686
def versioning_enabled(self):

gcloud/storage/test_blob.py

Lines changed: 24 additions & 10 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):
@@ -990,22 +990,36 @@ def test_storage_class(self):
990990
self.assertEqual(blob.storage_class, STORAGE_CLASS)
991991

992992
def test_time_deleted(self):
993+
import datetime
993994
BLOB_NAME = 'blob-name'
994995
connection = _Connection()
995996
bucket = _Bucket(connection)
996-
TIME_DELETED = '2014-11-05T20:34:37Z'
997+
TIME_DELETED = '2014-11-05T20:34:37.000Z'
998+
TIMESTAMP = datetime.datetime(2014, 11, 5, 20, 34, 37)
997999
properties = {'timeDeleted': TIME_DELETED}
9981000
blob = self._makeOne(BLOB_NAME, bucket=bucket, properties=properties)
999-
self.assertEqual(blob.time_deleted, TIME_DELETED)
1001+
self.assertEqual(blob.time_deleted, TIMESTAMP)
1002+
1003+
def test_time_deleted_unset(self):
1004+
BUCKET = object()
1005+
blob = self._makeOne('blob-name', bucket=BUCKET)
1006+
self.assertEqual(blob.time_deleted, None)
10001007

10011008
def test_updated(self):
1009+
import datetime
10021010
BLOB_NAME = 'blob-name'
10031011
connection = _Connection()
10041012
bucket = _Bucket(connection)
1005-
UPDATED = '2014-11-05T20:34:37Z'
1013+
UPDATED = '2014-11-05T20:34:37.000Z'
1014+
TIMESTAMP = datetime.datetime(2014, 11, 5, 20, 34, 37)
10061015
properties = {'updated': UPDATED}
10071016
blob = self._makeOne(BLOB_NAME, bucket=bucket, properties=properties)
1008-
self.assertEqual(blob.updated, UPDATED)
1017+
self.assertEqual(blob.updated, TIMESTAMP)
1018+
1019+
def test_updated_unset(self):
1020+
BUCKET = object()
1021+
blob = self._makeOne('blob-name', bucket=BUCKET)
1022+
self.assertEqual(blob.updated, None)
10091023

10101024

10111025
class _Responder(object):

gcloud/storage/test_bucket.py

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

870870
def test_time_created(self):
871-
TIME_CREATED = '2014-11-05T20:34:37Z'
871+
import datetime
872+
TIME_CREATED = '2014-11-05T20:34:37.000Z'
873+
TIMESTAMP = datetime.datetime(2014, 11, 5, 20, 34, 37)
872874
properties = {'timeCreated': TIME_CREATED}
873875
bucket = self._makeOne(properties=properties)
874-
self.assertEqual(bucket.time_created, TIME_CREATED)
876+
self.assertEqual(bucket.time_created, TIMESTAMP)
877+
878+
def test_time_created_unset(self):
879+
bucket = self._makeOne()
880+
self.assertEqual(bucket.time_created, None)
875881

876882
def test_versioning_enabled_getter_missing(self):
877883
NAME = 'name'

0 commit comments

Comments
 (0)