diff --git a/pillar/api/file_storage_backends/abstract.py b/pillar/api/file_storage_backends/abstract.py index 59c6aa07..480518cc 100644 --- a/pillar/api/file_storage_backends/abstract.py +++ b/pillar/api/file_storage_backends/abstract.py @@ -149,5 +149,9 @@ class Blob(metaclass=abc.ABCMeta): Only performs an actual action on backends that support temporary links. """ + @abc.abstractmethod + def exists(self) -> bool: + """Returns True iff the file exists on the storage backend.""" + Bl = typing.TypeVar('Bl', bound=Blob) diff --git a/pillar/api/file_storage_backends/gcs.py b/pillar/api/file_storage_backends/gcs.py index b9414daf..06845156 100644 --- a/pillar/api/file_storage_backends/gcs.py +++ b/pillar/api/file_storage_backends/gcs.py @@ -180,6 +180,11 @@ class GoogleCloudStorageBlob(Blob): def make_public(self): self.gblob.make_public() + def exists(self) -> bool: + # Reload to get the actual file properties from Google. + self.gblob.reload() + return self.gblob.exists() + def update_file_name(node): """Assign to the CGS blob the same name of the asset node. This way when diff --git a/pillar/api/file_storage_backends/local.py b/pillar/api/file_storage_backends/local.py index b57a5cab..40049770 100644 --- a/pillar/api/file_storage_backends/local.py +++ b/pillar/api/file_storage_backends/local.py @@ -100,3 +100,6 @@ class LocalBlob(Blob): def make_public(self): # No-op on this storage backend. pass + + def exists(self) -> bool: + return self.abspath().exists() diff --git a/tests/test_api/test_file_storage_backends.py b/tests/test_api/test_file_storage_backends.py index 62910f10..af12898b 100644 --- a/tests/test_api/test_file_storage_backends.py +++ b/tests/test_api/test_file_storage_backends.py @@ -94,6 +94,19 @@ class LocalStorageBackendTest(AbstractStorageBackendTest): self.assertEqual('512', resp.headers['Content-Length']) self.assertEqual(file_contents, resp.data) + def test_exists(self): + self.enter_app_context() + test_file, file_contents = self.create_test_file() + + bucket_class = self.storage_backend() + bucket = bucket_class(24 * 'a') + + blob = bucket.blob('somefile.bin') + blob.create_from_file(test_file, content_type='application/octet-stream') + + self.assertTrue(blob.exists()) + self.assertFalse(bucket.blob('ütff-8').exists()) + class MockedGoogleCloudStorageTest(AbstractStorageBackendTest): def storage_backend(self): @@ -114,6 +127,7 @@ class MockedGoogleCloudStorageTest(AbstractStorageBackendTest): mock_bucket.blob.return_value = mock_blob mock_blob.public_url = '/path/to/somefile.bin' mock_blob.size = 318 + mock_blob.exists.return_value = True test_file, file_contents = self.create_test_file() @@ -131,7 +145,9 @@ class MockedGoogleCloudStorageTest(AbstractStorageBackendTest): # Google-reported size should take precedence over reality. self.assertEqual(318, blob.size) + self.assertTrue(blob.exists()) + mock_blob.upload_from_file.assert_called_with(test_file, size=512, content_type='application/octet-stream') - mock_blob.reload.assert_called_once() + self.assertEqual(2, mock_blob.reload.call_count)