Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 0e5ec29

Browse filesBrowse files
authored
feat: add get_object method for async grpc client (#1735)
This method can be used to fetch the metadata of an object using the async grpc API.
1 parent 7a00dfb commit 0e5ec29
Copy full SHA for 0e5ec29

File tree

Expand file treeCollapse file tree

3 files changed

+191
-0
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

3 files changed

+191
-0
lines changed
Open diff view settings
Collapse file

‎google/cloud/storage/asyncio/async_grpc_client.py‎

Copy file name to clipboardExpand all lines: google/cloud/storage/asyncio/async_grpc_client.py
+63Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,66 @@ async def delete_object(
158158
**kwargs,
159159
)
160160
await self._grpc_client.delete_object(request=request)
161+
162+
async def get_object(
163+
self,
164+
bucket_name,
165+
object_name,
166+
generation=None,
167+
if_generation_match=None,
168+
if_generation_not_match=None,
169+
if_metageneration_match=None,
170+
if_metageneration_not_match=None,
171+
soft_deleted=None,
172+
**kwargs,
173+
):
174+
"""Retrieves an object's metadata.
175+
176+
In the gRPC API, this is performed by the GetObject RPC, which
177+
returns the object resource (metadata) without the object's data.
178+
179+
:type bucket_name: str
180+
:param bucket_name: The name of the bucket in which the object resides.
181+
182+
:type object_name: str
183+
:param object_name: The name of the object.
184+
185+
:type generation: int
186+
:param generation:
187+
(Optional) If present, selects a specific generation of an object.
188+
189+
:type if_generation_match: int
190+
:param if_generation_match: (Optional) Precondition for object generation match.
191+
192+
:type if_generation_not_match: int
193+
:param if_generation_not_match: (Optional) Precondition for object generation mismatch.
194+
195+
:type if_metageneration_match: int
196+
:param if_metageneration_match: (Optional) Precondition for metageneration match.
197+
198+
:type if_metageneration_not_match: int
199+
:param if_metageneration_not_match: (Optional) Precondition for metageneration mismatch.
200+
201+
:type soft_deleted: bool
202+
:param soft_deleted:
203+
(Optional) If True, return the soft-deleted version of this object.
204+
205+
:rtype: :class:`google.cloud._storage_v2.types.Object`
206+
:returns: The object metadata resource.
207+
"""
208+
bucket_path = f"projects/_/buckets/{bucket_name}"
209+
210+
request = storage_v2.GetObjectRequest(
211+
bucket=bucket_path,
212+
object=object_name,
213+
generation=generation,
214+
if_generation_match=if_generation_match,
215+
if_generation_not_match=if_generation_not_match,
216+
if_metageneration_match=if_metageneration_match,
217+
if_metageneration_not_match=if_metageneration_not_match,
218+
soft_deleted=soft_deleted or False,
219+
**kwargs,
220+
)
221+
222+
# Calls the underlying GAPIC StorageAsyncClient.get_object method
223+
return await self._grpc_client.get_object(request=request)
Collapse file

‎tests/system/test_zonal.py‎

Copy file name to clipboardExpand all lines: tests/system/test_zonal.py
+53Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,3 +601,56 @@ async def _run():
601601
gc.collect()
602602

603603
event_loop.run_until_complete(_run())
604+
605+
def test_get_object_after_appendable_write(
606+
grpc_clients,
607+
grpc_client_direct,
608+
event_loop,
609+
storage_client,
610+
blobs_to_delete,
611+
):
612+
"""Test getting object metadata after writing with AsyncAppendableObjectWriter.
613+
614+
This test:
615+
1. Creates a test object using AsyncAppendableObjectWriter
616+
2. Appends content to the object (without finalizing)
617+
3. Closes the write stream
618+
4. Fetches the object metadata using AsyncGrpcClient.get_object()
619+
5. Verifies the object size matches the written data
620+
"""
621+
622+
async def _run():
623+
grpc_client = grpc_client_direct
624+
object_name = f"test-get-object-{uuid.uuid4().hex}"
625+
test_data = b"Some test data bytes."
626+
expected_size = len(test_data)
627+
628+
writer = AsyncAppendableObjectWriter(
629+
grpc_client,
630+
_ZONAL_BUCKET,
631+
object_name,
632+
)
633+
634+
await writer.open()
635+
await writer.append(test_data)
636+
await writer.close(finalize_on_close=False)
637+
638+
obj = await grpc_client.get_object(
639+
bucket_name=_ZONAL_BUCKET,
640+
object_name=object_name,
641+
)
642+
643+
# Assert
644+
assert obj is not None
645+
assert obj.name == object_name
646+
assert obj.bucket == f"projects/_/buckets/{_ZONAL_BUCKET}"
647+
assert obj.size == expected_size, (
648+
f"Expected object size {expected_size}, got {obj.size}"
649+
)
650+
651+
# Cleanup
652+
blobs_to_delete.append(storage_client.bucket(_ZONAL_BUCKET).blob(object_name))
653+
del writer
654+
gc.collect()
655+
656+
event_loop.run_until_complete(_run())
Collapse file

‎tests/unit/asyncio/test_async_grpc_client.py‎

Copy file name to clipboardExpand all lines: tests/unit/asyncio/test_async_grpc_client.py
+75Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,78 @@ async def test_delete_object(self, mock_async_storage_client):
254254
assert request.if_generation_not_match == if_generation_not_match
255255
assert request.if_metageneration_match == if_metageneration_match
256256
assert request.if_metageneration_not_match == if_metageneration_not_match
257+
258+
@mock.patch("google.cloud._storage_v2.StorageAsyncClient")
259+
@pytest.mark.asyncio
260+
async def test_get_object(self, mock_async_storage_client):
261+
# Arrange
262+
mock_transport_cls = mock.MagicMock()
263+
mock_async_storage_client.get_transport_class.return_value = mock_transport_cls
264+
mock_gapic_client = mock.AsyncMock()
265+
mock_async_storage_client.return_value = mock_gapic_client
266+
267+
client = async_grpc_client.AsyncGrpcClient(
268+
credentials=_make_credentials(spec=AnonymousCredentials)
269+
)
270+
271+
bucket_name = "bucket"
272+
object_name = "object"
273+
274+
# Act
275+
await client.get_object(
276+
bucket_name,
277+
object_name,
278+
)
279+
280+
# Assert
281+
call_args, call_kwargs = mock_gapic_client.get_object.call_args
282+
request = call_kwargs["request"]
283+
assert request.bucket == "projects/_/buckets/bucket"
284+
assert request.object == "object"
285+
assert request.soft_deleted is False
286+
287+
@mock.patch("google.cloud._storage_v2.StorageAsyncClient")
288+
@pytest.mark.asyncio
289+
async def test_get_object_with_all_parameters(self, mock_async_storage_client):
290+
# Arrange
291+
mock_transport_cls = mock.MagicMock()
292+
mock_async_storage_client.get_transport_class.return_value = mock_transport_cls
293+
mock_gapic_client = mock.AsyncMock()
294+
mock_async_storage_client.return_value = mock_gapic_client
295+
296+
client = async_grpc_client.AsyncGrpcClient(
297+
credentials=_make_credentials(spec=AnonymousCredentials)
298+
)
299+
300+
bucket_name = "bucket"
301+
object_name = "object"
302+
generation = 123
303+
if_generation_match = 456
304+
if_generation_not_match = 789
305+
if_metageneration_match = 111
306+
if_metageneration_not_match = 222
307+
soft_deleted = True
308+
309+
# Act
310+
await client.get_object(
311+
bucket_name,
312+
object_name,
313+
generation=generation,
314+
if_generation_match=if_generation_match,
315+
if_generation_not_match=if_generation_not_match,
316+
if_metageneration_match=if_metageneration_match,
317+
if_metageneration_not_match=if_metageneration_not_match,
318+
soft_deleted=soft_deleted,
319+
)
320+
321+
# Assert
322+
call_args, call_kwargs = mock_gapic_client.get_object.call_args
323+
request = call_kwargs["request"]
324+
assert request.bucket == "projects/_/buckets/bucket"
325+
assert request.object == "object"
326+
assert request.generation == generation
327+
assert request.if_generation_match == if_generation_match
328+
assert request.if_generation_not_match == if_generation_not_match
329+
assert request.if_metageneration_match == if_metageneration_match
330+
assert request.if_metageneration_not_match == if_metageneration_not_match
331+
assert request.soft_deleted is True

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.