From 9f7d6fcbc6b9843162e25d5cbf9038375c3dd6f1 Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Tue, 7 Apr 2015 17:15:29 -0700 Subject: [PATCH] Returning native datetime objects for Bucket/Blob time properties. --- gcloud/storage/blob.py | 27 ++++++++++++++++----------- gcloud/storage/bucket.py | 12 ++++++++---- gcloud/storage/test_blob.py | 34 ++++++++++++++++++++++++---------- gcloud/storage/test_bucket.py | 10 ++++++++-- 4 files changed, 56 insertions(+), 27 deletions(-) diff --git a/gcloud/storage/blob.py b/gcloud/storage/blob.py index ff5aef4f9f4b..61afa642d99f 100644 --- a/gcloud/storage/blob.py +++ b/gcloud/storage/blob.py @@ -15,12 +15,12 @@ """Create / interact with Google Cloud Storage blobs.""" import copy +import datetime +from io import BytesIO import json import mimetypes import os import time -import datetime -from io import BytesIO import six from six.moves.urllib.parse import quote # pylint: disable=F0401 @@ -37,6 +37,7 @@ _API_ACCESS_ENDPOINT = 'https://storage.googleapis.com' +_GOOGLE_TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ' class Blob(_PropertyMixin): @@ -660,12 +661,14 @@ def time_deleted(self): See: https://cloud.google.com/storage/docs/json_api/v1/objects - :rtype: string or ``NoneType`` - :returns: RFC3339 valid timestamp, or ``None`` if the property is not - set locally. If the blob has not been deleted, this will - never be set. + :rtype: :class:`datetime.datetime` or ``NoneType`` + :returns: Datetime object parsed from RFC3339 valid timestamp, or + ``None`` if the property is not set locally. If the blob has + not been deleted, this will never be set. """ - return self._properties.get('timeDeleted') + value = self._properties.get('timeDeleted') + if value is not None: + return datetime.datetime.strptime(value, _GOOGLE_TIMESTAMP_FORMAT) @property def updated(self): @@ -673,11 +676,13 @@ def updated(self): See: https://cloud.google.com/storage/docs/json_api/v1/objects - :rtype: string or ``NoneType`` - :returns: RFC3339 valid timestamp, or ``None`` if the property is not - set locally. + :rtype: :class:`datetime.datetime` or ``NoneType`` + :returns: Datetime object parsed from RFC3339 valid timestamp, or + ``None`` if the property is not set locally. """ - return self._properties.get('updated') + value = self._properties.get('updated') + if value is not None: + return datetime.datetime.strptime(value, _GOOGLE_TIMESTAMP_FORMAT) class _UploadConfig(object): diff --git a/gcloud/storage/bucket.py b/gcloud/storage/bucket.py index 49e0dcae5459..8ce2bed7e90b 100644 --- a/gcloud/storage/bucket.py +++ b/gcloud/storage/bucket.py @@ -33,6 +33,7 @@ ... print blob """ +import datetime import copy import os import six @@ -45,6 +46,7 @@ from gcloud.storage.acl import DefaultObjectACL from gcloud.storage.iterator import Iterator from gcloud.storage.blob import Blob +from gcloud.storage.blob import _GOOGLE_TIMESTAMP_FORMAT class _BlobIterator(Iterator): @@ -672,11 +674,13 @@ def time_created(self): See: https://cloud.google.com/storage/docs/json_api/v1/buckets - :rtype: string or ``NoneType`` - :returns: RFC3339 valid timestamp, or ``None`` if the property is not - set locally. + :rtype: :class:`datetime.datetime` or ``NoneType`` + :returns: Datetime object parsed from RFC3339 valid timestamp, or + ``None`` if the property is not set locally. """ - return self._properties.get('timeCreated') + value = self._properties.get('timeCreated') + if value is not None: + return datetime.datetime.strptime(value, _GOOGLE_TIMESTAMP_FORMAT) @property def versioning_enabled(self): diff --git a/gcloud/storage/test_blob.py b/gcloud/storage/test_blob.py index 242477630780..80cfdcf7087f 100644 --- a/gcloud/storage/test_blob.py +++ b/gcloud/storage/test_blob.py @@ -125,12 +125,12 @@ def test_generate_signed_url_w_default_method(self): from gcloud.storage import blob as MUT BLOB_NAME = 'blob-name' - EXPIRATION = '2014-10-16T20:34:37Z' + EXPIRATION = '2014-10-16T20:34:37.000Z' connection = _Connection() bucket = _Bucket(connection) blob = self._makeOne(BLOB_NAME, bucket=bucket) URI = ('http://example.com/abucket/a-blob-name?Signature=DEADBEEF' - '&Expiration=2014-10-16T20:34:37Z') + '&Expiration=2014-10-16T20:34:37.000Z') SIGNER = _Signer() with _Monkey(MUT, generate_signed_url=SIGNER): @@ -151,12 +151,12 @@ def test_generate_signed_url_w_slash_in_name(self): from gcloud.storage import blob as MUT BLOB_NAME = 'parent/child' - EXPIRATION = '2014-10-16T20:34:37Z' + EXPIRATION = '2014-10-16T20:34:37.000Z' connection = _Connection() bucket = _Bucket(connection) blob = self._makeOne(BLOB_NAME, bucket=bucket) URI = ('http://example.com/abucket/a-blob-name?Signature=DEADBEEF' - '&Expiration=2014-10-16T20:34:37Z') + '&Expiration=2014-10-16T20:34:37.000Z') SIGNER = _Signer() with _Monkey(MUT, generate_signed_url=SIGNER): @@ -176,12 +176,12 @@ def test_generate_signed_url_w_explicit_method(self): from gcloud.storage import blob as MUT BLOB_NAME = 'blob-name' - EXPIRATION = '2014-10-16T20:34:37Z' + EXPIRATION = '2014-10-16T20:34:37.000Z' connection = _Connection() bucket = _Bucket(connection) blob = self._makeOne(BLOB_NAME, bucket=bucket) URI = ('http://example.com/abucket/a-blob-name?Signature=DEADBEEF' - '&Expiration=2014-10-16T20:34:37Z') + '&Expiration=2014-10-16T20:34:37.000Z') SIGNER = _Signer() with _Monkey(MUT, generate_signed_url=SIGNER): @@ -990,22 +990,36 @@ def test_storage_class(self): self.assertEqual(blob.storage_class, STORAGE_CLASS) def test_time_deleted(self): + import datetime BLOB_NAME = 'blob-name' connection = _Connection() bucket = _Bucket(connection) - TIME_DELETED = '2014-11-05T20:34:37Z' + TIME_DELETED = '2014-11-05T20:34:37.000Z' + TIMESTAMP = datetime.datetime(2014, 11, 5, 20, 34, 37) properties = {'timeDeleted': TIME_DELETED} blob = self._makeOne(BLOB_NAME, bucket=bucket, properties=properties) - self.assertEqual(blob.time_deleted, TIME_DELETED) + self.assertEqual(blob.time_deleted, TIMESTAMP) + + def test_time_deleted_unset(self): + BUCKET = object() + blob = self._makeOne('blob-name', bucket=BUCKET) + self.assertEqual(blob.time_deleted, None) def test_updated(self): + import datetime BLOB_NAME = 'blob-name' connection = _Connection() bucket = _Bucket(connection) - UPDATED = '2014-11-05T20:34:37Z' + UPDATED = '2014-11-05T20:34:37.000Z' + TIMESTAMP = datetime.datetime(2014, 11, 5, 20, 34, 37) properties = {'updated': UPDATED} blob = self._makeOne(BLOB_NAME, bucket=bucket, properties=properties) - self.assertEqual(blob.updated, UPDATED) + self.assertEqual(blob.updated, TIMESTAMP) + + def test_updated_unset(self): + BUCKET = object() + blob = self._makeOne('blob-name', bucket=BUCKET) + self.assertEqual(blob.updated, None) class _Responder(object): diff --git a/gcloud/storage/test_bucket.py b/gcloud/storage/test_bucket.py index ddd9dc245225..f224d40d7e95 100644 --- a/gcloud/storage/test_bucket.py +++ b/gcloud/storage/test_bucket.py @@ -868,10 +868,16 @@ def test_storage_class(self): self.assertEqual(bucket.storage_class, STORAGE_CLASS) def test_time_created(self): - TIME_CREATED = '2014-11-05T20:34:37Z' + import datetime + TIME_CREATED = '2014-11-05T20:34:37.000Z' + TIMESTAMP = datetime.datetime(2014, 11, 5, 20, 34, 37) properties = {'timeCreated': TIME_CREATED} bucket = self._makeOne(properties=properties) - self.assertEqual(bucket.time_created, TIME_CREATED) + self.assertEqual(bucket.time_created, TIMESTAMP) + + def test_time_created_unset(self): + bucket = self._makeOne() + self.assertEqual(bucket.time_created, None) def test_versioning_enabled_getter_missing(self): NAME = 'name'