Skip to content
This repository was archived by the owner on Mar 31, 2026. It is now read-only.

Commit 5ac2808

Browse files
authored
feat: add context manager to mrd (#1724)
feat: add context manager to mrd
1 parent dbd162b commit 5ac2808

3 files changed

Lines changed: 58 additions & 7 deletions

File tree

‎google/cloud/storage/_experimental/asyncio/async_multi_range_downloader.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,16 @@ def __init__(
209209
self._download_ranges_id_to_pending_read_ids= {}
210210
self.persisted_size: Optional[int] =None# updated after opening the stream
211211

212+
asyncdef__aenter__(self):
213+
"""Opens the underlying bidi-gRPC connection to read from the object."""
214+
awaitself.open()
215+
returnself
216+
217+
asyncdef__aexit__(self, exc_type, exc_val, exc_tb):
218+
"""Closes the underlying bidi-gRPC connection."""
219+
ifself.is_stream_open:
220+
awaitself.close()
221+
212222
def_on_open_error(self, exc):
213223
"""Extracts routing token and read handle on redirect error during open."""
214224
routing_token, read_handle=_handle_redirect(exc)

‎tests/system/test_zonal.py‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,19 +116,19 @@ async def _run():
116116
assertobject_metadata.size==object_size
117117
assertint(object_metadata.checksums.crc32c) ==object_checksum
118118

119-
mrd=AsyncMultiRangeDownloader(grpc_client, _ZONAL_BUCKET, object_name)
120119
buffer=BytesIO()
121-
awaitmrd.open()
122-
# (0, 0) means read the whole object
123-
awaitmrd.download_ranges([(0, 0, buffer)])
124-
awaitmrd.close()
120+
asyncwithAsyncMultiRangeDownloader(
121+
grpc_client, _ZONAL_BUCKET, object_name
122+
) asmrd:
123+
# (0, 0) means read the whole object
124+
awaitmrd.download_ranges([(0, 0, buffer)])
125+
assertmrd.persisted_size==object_size
126+
125127
assertbuffer.getvalue() ==object_data
126-
assertmrd.persisted_size==object_size
127128

128129
# Clean up; use json client (i.e. `storage_client` fixture) to delete.
129130
blobs_to_delete.append(storage_client.bucket(_ZONAL_BUCKET).blob(object_name))
130131
delwriter
131-
delmrd
132132
gc.collect()
133133

134134
event_loop.run_until_complete(_run())

‎tests/unit/asyncio/test_async_multi_range_downloader.py‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,3 +401,44 @@ async def test_download_ranges_raises_on_checksum_mismatch(
401401

402402
assert"Checksum mismatch"instr(exc_info.value)
403403
mock_checksum_class.assert_called_once_with(test_data)
404+
405+
@mock.patch(
406+
"google.cloud.storage._experimental.asyncio.async_multi_range_downloader.AsyncMultiRangeDownloader.open",
407+
new_callable=AsyncMock,
408+
)
409+
@mock.patch(
410+
"google.cloud.storage._experimental.asyncio.async_multi_range_downloader.AsyncMultiRangeDownloader.close",
411+
new_callable=AsyncMock,
412+
)
413+
@mock.patch(
414+
"google.cloud.storage._experimental.asyncio.async_grpc_client.AsyncGrpcClient.grpc_client"
415+
)
416+
@pytest.mark.asyncio
417+
asyncdeftest_async_context_manager_calls_open_and_close(
418+
self, mock_grpc_client, mock_close, mock_open
419+
):
420+
# Arrange
421+
mrd=AsyncMultiRangeDownloader(
422+
mock_grpc_client, _TEST_BUCKET_NAME, _TEST_OBJECT_NAME
423+
)
424+
425+
# To simulate the behavior of open and close changing the stream state
426+
asyncdefopen_side_effect():
427+
mrd._is_stream_open=True
428+
429+
asyncdefclose_side_effect():
430+
mrd._is_stream_open=False
431+
432+
mock_open.side_effect=open_side_effect
433+
mock_close.side_effect=close_side_effect
434+
mrd._is_stream_open=False
435+
436+
# Act
437+
asyncwithmrdasdownloader:
438+
# Assert
439+
mock_open.assert_called_once()
440+
assertdownloader==mrd
441+
assertmrd.is_stream_open
442+
443+
mock_close.assert_called_once()
444+
assertnotmrd.is_stream_open

0 commit comments

Comments
 (0)