Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions gcloud/storage/bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import copy

import six
from six.moves.urllib.parse import quote # pylint: disable=F0401

from gcloud._helpers import _rfc3339_to_datetime
from gcloud.exceptions import NotFound
Expand Down Expand Up @@ -202,7 +203,7 @@ def path(self):

return self.path_helper(self.name)

def get_blob(self, blob_name, client=None):
def get_blob(self, blob_name, client=None, generation=None):
"""Get a blob object by name.

This will return None if the blob doesn't exist::
Expand All @@ -214,6 +215,10 @@ def get_blob(self, blob_name, client=None):
<Blob: my-bucket, /path/to/blob.txt>
>>> print bucket.get_blob('/does-not-exist.txt')
None
>>> print bucket.get_blob(
... '/path/to/versioned_blob.txt',
... generation=generation_id)
<Blob: my-bucket, /path/to/versioned_blob.txt>

This comment was marked as spam.


:type blob_name: string
:param blob_name: The name of the blob to retrieve.
Expand All @@ -222,14 +227,22 @@ def get_blob(self, blob_name, client=None):
:param client: Optional. The client to use. If not passed, falls back
to the ``client`` stored on the current bucket.

:type generation: int
:param generation: Optional. The generation id to retrieve in a bucket
that supports versioning.

:rtype: :class:`gcloud.storage.blob.Blob` or None
:returns: The blob object if it exists, otherwise None.
"""
client = self._require_client(client)
blob = Blob(bucket=self, name=blob_name)
try:
path = blob.path
if generation is not None:
path = (blob.path + '?generation=' +
quote(str(generation), safe=''))

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

response = client.connection.api_request(
method='GET', path=blob.path, _target_object=blob)
method='GET', path=path, _target_object=blob)
# NOTE: We assume response.get('name') matches `blob_name`.
blob._set_properties(response)
# NOTE: This will not fail immediately in a batch. However, when
Expand Down Expand Up @@ -357,7 +370,7 @@ def delete(self, force=False, client=None):
client.connection.api_request(method='DELETE', path=self.path,
_target_object=None)

def delete_blob(self, blob_name, client=None):
def delete_blob(self, blob_name, client=None, generation=None):
"""Deletes a blob from the current bucket.

If the blob isn't found (backend 404), raises a
Expand All @@ -384,6 +397,10 @@ def delete_blob(self, blob_name, client=None):
:param client: Optional. The client to use. If not passed, falls back
to the ``client`` stored on the current bucket.

:type generation: int
:param generation: Optional. The generation of this object to delete.
Only works on buckets with versioning enabled.

:raises: :class:`gcloud.exceptions.NotFound` (to suppress
the exception, call ``delete_blobs``, passing a no-op
``on_error`` callback, e.g.::
Expand All @@ -392,6 +409,10 @@ def delete_blob(self, blob_name, client=None):
"""
client = self._require_client(client)
blob_path = Blob.path_helper(self.path, blob_name)
if generation is not None:
blob_path = (blob_path + '?generation=' +
quote(str(generation), safe=''))

# We intentionally pass `_target_object=None` since a DELETE
# request has no response value (whether in a standard request or
# in a batch request).
Expand Down Expand Up @@ -423,7 +444,13 @@ def delete_blobs(self, blobs, on_error=None, client=None):
blob_name = blob
if not isinstance(blob_name, six.string_types):
blob_name = blob.name
self.delete_blob(blob_name, client=client)

generation = None
if hasattr(blob, 'generation'):

This comment was marked as spam.

generation = blob.generation

self.delete_blob(blob_name, client=client,
generation=generation)
except NotFound:
if on_error is not None:
on_error(blob)
Expand Down
59 changes: 59 additions & 0 deletions gcloud/storage/test_bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,36 @@ def test_get_blob_hit(self):
self.assertEqual(kw['method'], 'GET')
self.assertEqual(kw['path'], '/b/%s/o/%s' % (NAME, BLOB_NAME))

def test_get_blob_miss_w_generation(self):
NAME = 'name'
BLOB_NAME = 'blob-name-w-version'
GENERATION = 999
connection = _Connection({'name': BLOB_NAME, 'generation': 1})
client = _Client(connection)
bucket = self._makeOne(name=NAME)
blob = bucket.get_blob(BLOB_NAME, client=client, generation=GENERATION)
self.assertTrue(blob, None)
kw, = connection._requested
self.assertEqual(kw['method'], 'GET')
self.assertEqual(kw['path'], '/b/%s/o/%s?generation=%d' %
(NAME, BLOB_NAME, GENERATION))

def test_get_blob_hit_w_generation(self):
NAME = 'name'
BLOB_NAME = 'blob-name-w-version'
GENERATION = 999
connection = _Connection({'name': BLOB_NAME, 'generation': GENERATION})
client = _Client(connection)
bucket = self._makeOne(name=NAME)
blob = bucket.get_blob(BLOB_NAME, client=client, generation=GENERATION)
self.assertTrue(blob.bucket is bucket)
self.assertEqual(blob.name, BLOB_NAME)
self.assertEqual(blob.generation, GENERATION)
kw, = connection._requested
self.assertEqual(kw['method'], 'GET')
self.assertEqual(kw['path'], '/b/%s/o/%s?generation=%s' %
(NAME, BLOB_NAME, GENERATION))

def test_list_blobs_defaults(self):
NAME = 'name'
connection = _Connection({'items': []})
Expand Down Expand Up @@ -419,6 +449,35 @@ def test_delete_blob_hit(self):
self.assertEqual(kw['method'], 'DELETE')
self.assertEqual(kw['path'], '/b/%s/o/%s' % (NAME, BLOB_NAME))

def test_delete_blob_miss_w_generation(self):
from gcloud.exceptions import NotFound
NAME = 'name'
BLOB_NAME = 'blob_name'
GENERATION = 1
connection = _Connection()
client = _Client(connection)
bucket = self._makeOne(client=client, name=NAME)
self.assertRaises(NotFound, bucket.delete_blob,
BLOB_NAME, generation=GENERATION)
kw, = connection._requested
self.assertEqual(kw['method'], 'DELETE')
self.assertEqual(kw['path'], '/b/%s/o/%s?generation=%d' %
(NAME, BLOB_NAME, GENERATION))

def test_delete_blob_hit_w_generation(self):
NAME = 'name'
BLOB_NAME = 'blob-name'
GENERATION = 999
connection = _Connection({})
client = _Client(connection)
bucket = self._makeOne(client=client, name=NAME)
result = bucket.delete_blob(BLOB_NAME, generation=GENERATION)
self.assertTrue(result is None)
kw, = connection._requested
self.assertEqual(kw['method'], 'DELETE')
self.assertEqual(kw['path'], '/b/%s/o/%s?generation=%d' %
(NAME, BLOB_NAME, GENERATION))

def test_delete_blobs_empty(self):
NAME = 'name'
connection = _Connection()
Expand Down