Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions google/cloud/storage/asyncio/async_grpc_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,55 @@ def grpc_client(self):
google.cloud._storage_v2.StorageAsyncClient: The configured GAPIC client.
"""
return self._grpc_client

async def delete_object(
self,
bucket_name,
object_name,
generation=None,
if_generation_match=None,
if_generation_not_match=None,
if_metageneration_match=None,
if_metageneration_not_match=None,
**kwargs,
):
"""Deletes an object and its metadata.

:type bucket_name: str
:param bucket_name: The name of the bucket in which the object resides.

:type object_name: str
:param object_name: The name of the object to delete.

:type generation: int
:param generation:
(Optional) If present, permanently deletes a specific generation
of an object.

:type if_generation_match: int
:param if_generation_match: (Optional)

:type if_generation_not_match: int
:param if_generation_not_match: (Optional)

:type if_metageneration_match: int
:param if_metageneration_match: (Optional)

:type if_metageneration_not_match: int
:param if_metageneration_not_match: (Optional)


"""
# The gRPC API requires the bucket name to be in the format "projects/_/buckets/bucket_name"
bucket_path = f"projects/_/buckets/{bucket_name}"
request = storage_v2.DeleteObjectRequest(
bucket=bucket_path,
object=object_name,
generation=generation,
if_generation_match=if_generation_match,
if_generation_not_match=if_generation_not_match,
if_metageneration_match=if_metageneration_match,
if_metageneration_not_match=if_metageneration_not_match,
**kwargs,
)
await self._grpc_client.delete_object(request=request)
33 changes: 32 additions & 1 deletion tests/system/test_zonal.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from google.cloud.storage.asyncio.async_multi_range_downloader import (
AsyncMultiRangeDownloader,
)
from google.api_core.exceptions import FailedPrecondition
from google.api_core.exceptions import FailedPrecondition, NotFound


pytestmark = pytest.mark.skipif(
Expand Down Expand Up @@ -570,3 +570,34 @@ async def _run():
blobs_to_delete.append(storage_client.bucket(_ZONAL_BUCKET).blob(object_name))

event_loop.run_until_complete(_run())


def test_delete_object_using_grpc_client(event_loop, grpc_client_direct):
"""
Test that a new writer when specifies `None` overrides the existing object.
Comment on lines +576 to +577
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The docstring for test_delete_object_using_grpc_client is incorrect. It describes a test for overriding an existing object, not for deleting an object. Please update it to accurately reflect the purpose of this test.

Suggested change
"""
Test that a new writer when specifies `None` overrides the existing object.
"""
Test that `delete_object` using the gRPC client successfully deletes an object.
"""

"""
object_name = f"test_append_with_generation-{uuid.uuid4()}"

async def _run():
writer = AsyncAppendableObjectWriter(
grpc_client_direct, _ZONAL_BUCKET, object_name, generation=0
)

# Empty object is created.
await writer.open()
await writer.append(b"some_bytes")
await writer.close()

await grpc_client_direct.delete_object(_ZONAL_BUCKET, object_name)

# trying to get raises raises 404.
with pytest.raises(NotFound):
# TODO: Remove this once GET_OBJECT is exposed in `AsyncGrpcClient`
await grpc_client_direct._grpc_client.get_object(
bucket=f"projects/_/buckets/{_ZONAL_BUCKET}", object_=object_name
)
# cleanup
del writer
gc.collect()

event_loop.run_until_complete(_run())
45 changes: 45 additions & 0 deletions tests/unit/asyncio/test_async_grpc_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

from unittest import mock
import pytest
from google.auth import credentials as auth_credentials
from google.auth.credentials import AnonymousCredentials
from google.api_core import client_info as client_info_lib
Expand Down Expand Up @@ -185,6 +186,7 @@ def test_grpc_client_with_anon_creds(self, mock_grpc_gapic_client):
credentials=anonymous_creds,
options=expected_options,
)
mock_transport_cls.assert_called_once_with(channel=channel_sentinel)

@mock.patch("google.cloud._storage_v2.StorageAsyncClient")
def test_user_agent_with_custom_client_info(self, mock_async_storage_client):
Expand All @@ -209,3 +211,46 @@ def test_user_agent_with_custom_client_info(self, mock_async_storage_client):
agent_version = f"gcloud-python/{__version__}"
expected_user_agent = f"custom-app/1.0 {agent_version} "
assert client_info.user_agent == expected_user_agent

@mock.patch("google.cloud._storage_v2.StorageAsyncClient")
@pytest.mark.asyncio
async def test_delete_object(self, mock_async_storage_client):
# Arrange
mock_transport_cls = mock.MagicMock()
mock_async_storage_client.get_transport_class.return_value = mock_transport_cls
mock_gapic_client = mock.AsyncMock()
mock_async_storage_client.return_value = mock_gapic_client

client = async_grpc_client.AsyncGrpcClient(
credentials=_make_credentials(spec=AnonymousCredentials)
)

bucket_name = "bucket"
object_name = "object"
generation = 123
if_generation_match = 456
if_generation_not_match = 789
if_metageneration_match = 111
if_metageneration_not_match = 222

# Act
await client.delete_object(
bucket_name,
object_name,
generation=generation,
if_generation_match=if_generation_match,
if_generation_not_match=if_generation_not_match,
if_metageneration_match=if_metageneration_match,
if_metageneration_not_match=if_metageneration_not_match,
)

# Assert
call_args, call_kwargs = mock_gapic_client.delete_object.call_args
request = call_kwargs["request"]
assert request.bucket == "projects/_/buckets/bucket"
assert request.object == "object"
assert request.generation == generation
assert request.if_generation_match == if_generation_match
assert request.if_generation_not_match == if_generation_not_match
assert request.if_metageneration_match == if_metageneration_match
assert request.if_metageneration_not_match == if_metageneration_not_match