Skip to content

Commit 10af2a5

Browse files
committed
persist custom metadata on append and download_ranges calls for resume
1 parent b1c2b3b commit 10af2a5

4 files changed

Lines changed: 127 additions & 2 deletions

File tree

packages/google-cloud-storage/google/cloud/storage/asyncio/async_appendable_object_writer.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,9 @@ async def append(
411411
412412
:raises ValueError: If the stream is not open.
413413
"""
414-
if metadata is None:
414+
if metadata is not None:
415+
self.metadata = metadata
416+
else:
415417
metadata = self.metadata
416418

417419
if not self._is_stream_open:

packages/google-cloud-storage/google/cloud/storage/asyncio/async_multi_range_downloader.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,9 @@ async def download_ranges(
416416
417417
"""
418418

419-
if metadata is None:
419+
if metadata is not None:
420+
self.metadata = metadata
421+
else:
420422
metadata = self.metadata
421423

422424
if len(read_ranges) > 1000:

packages/google-cloud-storage/tests/unit/asyncio/test_async_appendable_object_writer.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,3 +548,45 @@ async def test_append_persists_metadata_on_resumption(self, mock_appendable_writ
548548

549549
# Assert second open (during retry) used the same test_metadata
550550
mock_stream.open.assert_called_with(metadata=test_metadata)
551+
552+
@pytest.mark.asyncio
553+
async def test_append_updates_and_persists_metadata_on_resumption(
554+
self, mock_appendable_writer
555+
):
556+
# Arrange
557+
mock_client = mock_appendable_writer["mock_client"]
558+
mock_stream = mock_appendable_writer["mock_stream"]
559+
560+
initial_metadata = [("initial-key", "initial-value")]
561+
updated_metadata = [("updated-key", "updated-value")]
562+
writer = self._make_one(mock_client)
563+
564+
# Act - Open with initial metadata
565+
await writer.open(metadata=initial_metadata)
566+
assert writer.metadata == initial_metadata
567+
568+
# Setup resumption trigger when append is called
569+
retryable_exc = exceptions.ServiceUnavailable("Retry me")
570+
mock_stream.send.side_effect = retryable_exc
571+
mock_stream.open.reset_mock()
572+
573+
from google.api_core.retry_async import AsyncRetry
574+
fast_retry = AsyncRetry(
575+
predicate=lambda e: True,
576+
initial=0.01,
577+
maximum=0.01,
578+
multiplier=1.0,
579+
deadline=0.1,
580+
)
581+
582+
from google.api_core.exceptions import RetryError
583+
try:
584+
await writer.append(
585+
b"data", retry_policy=fast_retry, metadata=updated_metadata
586+
)
587+
except RetryError:
588+
pass
589+
590+
# Assert writer.metadata was updated and used during stream reopening
591+
assert writer.metadata == updated_metadata
592+
mock_stream.open.assert_called_with(metadata=updated_metadata)

packages/google-cloud-storage/tests/unit/asyncio/test_async_multi_range_downloader.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,3 +803,82 @@ async def fake_reopen_stream(broken_gen, stream_factory):
803803
# Verify mock_stream_2 was opened with test_metadata
804804
# (It should have been opened during the retry attempts)
805805
mock_stream_2.open.assert_called_with(metadata=test_metadata)
806+
807+
@mock.patch(
808+
"google.cloud.storage.asyncio.async_multi_range_downloader.generate_random_56_bit_integer"
809+
)
810+
@mock.patch(
811+
"google.cloud.storage.asyncio.async_multi_range_downloader._AsyncReadObjectStream"
812+
)
813+
@pytest.mark.asyncio
814+
async def test_download_ranges_updates_and_persists_metadata_on_resumption(
815+
self, mock_cls_async_read_object_stream, mock_random_int
816+
):
817+
# Arrange
818+
mock_client = mock.MagicMock()
819+
mock_client.grpc_client = mock.AsyncMock()
820+
821+
mock_stream_1 = mock.MagicMock()
822+
mock_stream_1.open = AsyncMock()
823+
mock_stream_1.generation_number = _TEST_GENERATION_NUMBER
824+
mock_stream_1.persisted_size = _TEST_OBJECT_SIZE
825+
mock_stream_1.read_handle = _TEST_READ_HANDLE
826+
mock_stream_1.is_stream_open = True
827+
828+
mock_stream_2 = mock.MagicMock()
829+
mock_stream_2.open = AsyncMock()
830+
mock_stream_2.generation_number = _TEST_GENERATION_NUMBER
831+
mock_stream_2.persisted_size = _TEST_OBJECT_SIZE
832+
mock_stream_2.read_handle = _TEST_READ_HANDLE
833+
mock_stream_2.is_stream_open = True
834+
835+
mock_cls_async_read_object_stream.side_effect = [mock_stream_1, mock_stream_2]
836+
837+
initial_metadata = [("initial-key", "initial-value")]
838+
updated_metadata = [("updated-key", "updated-value")]
839+
840+
# Act - Create MRD with initial metadata
841+
mrd = await AsyncMultiRangeDownloader.create_mrd(
842+
mock_client, _TEST_BUCKET_NAME, _TEST_OBJECT_NAME, metadata=initial_metadata
843+
)
844+
assert mrd.metadata == initial_metadata
845+
846+
from google.api_core import exceptions as core_exceptions
847+
retryable_exc = core_exceptions.ServiceUnavailable("Retry me")
848+
849+
mrd._multiplexer = mock.MagicMock()
850+
mrd._multiplexer.stream_generation = 1
851+
852+
async def fake_reopen_stream(broken_gen, stream_factory):
853+
await stream_factory()
854+
855+
mrd._multiplexer.reopen_stream = AsyncMock(side_effect=fake_reopen_stream)
856+
mrd._multiplexer.send = AsyncMock(side_effect=retryable_exc)
857+
858+
mock_queue = mock.AsyncMock()
859+
mrd._multiplexer.register.return_value = mock_queue
860+
from google.cloud.storage.asyncio._stream_multiplexer import _StreamError
861+
mock_queue.get.return_value = _StreamError(retryable_exc, generation=1)
862+
863+
mock_random_int.return_value = 123
864+
865+
from google.api_core.retry_async import AsyncRetry
866+
fast_retry = AsyncRetry(
867+
predicate=lambda e: True,
868+
initial=0.01,
869+
maximum=0.01,
870+
multiplier=1.0,
871+
deadline=0.1,
872+
)
873+
874+
buffer = BytesIO()
875+
from google.api_core.exceptions import RetryError
876+
try:
877+
await mrd.download_ranges(
878+
[(0, 10, buffer)], retry_policy=fast_retry, metadata=updated_metadata
879+
)
880+
except RetryError:
881+
pass
882+
883+
assert mrd.metadata == updated_metadata
884+
mock_stream_2.open.assert_called_with(metadata=updated_metadata)

0 commit comments

Comments
 (0)