From ff4cb6d72ef4368d9fa9bf1230280bb224112c00 Mon Sep 17 00:00:00 2001 From: Shubham Kaushal Date: Tue, 20 May 2025 06:57:11 +0000 Subject: [PATCH 1/8] add samples and test cases for soft delete objects --- samples/snippets/snippets_test.py | 146 ++++++++++++++++-- .../snippets/storage_disable_soft_delete.py | 41 +++++ .../storage_get_soft_delete_policy.py | 46 ++++++ ...orage_list_soft_deleted_object_versions.py | 44 ++++++ .../storage_list_soft_deleted_objects.py | 43 ++++++ samples/snippets/storage_restore_object.py | 47 ++++++ .../storage_set_soft_delete_policy.py | 45 ++++++ 7 files changed, 402 insertions(+), 10 deletions(-) create mode 100644 samples/snippets/storage_disable_soft_delete.py create mode 100644 samples/snippets/storage_get_soft_delete_policy.py create mode 100644 samples/snippets/storage_list_soft_deleted_object_versions.py create mode 100644 samples/snippets/storage_list_soft_deleted_objects.py create mode 100644 samples/snippets/storage_restore_object.py create mode 100644 samples/snippets/storage_set_soft_delete_policy.py diff --git a/samples/snippets/snippets_test.py b/samples/snippets/snippets_test.py index 4f98884b5..ccae65734 100644 --- a/samples/snippets/snippets_test.py +++ b/samples/snippets/snippets_test.py @@ -25,8 +25,8 @@ import requests import storage_add_bucket_label -import storage_async_upload import storage_async_download +import storage_async_upload import storage_batch_request import storage_bucket_delete_default_kms_key import storage_change_default_storage_class @@ -44,6 +44,7 @@ import storage_delete_file import storage_delete_file_archived_generation import storage_disable_bucket_lifecycle_management +import storage_disable_soft_delete import storage_disable_versioning import storage_download_byte_range import storage_download_file @@ -59,26 +60,31 @@ import storage_get_autoclass import storage_get_bucket_labels import storage_get_bucket_metadata -import storage_get_soft_deleted_bucket import storage_get_metadata import storage_get_service_account +import storage_get_soft_delete_policy +import storage_get_soft_deleted_bucket import storage_list_buckets -import storage_list_soft_deleted_buckets -import storage_restore_soft_deleted_bucket import storage_list_file_archived_generations import storage_list_files import storage_list_files_with_prefix +import storage_list_soft_deleted_buckets +import storage_list_soft_deleted_object_versions +import storage_list_soft_deleted_objects import storage_make_public import storage_move_file import storage_object_get_kms_key import storage_remove_bucket_label import storage_remove_cors_configuration import storage_rename_file +import storage_restore_object +import storage_restore_soft_deleted_bucket import storage_set_autoclass import storage_set_bucket_default_kms_key import storage_set_client_endpoint -import storage_set_object_retention_policy import storage_set_metadata +import storage_set_object_retention_policy +import storage_set_soft_delete_policy import storage_trace_quickstart import storage_transfer_manager_download_bucket import storage_transfer_manager_download_chunks_concurrently @@ -147,6 +153,21 @@ def test_soft_deleted_bucket(): yield bucket +@pytest.fixture(scope="module") +def test_soft_delete_enabled_bucket(): + """Yields a bucket with soft-delete enabled that is deleted after the test completes.""" + bucket = None + while bucket is None or bucket.exists(): + bucket_name = f"storage-snippets-test-{uuid.uuid4()}" + bucket = storage.Client().bucket(bucket_name) + # Soft-delete retention for 7 days (minimum allowed by API) + bucket.soft_delete_policy.retention_duration_seconds = 7 * 24 * 60 * 60 + # Soft-delete requires a region + bucket.create(location="US") + yield bucket + bucket.delete(force=True) + + @pytest.fixture(scope="function") def test_public_bucket(): # The new projects don't allow to make a bucket available to public, so @@ -230,13 +251,17 @@ def test_bucket_metadata(test_bucket, capsys): def test_get_soft_deleted_bucket(test_soft_deleted_bucket, capsys): - storage_get_soft_deleted_bucket.get_soft_deleted_bucket(test_soft_deleted_bucket.name, test_soft_deleted_bucket.generation) + storage_get_soft_deleted_bucket.get_soft_deleted_bucket( + test_soft_deleted_bucket.name, test_soft_deleted_bucket.generation + ) out, _ = capsys.readouterr() assert test_soft_deleted_bucket.name in out def test_restore_soft_deleted_bucket(test_soft_deleted_bucket, capsys): - storage_restore_soft_deleted_bucket.restore_bucket(test_soft_deleted_bucket.name, test_soft_deleted_bucket.generation) + storage_restore_soft_deleted_bucket.restore_bucket( + test_soft_deleted_bucket.name, test_soft_deleted_bucket.generation + ) out, _ = capsys.readouterr() assert test_soft_deleted_bucket.name in out @@ -309,7 +334,9 @@ def test_async_download(test_bucket, capsys): blob = test_bucket.blob(source) blob.upload_from_string(source) - asyncio.run(storage_async_download.async_download_blobs(test_bucket.name, *source_files)) + asyncio.run( + storage_async_download.async_download_blobs(test_bucket.name, *source_files) + ) out, _ = capsys.readouterr() for x in range(object_count): assert f"Downloaded storage object async_sample_blob_{x}" in out @@ -877,7 +904,10 @@ def test_object_retention_policy(test_bucket_create, capsys): test_bucket_create.name ) out, _ = capsys.readouterr() - assert f"Created bucket {test_bucket_create.name} with object retention enabled setting" in out + assert ( + f"Created bucket {test_bucket_create.name} with object retention enabled setting" + in out + ) blob_name = "test_object_retention" storage_set_object_retention_policy.set_object_retention_policy( @@ -898,7 +928,10 @@ def test_create_bucket_hierarchical_namespace(test_bucket_create, capsys): test_bucket_create.name ) out, _ = capsys.readouterr() - assert f"Created bucket {test_bucket_create.name} with hierarchical namespace enabled" in out + assert ( + f"Created bucket {test_bucket_create.name} with hierarchical namespace enabled" + in out + ) def test_storage_trace_quickstart(test_bucket, capsys): @@ -911,3 +944,96 @@ def test_storage_trace_quickstart(test_bucket, capsys): assert ( f"Downloaded storage object {blob_name} from bucket {test_bucket.name}" in out ) + + +def test_storage_disable_soft_delete(test_soft_delete_enabled_bucket, capsys): + bucket_name = test_soft_delete_enabled_bucket.name + storage_disable_soft_delete.disable_soft_delete(bucket_name) + out, _ = capsys.readouterr() + assert f"Soft-delete policy is disabled for bucket {bucket_name}" in out + + +def test_storage_get_soft_delete_policy(test_soft_delete_enabled_bucket, capsys): + bucket_name = test_soft_delete_enabled_bucket.name + storage_get_soft_delete_policy.get_soft_delete_policy(bucket_name) + out, _ = capsys.readouterr() + assert f"Soft-delete policy for {bucket_name}" in out + assert f"Object soft-delete policy is enabled" in out + assert "Object retention duration: " in out + assert "Policy effective time: " in out + + # Disable the soft-delete policy + test_soft_delete_enabled_bucket.soft_delete_policy.retention_duration_seconds = 0 + test_soft_delete_enabled_bucket.patch() + storage_get_soft_delete_policy.get_soft_delete_policy(bucket_name) + out, _ = capsys.readouterr() + assert f"Soft-delete policy for {bucket_name}" in out + assert f"Object soft-delete policy is disabled" in out + + +def test_storage_set_soft_delete_policy(test_soft_delete_enabled_bucket, capsys): + bucket_name = test_soft_delete_enabled_bucket.name + retention_duration_seconds = 10 * 24 * 60 * 60 # 10 days + storage_set_soft_delete_policy.set_soft_delete_policy( + bucket_name, retention_duration_seconds + ) + out, _ = capsys.readouterr() + assert ( + f"Object soft-delete duration is updated to {retention_duration_seconds} seconds in the bucket {bucket_name}" + in out + ) + + +def test_storage_list_soft_deleted_objects(test_soft_delete_enabled_bucket, capsys): + bucket_name = test_soft_delete_enabled_bucket.name + blob_name = f"test_object_{uuid.uuid4().hex}.txt" + blob_content = "This object will be soft-deleted for listing." + blob = test_soft_delete_enabled_bucket.blob(blob_name) + blob.upload_from_string(blob_content) + blob_generation = blob.generation + + blob.delete() # Soft-delete the object + storage_list_soft_deleted_objects.list_soft_deleted_objects(bucket_name) + out, _ = capsys.readouterr() + assert f"Name: {blob_name}, Generation: {blob_generation}" in out + + +def test_storage_list_soft_deleted_object_versions( + test_soft_delete_enabled_bucket, capsys +): + bucket_name = test_soft_delete_enabled_bucket.name + blob_name = f"test_object_{uuid.uuid4().hex}.txt" + blob_content = "This object will be soft-deleted for version listing." + blob = test_soft_delete_enabled_bucket.blob(blob_name) + blob.upload_from_string(blob_content) + blob_generation = blob.generation + + blob.delete() # Soft-delete the object + storage_list_soft_deleted_object_versions.list_soft_deleted_object_versions( + bucket_name, blob_name + ) + out, _ = capsys.readouterr() + assert f"Version ID: {blob_generation}" in out + + +def test_storage_restore_soft_deleted_object(test_soft_delete_enabled_bucket, capsys): + bucket_name = test_soft_delete_enabled_bucket.name + blob_name = f"test-restore-sd-obj-{uuid.uuid4().hex}.txt" + blob_content = "This object will be soft-deleted and restored." + blob = test_soft_delete_enabled_bucket.blob(blob_name) + blob.upload_from_string(blob_content) + blob_generation = blob.generation + + blob.delete() # Soft-delete the object + storage_restore_object.restore_soft_deleted_object( + bucket_name, blob_name, blob_generation + ) + out, _ = capsys.readouterr() + assert ( + f"Soft-deleted object {blob_name} with generation {blob_generation} is restored in the bucket {bucket_name}" + in out + ) + + # Verify the restoration + blob = test_soft_delete_enabled_bucket.get_blob(blob_name) + assert blob is not None diff --git a/samples/snippets/storage_disable_soft_delete.py b/samples/snippets/storage_disable_soft_delete.py new file mode 100644 index 000000000..9bd04b41e --- /dev/null +++ b/samples/snippets/storage_disable_soft_delete.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +# Copyright 2025 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +from google.cloud import storage + +# [START storage_disable_soft_delete] + + +def disable_soft_delete(bucket_name): + """Disable soft-delete policy for the bucket.""" + # bucket_name = "your-bucket-name" + + storage_client = storage.Client() + bucket = storage_client.get_bucket(bucket_name) + + # Setting the retention duration to 0 disables soft-delete. + bucket.soft_delete_policy.retention_duration_seconds = 0 + bucket.patch() + + print(f"Soft-delete policy is disabled for bucket {bucket_name}") + + +# [END storage_disable_soft_delete] + +if __name__ == "__main__": + disable_soft_delete(bucket_name=sys.argv[1]) diff --git a/samples/snippets/storage_get_soft_delete_policy.py b/samples/snippets/storage_get_soft_delete_policy.py new file mode 100644 index 000000000..06d962efb --- /dev/null +++ b/samples/snippets/storage_get_soft_delete_policy.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +# Copyright 2025 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +from google.cloud import storage + +# [START storage_get_soft_delete_policy] + + +def get_soft_delete_policy(bucket_name): + """Gets the soft-delete policy of the bucket""" + # bucket_name = "your-bucket-name" + + storage_client = storage.Client() + bucket = storage_client.bucket(bucket_name) + bucket.reload() + + print(f"Soft-delete policy for {bucket_name}") + if bucket.soft_delete_policy and bucket.soft_delete_policy.retention_duration_seconds: + print(f"Object soft-delete policy is enabled") + print( + f"Object retention duration: {bucket.soft_delete_policy.retention_duration_seconds} seconds" + ) + print(f"Policy effective time: {bucket.soft_delete_policy.effective_time}") + else: + print(f"Object soft-delete policy is disabled") + + +# [END storage_get_soft_delete_policy] + +if __name__ == "__main__": + get_soft_delete_policy(bucket_name=sys.argv[1]) diff --git a/samples/snippets/storage_list_soft_deleted_object_versions.py b/samples/snippets/storage_list_soft_deleted_object_versions.py new file mode 100644 index 000000000..82cf304ee --- /dev/null +++ b/samples/snippets/storage_list_soft_deleted_object_versions.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +from google.cloud import storage + +# [START storage_list_soft_deleted_object_versions] + + +def list_soft_deleted_object_versions(bucket_name, blob_name): + """Lists all versions of a soft-deleted object in the bucket.""" + # bucket_name = "your-bucket-name" + # blob_name = "your-object-name" + + storage_client = storage.Client() + + # Note: Client.list_blobs requires at least package version 1.17.0. + blobs = storage_client.list_blobs(bucket_name, prefix=blob_name, soft_deleted=True) + + # Note: The call returns a response only when the iterator is consumed. + for blob in blobs: + print( + f"Version ID: {blob.generation}, Soft Delete Time: {blob.soft_delete_time}" + ) + + +# [END storage_list_soft_deleted_object_versions] + +if __name__ == "__main__": + list_soft_deleted_object_versions(bucket_name=sys.argv[1], blob_name=sys.argv[2]) diff --git a/samples/snippets/storage_list_soft_deleted_objects.py b/samples/snippets/storage_list_soft_deleted_objects.py new file mode 100644 index 000000000..e397eaa13 --- /dev/null +++ b/samples/snippets/storage_list_soft_deleted_objects.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +from google.cloud import storage + +# [START storage_list_soft_deleted_objects] + + +def list_soft_deleted_objects(bucket_name): + """Lists all soft-deleted objects in the bucket.""" + # bucket_name = "your-bucket-name" + + storage_client = storage.Client() + + # Note: Client.list_blobs requires at least package version 1.17.0. + blobs = storage_client.list_blobs(bucket_name, soft_deleted=True) + + # Note: The call returns a response only when the iterator is consumed. + for blob in blobs: + print( + f"Name: {blob.name}, Generation: {blob.generation}, Soft Delete Time: {blob.soft_delete_time}" + ) + + +# [END storage_list_soft_deleted_objects] + +if __name__ == "__main__": + list_soft_deleted_objects(bucket_name=sys.argv[1]) diff --git a/samples/snippets/storage_restore_object.py b/samples/snippets/storage_restore_object.py new file mode 100644 index 000000000..08c7f3132 --- /dev/null +++ b/samples/snippets/storage_restore_object.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import sys + +from google.cloud import storage + +# [START storage_restore_object] + + +def restore_soft_deleted_object(bucket_name, blob_name, blob_generation): + """Restores a soft-deleted object in the bucket.""" + # bucket_name = "your-bucket-name" + # blob_name = "your-object-name" + # blob_generation = "your-object-version-id" + + storage_client = storage.Client() + bucket = storage_client.bucket(bucket_name) + + # Function will override the object if it already exists. + bucket.restore_blob(blob_name, generation=blob_generation) + + print( + f"Soft-deleted object {blob_name} with generation {blob_generation} is restored in the bucket {bucket_name}" + ) + + +# [END storage_restore_object] + +if __name__ == "__main__": + restore_soft_deleted_object( + bucket_name=sys.argv[1], blob_name=sys.argv[2], blob_generation=sys.argv[3] + ) diff --git a/samples/snippets/storage_set_soft_delete_policy.py b/samples/snippets/storage_set_soft_delete_policy.py new file mode 100644 index 000000000..1c0545a10 --- /dev/null +++ b/samples/snippets/storage_set_soft_delete_policy.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python + +# Copyright 2025 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import sys + +from google.cloud import storage +from google.cloud.storage.bucket import SoftDeletePolicy + +# [START storage_set_soft_delete_policy] + + +def set_soft_delete_policy(bucket_name, duration_in_seconds): + """Sets a soft-delete policy on the bucket""" + # bucket_name = "your-bucket-name" + # duration_in_seconds = "your-soft-delete-retention-duration-in-seconds" + + storage_client = storage.Client() + bucket = storage_client.bucket(bucket_name) + + bucket.soft_delete_policy.retention_duration_seconds = duration_in_seconds + bucket.patch() + + print( + f"Object soft-delete duration is updated to {duration_in_seconds} seconds in the bucket {bucket_name}" + ) + + +# [END storage_set_soft_delete_policy] + +if __name__ == "__main__": + set_soft_delete_policy(bucket_name=sys.argv[1], duration_in_seconds=sys.argv[2]) From 7ef37d851e04a0a47580d4e1985d3f010ae3f619 Mon Sep 17 00:00:00 2001 From: Shubham Kaushal Date: Tue, 20 May 2025 09:02:19 +0000 Subject: [PATCH 2/8] file formatting --- samples/snippets/storage_get_soft_delete_policy.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/samples/snippets/storage_get_soft_delete_policy.py b/samples/snippets/storage_get_soft_delete_policy.py index 06d962efb..0bca87b58 100644 --- a/samples/snippets/storage_get_soft_delete_policy.py +++ b/samples/snippets/storage_get_soft_delete_policy.py @@ -30,7 +30,10 @@ def get_soft_delete_policy(bucket_name): bucket.reload() print(f"Soft-delete policy for {bucket_name}") - if bucket.soft_delete_policy and bucket.soft_delete_policy.retention_duration_seconds: + if ( + bucket.soft_delete_policy + and bucket.soft_delete_policy.retention_duration_seconds + ): print(f"Object soft-delete policy is enabled") print( f"Object retention duration: {bucket.soft_delete_policy.retention_duration_seconds} seconds" From b54f8c58e9b209e63e299fae6079d3cd475be57e Mon Sep 17 00:00:00 2001 From: Shubham Kaushal Date: Tue, 20 May 2025 09:56:24 +0000 Subject: [PATCH 3/8] fix lint --- samples/snippets/snippets_test.py | 4 ++-- samples/snippets/storage_get_soft_delete_policy.py | 4 ++-- samples/snippets/storage_set_soft_delete_policy.py | 2 -- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/samples/snippets/snippets_test.py b/samples/snippets/snippets_test.py index ccae65734..a56d11e54 100644 --- a/samples/snippets/snippets_test.py +++ b/samples/snippets/snippets_test.py @@ -958,7 +958,7 @@ def test_storage_get_soft_delete_policy(test_soft_delete_enabled_bucket, capsys) storage_get_soft_delete_policy.get_soft_delete_policy(bucket_name) out, _ = capsys.readouterr() assert f"Soft-delete policy for {bucket_name}" in out - assert f"Object soft-delete policy is enabled" in out + assert "Object soft-delete policy is enabled" in out assert "Object retention duration: " in out assert "Policy effective time: " in out @@ -968,7 +968,7 @@ def test_storage_get_soft_delete_policy(test_soft_delete_enabled_bucket, capsys) storage_get_soft_delete_policy.get_soft_delete_policy(bucket_name) out, _ = capsys.readouterr() assert f"Soft-delete policy for {bucket_name}" in out - assert f"Object soft-delete policy is disabled" in out + assert "Object soft-delete policy is disabled" in out def test_storage_set_soft_delete_policy(test_soft_delete_enabled_bucket, capsys): diff --git a/samples/snippets/storage_get_soft_delete_policy.py b/samples/snippets/storage_get_soft_delete_policy.py index 0bca87b58..358c70f51 100644 --- a/samples/snippets/storage_get_soft_delete_policy.py +++ b/samples/snippets/storage_get_soft_delete_policy.py @@ -34,13 +34,13 @@ def get_soft_delete_policy(bucket_name): bucket.soft_delete_policy and bucket.soft_delete_policy.retention_duration_seconds ): - print(f"Object soft-delete policy is enabled") + print("Object soft-delete policy is enabled") print( f"Object retention duration: {bucket.soft_delete_policy.retention_duration_seconds} seconds" ) print(f"Policy effective time: {bucket.soft_delete_policy.effective_time}") else: - print(f"Object soft-delete policy is disabled") + print("Object soft-delete policy is disabled") # [END storage_get_soft_delete_policy] diff --git a/samples/snippets/storage_set_soft_delete_policy.py b/samples/snippets/storage_set_soft_delete_policy.py index 1c0545a10..40d0e63ed 100644 --- a/samples/snippets/storage_set_soft_delete_policy.py +++ b/samples/snippets/storage_set_soft_delete_policy.py @@ -14,11 +14,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import datetime import sys from google.cloud import storage -from google.cloud.storage.bucket import SoftDeletePolicy # [START storage_set_soft_delete_policy] From 2de7c06b5b90ed86bd2e0b0d506301d178316d39 Mon Sep 17 00:00:00 2001 From: Shubham Kaushal Date: Thu, 22 May 2025 10:05:12 +0000 Subject: [PATCH 4/8] minor fixes --- samples/snippets/snippets_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/snippets/snippets_test.py b/samples/snippets/snippets_test.py index a56d11e54..cf5e0bb3b 100644 --- a/samples/snippets/snippets_test.py +++ b/samples/snippets/snippets_test.py @@ -163,7 +163,9 @@ def test_soft_delete_enabled_bucket(): # Soft-delete retention for 7 days (minimum allowed by API) bucket.soft_delete_policy.retention_duration_seconds = 7 * 24 * 60 * 60 # Soft-delete requires a region - bucket.create(location="US") + bucket.create(location="US-CENTRAL1") + time.sleep(2) # Let change propagate as needed + bucket.reload() yield bucket bucket.delete(force=True) From 399171a800380ca9f1edf77707289182c0d83840 Mon Sep 17 00:00:00 2001 From: Shubham Kaushal Date: Fri, 23 May 2025 06:49:43 +0000 Subject: [PATCH 5/8] minor fix --- samples/snippets/snippets_test.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/samples/snippets/snippets_test.py b/samples/snippets/snippets_test.py index cf5e0bb3b..673569864 100644 --- a/samples/snippets/snippets_test.py +++ b/samples/snippets/snippets_test.py @@ -164,8 +164,20 @@ def test_soft_delete_enabled_bucket(): bucket.soft_delete_policy.retention_duration_seconds = 7 * 24 * 60 * 60 # Soft-delete requires a region bucket.create(location="US-CENTRAL1") - time.sleep(2) # Let change propagate as needed - bucket.reload() + # Wait until soft-delete policy is available (max 60 seconds) + max_wait = 60 + elapsed = 0 + while elapsed < max_wait: + bucket.reload() + if ( + bucket.soft_delete_policy + and bucket.soft_delete_policy.retention_duration_seconds + ): + break + time.sleep(2) + elapsed += 2 + else: + raise TimeoutError("Soft-delete policy did not propagate in time.") yield bucket bucket.delete(force=True) From b7538fdb62cad91a635cce8a0ae802625832f65d Mon Sep 17 00:00:00 2001 From: Shubham Kaushal Date: Fri, 30 May 2025 05:43:34 +0000 Subject: [PATCH 6/8] changing pytest fixture type --- samples/snippets/snippets_test.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/samples/snippets/snippets_test.py b/samples/snippets/snippets_test.py index 673569864..320d8390c 100644 --- a/samples/snippets/snippets_test.py +++ b/samples/snippets/snippets_test.py @@ -153,7 +153,7 @@ def test_soft_deleted_bucket(): yield bucket -@pytest.fixture(scope="module") +@pytest.fixture(scope="function") def test_soft_delete_enabled_bucket(): """Yields a bucket with soft-delete enabled that is deleted after the test completes.""" bucket = None @@ -164,20 +164,6 @@ def test_soft_delete_enabled_bucket(): bucket.soft_delete_policy.retention_duration_seconds = 7 * 24 * 60 * 60 # Soft-delete requires a region bucket.create(location="US-CENTRAL1") - # Wait until soft-delete policy is available (max 60 seconds) - max_wait = 60 - elapsed = 0 - while elapsed < max_wait: - bucket.reload() - if ( - bucket.soft_delete_policy - and bucket.soft_delete_policy.retention_duration_seconds - ): - break - time.sleep(2) - elapsed += 2 - else: - raise TimeoutError("Soft-delete policy did not propagate in time.") yield bucket bucket.delete(force=True) From 4c325a08ef9a06ac8b4a082082b235136fb2f6d7 Mon Sep 17 00:00:00 2001 From: Shubham Kaushal Date: Tue, 3 Jun 2025 07:20:08 +0000 Subject: [PATCH 7/8] resolving comments --- samples/snippets/snippets_test.py | 4 ++-- samples/snippets/storage_disable_soft_delete.py | 5 ++--- samples/snippets/storage_get_soft_delete_policy.py | 8 +++----- .../snippets/storage_list_soft_deleted_object_versions.py | 3 +-- samples/snippets/storage_list_soft_deleted_objects.py | 5 +---- samples/snippets/storage_restore_object.py | 8 ++++---- samples/snippets/storage_set_soft_delete_policy.py | 7 +++---- 7 files changed, 16 insertions(+), 24 deletions(-) diff --git a/samples/snippets/snippets_test.py b/samples/snippets/snippets_test.py index 320d8390c..3fe377b6b 100644 --- a/samples/snippets/snippets_test.py +++ b/samples/snippets/snippets_test.py @@ -979,7 +979,7 @@ def test_storage_set_soft_delete_policy(test_soft_delete_enabled_bucket, capsys) ) out, _ = capsys.readouterr() assert ( - f"Object soft-delete duration is updated to {retention_duration_seconds} seconds in the bucket {bucket_name}" + f"Soft delete policy for bucket {bucket_name} was set to {retention_duration_seconds} seconds retention period" in out ) @@ -1030,7 +1030,7 @@ def test_storage_restore_soft_deleted_object(test_soft_delete_enabled_bucket, ca ) out, _ = capsys.readouterr() assert ( - f"Soft-deleted object {blob_name} with generation {blob_generation} is restored in the bucket {bucket_name}" + f"Soft-deleted object {blob_name} is restored in the bucket {bucket_name}" in out ) diff --git a/samples/snippets/storage_disable_soft_delete.py b/samples/snippets/storage_disable_soft_delete.py index 9bd04b41e..dc2447ae8 100644 --- a/samples/snippets/storage_disable_soft_delete.py +++ b/samples/snippets/storage_disable_soft_delete.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright 2025 Google Inc. All Rights Reserved. +# Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the 'License'); # you may not use this file except in compliance with the License. @@ -16,9 +16,8 @@ import sys -from google.cloud import storage - # [START storage_disable_soft_delete] +from google.cloud import storage def disable_soft_delete(bucket_name): diff --git a/samples/snippets/storage_get_soft_delete_policy.py b/samples/snippets/storage_get_soft_delete_policy.py index 358c70f51..99c4e572a 100644 --- a/samples/snippets/storage_get_soft_delete_policy.py +++ b/samples/snippets/storage_get_soft_delete_policy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright 2025 Google Inc. All Rights Reserved. +# Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the 'License'); # you may not use this file except in compliance with the License. @@ -16,9 +16,8 @@ import sys -from google.cloud import storage - # [START storage_get_soft_delete_policy] +from google.cloud import storage def get_soft_delete_policy(bucket_name): @@ -26,8 +25,7 @@ def get_soft_delete_policy(bucket_name): # bucket_name = "your-bucket-name" storage_client = storage.Client() - bucket = storage_client.bucket(bucket_name) - bucket.reload() + bucket = storage_client.get_bucket(bucket_name) print(f"Soft-delete policy for {bucket_name}") if ( diff --git a/samples/snippets/storage_list_soft_deleted_object_versions.py b/samples/snippets/storage_list_soft_deleted_object_versions.py index 82cf304ee..a057ccdbd 100644 --- a/samples/snippets/storage_list_soft_deleted_object_versions.py +++ b/samples/snippets/storage_list_soft_deleted_object_versions.py @@ -16,9 +16,8 @@ import sys -from google.cloud import storage - # [START storage_list_soft_deleted_object_versions] +from google.cloud import storage def list_soft_deleted_object_versions(bucket_name, blob_name): diff --git a/samples/snippets/storage_list_soft_deleted_objects.py b/samples/snippets/storage_list_soft_deleted_objects.py index e397eaa13..764cac56a 100644 --- a/samples/snippets/storage_list_soft_deleted_objects.py +++ b/samples/snippets/storage_list_soft_deleted_objects.py @@ -16,9 +16,8 @@ import sys -from google.cloud import storage - # [START storage_list_soft_deleted_objects] +from google.cloud import storage def list_soft_deleted_objects(bucket_name): @@ -26,8 +25,6 @@ def list_soft_deleted_objects(bucket_name): # bucket_name = "your-bucket-name" storage_client = storage.Client() - - # Note: Client.list_blobs requires at least package version 1.17.0. blobs = storage_client.list_blobs(bucket_name, soft_deleted=True) # Note: The call returns a response only when the iterator is consumed. diff --git a/samples/snippets/storage_restore_object.py b/samples/snippets/storage_restore_object.py index 08c7f3132..d1e3f2937 100644 --- a/samples/snippets/storage_restore_object.py +++ b/samples/snippets/storage_restore_object.py @@ -17,9 +17,8 @@ import sys -from google.cloud import storage - # [START storage_restore_object] +from google.cloud import storage def restore_soft_deleted_object(bucket_name, blob_name, blob_generation): @@ -31,11 +30,12 @@ def restore_soft_deleted_object(bucket_name, blob_name, blob_generation): storage_client = storage.Client() bucket = storage_client.bucket(bucket_name) - # Function will override the object if it already exists. + # Restore function will override if a live object already + # exists with the same name. bucket.restore_blob(blob_name, generation=blob_generation) print( - f"Soft-deleted object {blob_name} with generation {blob_generation} is restored in the bucket {bucket_name}" + f"Soft-deleted object {blob_name} is restored in the bucket {bucket_name}" ) diff --git a/samples/snippets/storage_set_soft_delete_policy.py b/samples/snippets/storage_set_soft_delete_policy.py index 40d0e63ed..26bc59436 100644 --- a/samples/snippets/storage_set_soft_delete_policy.py +++ b/samples/snippets/storage_set_soft_delete_policy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Copyright 2025 Google Inc. All Rights Reserved. +# Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the 'License'); # you may not use this file except in compliance with the License. @@ -16,9 +16,8 @@ import sys -from google.cloud import storage - # [START storage_set_soft_delete_policy] +from google.cloud import storage def set_soft_delete_policy(bucket_name, duration_in_seconds): @@ -33,7 +32,7 @@ def set_soft_delete_policy(bucket_name, duration_in_seconds): bucket.patch() print( - f"Object soft-delete duration is updated to {duration_in_seconds} seconds in the bucket {bucket_name}" + f"Soft delete policy for bucket {bucket_name} was set to {duration_in_seconds} seconds retention period" ) From 993e8edbf5143b007a173379fb6c70b4788897e1 Mon Sep 17 00:00:00 2001 From: Shubham Kaushal Date: Fri, 6 Jun 2025 09:20:14 +0000 Subject: [PATCH 8/8] removing a comment --- samples/snippets/storage_list_soft_deleted_object_versions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/samples/snippets/storage_list_soft_deleted_object_versions.py b/samples/snippets/storage_list_soft_deleted_object_versions.py index a057ccdbd..ecb9851c4 100644 --- a/samples/snippets/storage_list_soft_deleted_object_versions.py +++ b/samples/snippets/storage_list_soft_deleted_object_versions.py @@ -26,8 +26,6 @@ def list_soft_deleted_object_versions(bucket_name, blob_name): # blob_name = "your-object-name" storage_client = storage.Client() - - # Note: Client.list_blobs requires at least package version 1.17.0. blobs = storage_client.list_blobs(bucket_name, prefix=blob_name, soft_deleted=True) # Note: The call returns a response only when the iterator is consumed.