From 1c80d618f265c01ac01a794b5af12f03a6438fb1 Mon Sep 17 00:00:00 2001 From: Guilherme Salgado Date: Sun, 8 Jun 2014 18:31:35 +0200 Subject: [PATCH] Fix #64 - Add Bucket.copy_key() and Key.rename() methods. --- gcloud/storage/bucket.py | 24 ++++++++++++++++++++++-- gcloud/storage/key.py | 13 +++++++++++++ gcloud/storage/test_key.py | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 gcloud/storage/test_key.py diff --git a/gcloud/storage/bucket.py b/gcloud/storage/bucket.py index 13b5b91bcf83..151a40193aa3 100644 --- a/gcloud/storage/bucket.py +++ b/gcloud/storage/bucket.py @@ -194,8 +194,28 @@ def delete_keys(self, keys): for key in keys: self.delete_key(key) - def copy_key(self): - raise NotImplementedError + def copy_key(self, key, destination_bucket, new_name=None): + """Copy the given key to the given bucket, optionally with a new name. + + :type key: string or :class:`gcloud.storage.key.Key` + :param key: The key to be copied. + + :type destination_bucket: :class:`gcloud.storage.bucket.Bucket` + :param destination_bucket: The bucket where the key should be copied into. + + :type new_name: string + :param new_name: (optional) the new name for the copied file. + + :rtype: :class:`gcloud.storage.key.Key` + :returns: The new Key. + """ + # TODO: Check that the user has WRITE permissions on the dst bucket? + if new_name is None: + new_name = key.name + new_key = destination_bucket.new_key(new_name) + api_path = key.path + '/copyTo' + new_key.path + self.connection.api_request(method='POST', path=api_path) + return new_key def upload_file(self, filename, key=None): # TODO: What do we do about overwriting data? diff --git a/gcloud/storage/key.py b/gcloud/storage/key.py index 9b970bd12f1d..8b3cb36ae634 100644 --- a/gcloud/storage/key.py +++ b/gcloud/storage/key.py @@ -132,6 +132,19 @@ def exists(self): return self.bucket.get_key(self.name) is not None + def rename(self, new_name): + """Renames this key. + + :type new_name: string + :param new_name: The new name for this key. + """ + new_key = self.bucket.copy_key(self, self.bucket, new_name) + self.delete() + # This feels like a dirty hack, but since the bucket is the same we can + # just change the name attribute of this instance to have it point to the + # new key. + self.name = new_key.name + def delete(self): """Deletes a key from Cloud Storage. diff --git a/gcloud/storage/test_key.py b/gcloud/storage/test_key.py new file mode 100644 index 000000000000..284f94195deb --- /dev/null +++ b/gcloud/storage/test_key.py @@ -0,0 +1,36 @@ +import unittest2 + +from gcloud.storage.bucket import Bucket +from gcloud.storage.connection import Connection +from gcloud.storage.key import Key + + +class TestKey(unittest2.TestCase): + + def setUp(self): + # Mock Connection.api_request with a method that just stores the HTTP + # method, path and query params in an instance variable for later + # inspection. + # TODO: It'd be better to make the Connection talk to a local HTTP server + # that we can inspect, but a simple test using a mock is certainly better + # than no tests. + self.connection = Connection('project-name') + self.connection.api_request = self.mock_api_request + self.api_request_calls = [] + + def mock_api_request(self, method, path=None, query_params=None, + data=None, content_type=None, + api_base_url=None, api_version=None, + expect_json=True): + self.api_request_calls.append([method, path, query_params]) + + def test_rename(self): + bucket = Bucket(self.connection, 'bucket') + key = Key(bucket, 'key') + orig_key_path = key.path + key.rename('new-name') + expected = [ + ['POST', orig_key_path + '/copyTo/b/bucket/o/new-name', None], + ['DELETE', orig_key_path, None]] + self.assertEqual(key.name, 'new-name') + self.assertEqual(self.api_request_calls, expected)