Skip to content

Commit dfd8654

Browse files
feat(storage): support delete_source_objects in compose API (Address PR feedback)
- Removed unnecessary fixture usage in system test. - Ensured destination metadata is not empty in unit test by setting content_type. - verified end-to-end functionality in system test. Reference PR: googleapis/google-cloud-java#12873 Co-authored-by: nidhiii-27 <224584462+nidhiii-27@users.noreply.github.com>
1 parent 471eb13 commit dfd8654

3 files changed

Lines changed: 68 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
with create_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+
if delete_source_objects is not None:
3974+
request["deleteSourceObjects"] = delete_source_objects
3975+
39673976
if self.user_project is not None:
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
@@ -853,6 +853,26 @@ def test_blob_compose_new_blob(shared_bucket, blobs_to_delete):
853853
assert destination.download_as_bytes() == payload_1 + payload_2
854854

855855

856+
def test_blob_compose_delete_source_objects(shared_bucket):
857+
payload_1 = b"AAA\n"
858+
source_1 = shared_bucket.blob("source-1-delete")
859+
source_1.upload_from_string(payload_1)
860+
861+
payload_2 = b"BBB\n"
862+
source_2 = shared_bucket.blob("source-2-delete")
863+
source_2.upload_from_string(payload_2)
864+
865+
destination = shared_bucket.blob("destination-delete")
866+
destination.compose([source_1, source_2], delete_source_objects=True)
867+
868+
try:
869+
assert destination.download_as_bytes() == payload_1 + payload_2
870+
assert not source_1.exists()
871+
assert not source_2.exists()
872+
finally:
873+
destination.delete()
874+
875+
856876
def test_blob_compose_new_blob_wo_content_type(shared_bucket, blobs_to_delete):
857877
payload_1 = b"AAA\n"
858878
source_1 = shared_bucket.blob("source-1")

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4480,6 +4480,45 @@ def test_compose_wo_content_type_set(self):
44804480
_target_object=destination,
44814481
)
44824482

4483+
def test_compose_w_delete_source_objects(self):
4484+
source_1_name = "source-1"
4485+
source_2_name = "source-2"
4486+
destination_name = "destination"
4487+
content_type = "text/plain"
4488+
delete_source_objects = True
4489+
api_response = {}
4490+
client = mock.Mock(spec=["_post_resource"])
4491+
client._post_resource.return_value = api_response
4492+
bucket = _Bucket(client=client)
4493+
source_1 = self._make_one(source_1_name, bucket=bucket)
4494+
source_2 = self._make_one(source_2_name, bucket=bucket)
4495+
destination = self._make_one(destination_name, bucket=bucket)
4496+
destination.content_type = content_type
4497+
4498+
destination.compose(
4499+
sources=[source_1, source_2],
4500+
delete_source_objects=delete_source_objects,
4501+
)
4502+
4503+
expected_path = f"/b/name/o/{destination_name}/compose"
4504+
expected_data = {
4505+
"sourceObjects": [
4506+
{"name": source_1.name, "generation": source_1.generation},
4507+
{"name": source_2.name, "generation": source_2.generation},
4508+
],
4509+
"destination": {"contentType": content_type},
4510+
"deleteSourceObjects": delete_source_objects,
4511+
}
4512+
expected_query_params = {}
4513+
client._post_resource.assert_called_once_with(
4514+
expected_path,
4515+
expected_data,
4516+
query_params=expected_query_params,
4517+
timeout=self._get_default_timeout(),
4518+
retry=DEFAULT_RETRY_IF_GENERATION_SPECIFIED,
4519+
_target_object=destination,
4520+
)
4521+
44834522
def test_compose_minimal_w_user_project_w_timeout(self):
44844523
source_1_name = "source-1"
44854524
source_2_name = "source-2"

0 commit comments

Comments
 (0)