Skip to content
This repository was archived by the owner on Mar 31, 2026. It is now read-only.

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 <chandrasiri@google.com>
1 parent 7d17922 commit ec470a2

2 files changed

Lines changed: 37 additions & 1 deletion

File tree

‎google/cloud/storage/bucket.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
fromgoogle.cloud.storage._opentelemetry_tracingimportcreate_trace_span
4242
fromgoogle.cloud.storage.aclimportBucketACL
4343
fromgoogle.cloud.storage.aclimportDefaultObjectACL
44+
fromgoogle.cloud.storage.blobimport_quote
4445
fromgoogle.cloud.storage.blobimportBlob
4546
fromgoogle.cloud.storage.constantsimport_DEFAULT_TIMEOUT
4647
fromgoogle.cloud.storage.constantsimportARCHIVE_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
importmock
1919
importpytest
2020

21+
fromgoogle.cloud.storage.blobimport_quote
2122
fromgoogle.cloud.storage.retryimportDEFAULT_RETRY
2223
fromgoogle.cloud.storage.retryimportDEFAULT_RETRY_IF_ETAG_IN_JSON
2324
fromgoogle.cloud.storage.retryimportDEFAULT_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+
deftest_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
deftest_move_blob_w_user_project(self):
23242356
source_name="source"
23252357
blob_name="blob-name"

0 commit comments

Comments
 (0)