Skip to content

Commit 066be2d

Browse files
authored
feat: download_to_filename deletes the empty file on a 404 (#1394)
1 parent 6aef9b7 commit 066be2d

File tree

3 files changed

+47
-2
lines changed

3 files changed

+47
-2
lines changed

README.rst

+2
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ Miscellaneous
9999
- Retry behavior is now identical between media operations (uploads and
100100
downloads) and other operations, and custom predicates are now supported for
101101
media operations as well.
102+
- Blob.download_as_filename() will now delete the empty file if it results in a
103+
google.cloud.exceptions.NotFound exception (HTTP 404).
102104

103105
Quick Start
104106
-----------

google/cloud/storage/blob.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1252,8 +1252,8 @@ def _handle_filename_and_download(self, filename, *args, **kwargs):
12521252
**kwargs,
12531253
)
12541254

1255-
except DataCorruption:
1256-
# Delete the corrupt downloaded file.
1255+
except (DataCorruption, NotFound):
1256+
# Delete the corrupt or empty downloaded file.
12571257
os.remove(filename)
12581258
raise
12591259

tests/unit/test_blob.py

+43
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import mock
2828
import pytest
2929

30+
from google.cloud.exceptions import NotFound
3031
from google.cloud.storage import _helpers
3132
from google.cloud.storage._helpers import _get_default_headers
3233
from google.cloud.storage._helpers import _get_default_storage_base_url
@@ -1848,6 +1849,48 @@ def test_download_to_filename_corrupted(self):
18481849
stream = blob._prep_and_do_download.mock_calls[0].args[0]
18491850
self.assertEqual(stream.name, filename)
18501851

1852+
def test_download_to_filename_notfound(self):
1853+
blob_name = "blob-name"
1854+
client = self._make_client()
1855+
bucket = _Bucket(client)
1856+
blob = self._make_one(blob_name, bucket=bucket)
1857+
1858+
with mock.patch.object(blob, "_prep_and_do_download"):
1859+
blob._prep_and_do_download.side_effect = NotFound("testing")
1860+
1861+
# Try to download into a temporary file (don't use
1862+
# `_NamedTemporaryFile` it will try to remove after the file is
1863+
# already removed)
1864+
filehandle, filename = tempfile.mkstemp()
1865+
os.close(filehandle)
1866+
self.assertTrue(os.path.exists(filename))
1867+
1868+
with self.assertRaises(NotFound):
1869+
blob.download_to_filename(filename)
1870+
1871+
# Make sure the file was cleaned up.
1872+
self.assertFalse(os.path.exists(filename))
1873+
1874+
expected_timeout = self._get_default_timeout()
1875+
blob._prep_and_do_download.assert_called_once_with(
1876+
mock.ANY,
1877+
client=None,
1878+
start=None,
1879+
end=None,
1880+
if_etag_match=None,
1881+
if_etag_not_match=None,
1882+
if_generation_match=None,
1883+
if_generation_not_match=None,
1884+
if_metageneration_match=None,
1885+
if_metageneration_not_match=None,
1886+
raw_download=False,
1887+
timeout=expected_timeout,
1888+
checksum="auto",
1889+
retry=DEFAULT_RETRY,
1890+
)
1891+
stream = blob._prep_and_do_download.mock_calls[0].args[0]
1892+
self.assertEqual(stream.name, filename)
1893+
18511894
def _download_as_bytes_helper(self, raw_download, timeout=None, **extra_kwargs):
18521895
blob_name = "blob-name"
18531896
client = self._make_client()

0 commit comments

Comments
 (0)