Skip to content

Commit ec470a2

Browse files
fix(bucket): Move blob fails when the new blob name contains characters that need to be url encoded (#1605)
fix(bucket): url encode new_name parameter in move_blob() The move_blob() method was not URL encoding the new_name parameter before passing it to the API call, unlike how the blob encodes its own path. This caused failures when moving blobs to paths with special characters. Added URL encoding for new_name to match the blob path encoding, as both names must fit in the API URL format: "{blob_path}/moveTo/o/{new_name}" Here's an example of what fails: ```python from google.cloud import storage gcs = storage.Client() bucket = gcs.bucket("") blob = bucket.get_blob("test/blob.csv") bucket.move_blob( blob, new_name="test/blob2.csv" ) ``` Fixes #1523 --------- Co-authored-by: Chandra Shekhar Sirimala <[email protected]>
1 parent 7d17922 commit ec470a2

File tree

2 files changed

+37
-1
lines changed

2 files changed

+37
-1
lines changed

google/cloud/storage/bucket.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from google.cloud.storage._opentelemetry_tracing import create_trace_span
4242
from google.cloud.storage.acl import BucketACL
4343
from google.cloud.storage.acl import DefaultObjectACL
44+
from google.cloud.storage.blob import _quote
4445
from google.cloud.storage.blob import Blob
4546
from google.cloud.storage.constants import _DEFAULT_TIMEOUT
4647
from google.cloud.storage.constants import ARCHIVE_STORAGE_CLASS
@@ -2360,7 +2361,10 @@ def move_blob(
23602361
)
23612362

23622363
new_blob = Blob(bucket=self, name=new_name)
2363-
api_path = blob.path + "/moveTo/o/" + new_blob.name
2364+
api_path = "{blob_path}/moveTo/o/{new_name}".format(
2365+
blob_path=blob.path, new_name=_quote(new_blob.name)
2366+
)
2367+
23642368
move_result = client._post_resource(
23652369
api_path,
23662370
None,

tests/unit/test_bucket.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import mock
1919
import pytest
2020

21+
from google.cloud.storage.blob import _quote
2122
from google.cloud.storage.retry import DEFAULT_RETRY
2223
from google.cloud.storage.retry import DEFAULT_RETRY_IF_ETAG_IN_JSON
2324
from google.cloud.storage.retry import DEFAULT_RETRY_IF_GENERATION_SPECIFIED
@@ -2320,6 +2321,37 @@ def test_move_blob_w_no_retry_timeout_and_generation_match(self):
23202321
_target_object=new_blob,
23212322
)
23222323

2324+
def test_move_blob_needs_url_encoding(self):
2325+
source_name = "source"
2326+
blob_name = "blob-name"
2327+
new_name = "new/name"
2328+
api_response = {}
2329+
client = mock.Mock(spec=["_post_resource"])
2330+
client._post_resource.return_value = api_response
2331+
source = self._make_one(client=client, name=source_name)
2332+
blob = self._make_blob(source_name, blob_name)
2333+
2334+
new_blob = source.move_blob(
2335+
blob, new_name, if_generation_match=0, retry=None, timeout=30
2336+
)
2337+
2338+
self.assertIs(new_blob.bucket, source)
2339+
self.assertEqual(new_blob.name, new_name)
2340+
2341+
expected_path = "/b/{}/o/{}/moveTo/o/{}".format(
2342+
source_name, blob_name, _quote(new_name)
2343+
)
2344+
expected_data = None
2345+
expected_query_params = {"ifGenerationMatch": 0}
2346+
client._post_resource.assert_called_once_with(
2347+
expected_path,
2348+
expected_data,
2349+
query_params=expected_query_params,
2350+
timeout=30,
2351+
retry=None,
2352+
_target_object=new_blob,
2353+
)
2354+
23232355
def test_move_blob_w_user_project(self):
23242356
source_name = "source"
23252357
blob_name = "blob-name"

0 commit comments

Comments
 (0)