Skip to content

Commit 16ab4c2

Browse files
feat(storage): Add delete_source_objects optional parameter to compose API (#17163)
Add an optional `delete_source_objects` boolean parameter to the `Blob.compose` method in `google-cloud-storage`. When this parameter is set to `True`, it includes the `deleteSourceObjects` field in the request body to the GCS JSON API, which triggers the deletion of source objects upon successful composition. Unit and system tests have been added and verified to pass. --- *PR created automatically by Jules for task [8742736712890332678](https://jules.google.com/task/8742736712890332678) started by @nidhiii-27* --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: nidhiii-27 <224584462+nidhiii-27@users.noreply.github.com>
1 parent 9ba049a commit 16ab4c2

3 files changed

Lines changed: 66 additions & 0 deletions

File tree

‎packages/google-cloud-storage/google/cloud/storage/blob.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3849,6 +3849,7 @@ def compose(
38493849
if_metageneration_match=None,
38503850
if_source_generation_match=None,
38513851
retry=DEFAULT_RETRY_IF_GENERATION_SPECIFIED,
3852+
delete_source_objects=None,
38523853
):
38533854
"""Concatenate source blobs into this one.
38543855
@@ -3908,6 +3909,11 @@ def compose(
39083909
Change the value to ``DEFAULT_RETRY`` or another `google.api_core.retry.Retry` object
39093910
to enable retries regardless of generation precondition setting.
39103911
See [Configuring Retries](https://cloud.google.com/python/docs/reference/storage/latest/retry_timeout).
3912+
3913+
:type delete_source_objects: bool
3914+
:param delete_source_objects:
3915+
(Optional) If True, the source objects will be deleted after a
3916+
successful composition.
39113917
"""
39123918
withcreate_trace_span(name="Storage.Blob.compose"):
39133919
sources_len=len(sources)
@@ -3964,6 +3970,9 @@ def compose(
39643970
"destination": self._properties.copy(),
39653971
}
39663972

3973+
ifdelete_source_objectsisnotNone:
3974+
request["deleteSourceObjects"] =delete_source_objects
3975+
39673976
ifself.user_projectisnotNone:
39683977
query_params["userProject"] =self.user_project
39693978

‎packages/google-cloud-storage/tests/system/test_blob.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,3 +1209,23 @@ def test_blob_download_as_bytes_single_shot_download(
12091209

12101210
result_single_shot_download=blob.download_as_bytes(single_shot_download=True)
12111211
assertresult_single_shot_download==payload
1212+
1213+
1214+
deftest_blob_compose_delete_source_objects(shared_bucket, blobs_to_delete):
1215+
payload_1=b"AAA\n"
1216+
source_1=shared_bucket.blob("source-1-delete")
1217+
source_1.upload_from_string(payload_1)
1218+
blobs_to_delete.append(source_1)
1219+
1220+
payload_2=b"BBB\n"
1221+
source_2=shared_bucket.blob("source-2-delete")
1222+
source_2.upload_from_string(payload_2)
1223+
blobs_to_delete.append(source_2)
1224+
1225+
destination=shared_bucket.blob("destination-delete")
1226+
destination.compose([source_1, source_2], delete_source_objects=True)
1227+
blobs_to_delete.append(destination)
1228+
1229+
assertdestination.download_as_bytes() ==payload_1+payload_2
1230+
assertnotsource_1.exists()
1231+
assertnotsource_2.exists()

‎packages/google-cloud-storage/tests/unit/test_blob.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4874,6 +4874,43 @@ def test_compose_w_metageneration_match(self):
48744874
_target_object=destination,
48754875
)
48764876

4877+
deftest_compose_w_delete_source_objects(self):
4878+
source_1_name="source-1"
4879+
source_2_name="source-2"
4880+
destination_name="destination"
4881+
delete_source_objects=True
4882+
api_response= {}
4883+
client=mock.Mock(spec=["_post_resource"])
4884+
client._post_resource.return_value=api_response
4885+
bucket=_Bucket(client=client)
4886+
source_1=self._make_one(source_1_name, bucket=bucket)
4887+
source_2=self._make_one(source_2_name, bucket=bucket)
4888+
destination=self._make_one(destination_name, bucket=bucket)
4889+
4890+
destination.compose(
4891+
sources=[source_1, source_2],
4892+
delete_source_objects=delete_source_objects,
4893+
)
4894+
4895+
expected_path=f"/b/name/o/{destination_name}/compose"
4896+
expected_data= {
4897+
"sourceObjects": [
4898+
{"name": source_1.name, "generation": source_1.generation},
4899+
{"name": source_2.name, "generation": source_2.generation},
4900+
],
4901+
"destination": {},
4902+
"deleteSourceObjects": delete_source_objects,
4903+
}
4904+
expected_query_params= {}
4905+
client._post_resource.assert_called_once_with(
4906+
expected_path,
4907+
expected_data,
4908+
query_params=expected_query_params,
4909+
timeout=self._get_default_timeout(),
4910+
retry=DEFAULT_RETRY_IF_GENERATION_SPECIFIED,
4911+
_target_object=destination,
4912+
)
4913+
48774914
deftest_rewrite_w_response_wo_resource(self):
48784915
source_name="source"
48794916
dest_name="dest"

0 commit comments

Comments
 (0)