Repository: googleapis/google-cloud-python

Issue:
Error when downloading blobs with storage v1.1.0
See build log: https://www.google.com/url?q=https%3A%2F%2Ftravis-ci.org%2FGoogleCloudPlatform%2Fpython-docs-samples%2Fbuilds%2F226935178%23L786&sa=D&sntz=1&usg=AFQjCNG3mWBi07AXjvj70yKUdQ0pI4hJNw


Discussion:
So this is kind of a bug, kind of not a bug, but **some code** needs to change.

TLDR; **My vote for a fix**: make the `Blob.download_to_filename` [file timestamp update][4] optional, dependent on `self.updated is not None`

----

The [old implementation][1] did something somewhat silly. It [called `reload`][2] on the instance if the `media_link` wasn't present, essentially forcing users to incur two round trips.

I "turned off" this "feature" in my fix (and @tseaver [asked][3] why / if that was OK during code review). However, that revealed a (IMO long-standing) bug [elsewhere in the code][4]:

```python
mtime = time.mktime(self.updated.timetuple())
```

i.e. the `Blob.download_to_filename` **assumes** the `updated` property is populated, which **assumes** `reload` has been called (or the `_properties` have been populated in some other way).


[1]: https://github.com/GoogleCloudPlatform/google-cloud-python/blob/storage-1.0.0/storage/google/cloud/storage/blob.py#L349-L371
[2]: https://github.com/GoogleCloudPlatform/google-cloud-python/blob/storage-1.0.0/storage/google/cloud/storage/blob.py#L350-L351
[3]: https://github.com/GoogleCloudPlatform/google-cloud-python/pull/3323#discussion_r113035901
[4]: https://github.com/GoogleCloudPlatform/google-cloud-python/blob/storage-1.1.0/storage/google/cloud/storage/blob.py#L435
> TLDR; My vote for a fix: make the Blob.download_to_filename file timestamp update optional, dependent on self.updated is not None

Fine with me.
Same issue here, is there any workaround or sticking to 1.0.0 'til the fix? All our tests failed miserably because of the upgrade.
@danigosa I'll send out a fix today and we can push a `1.1.1` release. SGTY @jonparrott and @lukesneeringer?

Resolving patch:
diff --git a/storage/google/cloud/storage/blob.py b/storage/google/cloud/storage/blob.py
--- a/storage/google/cloud/storage/blob.py
+++ b/storage/google/cloud/storage/blob.py
@@ -432,8 +432,10 @@ def download_to_filename(self, filename, client=None):
         with open(filename, 'wb') as file_obj:
             self.download_to_file(file_obj, client=client)
 
-        mtime = time.mktime(self.updated.timetuple())
-        os.utime(file_obj.name, (mtime, mtime))
+        updated = self.updated
+        if updated is not None:
+            mtime = time.mktime(updated.timetuple())
+            os.utime(file_obj.name, (mtime, mtime))
 
     def download_as_string(self, client=None):
         """Download the contents of this blob as a string.


Test patch:
diff --git a/storage/tests/unit/test_blob.py b/storage/tests/unit/test_blob.py
--- a/storage/tests/unit/test_blob.py
+++ b/storage/tests/unit/test_blob.py
@@ -545,8 +545,7 @@ def test_download_to_file_default(self):
     def test_download_to_file_with_chunk_size(self):
         self._download_to_file_helper(use_chunks=True)
 
-    @mock.patch('google.auth.transport.requests.AuthorizedSession')
-    def test_download_to_filename(self, fake_session_factory):
+    def _download_to_filename_helper(self, fake_session_factory, updated=None):
         import os
         import time
         from google.cloud._testing import _NamedTemporaryFile
@@ -558,8 +557,10 @@ def test_download_to_filename(self, fake_session_factory):
             _credentials=_make_credentials(), spec=['_credentials'])
         bucket = _Bucket(client)
         media_link = 'http://example.com/media/'
-        properties = {'mediaLink': media_link,
-                      'updated': '2014-12-06T13:13:50.690Z'}
+        properties = {'mediaLink': media_link}
+        if updated is not None:
+            properties['updated'] = updated
+
         blob = self._make_one(blob_name, bucket=bucket, properties=properties)
         # Modify the blob so there there will be 2 chunks of size 3.
         blob._CHUNK_SIZE_MULTIPLE = 1
@@ -569,14 +570,27 @@ def test_download_to_filename(self, fake_session_factory):
             blob.download_to_filename(temp.name)
             with open(temp.name, 'rb') as file_obj:
                 wrote = file_obj.read()
-                mtime = os.path.getmtime(temp.name)
-                updated_time = time.mktime(blob.updated.timetuple())
+                if updated is None:
+                    self.assertIsNone(blob.updated)
+                else:
+                    mtime = os.path.getmtime(temp.name)
+                    updated_time = time.mktime(blob.updated.timetuple())
+                    self.assertEqual(mtime, updated_time)
 
         self.assertEqual(wrote, b'abcdef')
-        self.assertEqual(mtime, updated_time)
 
         self._check_session_mocks(client, fake_session_factory, media_link)
 
+    @mock.patch('google.auth.transport.requests.AuthorizedSession')
+    def test_download_to_filename(self, fake_session_factory):
+        updated = '2014-12-06T13:13:50.690Z'
+        self._download_to_filename_helper(
+            fake_session_factory, updated=updated)
+
+    @mock.patch('google.auth.transport.requests.AuthorizedSession')
+    def test_download_to_filename_wo_updated(self, fake_session_factory):
+        self._download_to_filename_helper(fake_session_factory)
+
     @mock.patch('google.auth.transport.requests.AuthorizedSession')
     def test_download_to_filename_w_key(self, fake_session_factory):
         import os