diff --git a/storage/google/cloud/storage/blob.py b/storage/google/cloud/storage/blob.py index 8d30a94b600b..d097c5eaafcc 100644 --- a/storage/google/cloud/storage/blob.py +++ b/storage/google/cloud/storage/blob.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +# pylint: disable=too-many-lines + """Create / interact with Google Cloud Storage blobs.""" import base64 @@ -73,6 +75,9 @@ class Blob(_PropertyMixin): _CHUNK_SIZE_MULTIPLE = 256 * 1024 """Number (256 KB, in bytes) that must divide the chunk size.""" + _STORAGE_CLASSES = ( + 'STANDARD', 'NEARLINE', 'MULTI_REGIONAL', 'REGIONAL', 'COLDLINE') + def __init__(self, name, bucket, chunk_size=None, encryption_key=None): super(Blob, self).__init__(name=name) @@ -852,6 +857,33 @@ def rewrite(self, source, token=None, client=None): return api_response['rewriteToken'], rewritten, size + def update_storage_class(self, new_class, client=None): + """Update blob's storage class via a rewrite-in-place. + + See: + https://cloud.google.com/storage/docs/per-object-storage-class + + :type new_class: str + :param new_class: new storage class for the object + + :type client: :class:`~google.cloud.storage.client.Client` + :param client: Optional. The client to use. If not passed, falls back + to the ``client`` stored on the blob's bucket. + """ + if new_class not in self._STORAGE_CLASSES: + raise ValueError("Invalid storage class: %s" % (new_class,)) + + client = self._require_client(client) + headers = _get_encryption_headers(self._encryption_key) + headers.update(_get_encryption_headers( + self._encryption_key, source=True)) + + api_response = client._connection.api_request( + method='POST', path=self.path + '/rewriteTo' + self.path, + data={'storageClass': new_class}, headers=headers, + _target_object=self) + self._set_properties(api_response['resource']) + cache_control = _scalar_property('cacheControl') """HTTP 'Cache-Control' header for this object. diff --git a/storage/unit_tests/test_blob.py b/storage/unit_tests/test_blob.py index 9724fd7a4210..f14fc0b2af05 100644 --- a/storage/unit_tests/test_blob.py +++ b/storage/unit_tests/test_blob.py @@ -1446,6 +1446,48 @@ def test_rewrite_same_name_no_key_new_key_w_token(self): self.assertEqual( headers['X-Goog-Encryption-Key-Sha256'], DEST_KEY_HASH_B64) + def test_update_storage_class_invalid(self): + BLOB_NAME = 'blob-name' + bucket = _Bucket() + blob = self._make_one(BLOB_NAME, bucket=bucket) + with self.assertRaises(ValueError): + blob.update_storage_class(u'BOGUS') + + def test_update_storage_class_setter_valid(self): + from six.moves.http_client import OK + BLOB_NAME = 'blob-name' + STORAGE_CLASS = u'NEARLINE' + RESPONSE = { + 'resource': {'storageClass': STORAGE_CLASS}, + } + response = ({'status': OK}, RESPONSE) + connection = _Connection(response) + client = _Client(connection) + bucket = _Bucket(client=client) + blob = self._make_one(BLOB_NAME, bucket=bucket) + + blob.update_storage_class('NEARLINE') + + self.assertEqual(blob.storage_class, 'NEARLINE') + + kw = connection._requested + self.assertEqual(len(kw), 1) + self.assertEqual(kw[0]['method'], 'POST') + PATH = '/b/name/o/%s/rewriteTo/b/name/o/%s' % (BLOB_NAME, BLOB_NAME) + self.assertEqual(kw[0]['path'], PATH) + self.assertNotIn('query_params', kw[0]) + SENT = {'storageClass': STORAGE_CLASS} + self.assertEqual(kw[0]['data'], SENT) + + headers = { + key.title(): str(value) for key, value in kw[0]['headers'].items()} + self.assertNotIn('X-Goog-Copy-Source-Encryption-Algorithm', headers) + self.assertNotIn('X-Goog-Copy-Source-Encryption-Key', headers) + self.assertNotIn('X-Goog-Copy-Source-Encryption-Key-Sha256', headers) + self.assertNotIn('X-Goog-Encryption-Algorithm', headers) + self.assertNotIn('X-Goog-Encryption-Key', headers) + self.assertNotIn('X-Goog-Encryption-Key-Sha256', headers) + def test_cache_control_getter(self): BLOB_NAME = 'blob-name' bucket = _Bucket()