diff --git a/CHANGELOG.md b/CHANGELOG.md index 144092d1c..329740c33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ [1]: https://pypi.org/project/google-cloud-storage/#history +## [3.5.0](https://github.com/googleapis/python-storage/compare/v3.4.1...v3.5.0) (2025-11-05) + + +### Features + +* **experimental:** Add base resumption strategy for bidi streams ([#1594](https://github.com/googleapis/python-storage/issues/1594)) ([5fb85ea](https://github.com/googleapis/python-storage/commit/5fb85ea544dcc9ed9dca65957c872c3811f02b87)) +* **experimental:** Add checksum for bidi reads operation ([#1566](https://github.com/googleapis/python-storage/issues/1566)) ([93ce515](https://github.com/googleapis/python-storage/commit/93ce515d60f0ac77ab83680ba2b4d6a9f57e75d0)) +* **experimental:** Add read resumption strategy ([#1599](https://github.com/googleapis/python-storage/issues/1599)) ([5d5e895](https://github.com/googleapis/python-storage/commit/5d5e895e173075da557b58614fecc84086aaf9cb)) +* **experimental:** Handle BidiReadObjectRedirectedError for bidi reads ([#1600](https://github.com/googleapis/python-storage/issues/1600)) ([71b0f8a](https://github.com/googleapis/python-storage/commit/71b0f8a368a61bed9bd793a059f980562061223e)) +* Indicate that md5 is used as a CRC ([#1522](https://github.com/googleapis/python-storage/issues/1522)) ([961536c](https://github.com/googleapis/python-storage/commit/961536c7bf3652a824c207754317030526b9dd28)) +* Provide option to update user_agent ([#1596](https://github.com/googleapis/python-storage/issues/1596)) ([02f1451](https://github.com/googleapis/python-storage/commit/02f1451aaa8dacd10a862e97abb62ae48249b9b4)) + + +### Bug Fixes + +* Deprecate credentials_file argument ([74415a2](https://github.com/googleapis/python-storage/commit/74415a2a120e9bfa42f4f5fc8bd2f8e0d4cf5d18)) +* Flaky system tests for resumable_media ([#1592](https://github.com/googleapis/python-storage/issues/1592)) ([7fee3dd](https://github.com/googleapis/python-storage/commit/7fee3dd3390cfb5475a39d8f8272ea825dbda449)) +* Make `download_ranges` compatible with `asyncio.create_task(..)` ([#1591](https://github.com/googleapis/python-storage/issues/1591)) ([faf8b83](https://github.com/googleapis/python-storage/commit/faf8b83b1f0ac378f8f6f47ce33dc23a866090c9)) +* Make `download_ranges` compatible with `asyncio.create_task(..)` ([#1591](https://github.com/googleapis/python-storage/issues/1591)) ([faf8b83](https://github.com/googleapis/python-storage/commit/faf8b83b1f0ac378f8f6f47ce33dc23a866090c9)) +* Redact sensitive data from OTEL traces and fix env var parsing ([#1553](https://github.com/googleapis/python-storage/issues/1553)) ([a38ca19](https://github.com/googleapis/python-storage/commit/a38ca1977694def98f65ae7239e300a987bbd262)) +* Redact sensitive data from OTEL traces and fix env var parsing ([#1553](https://github.com/googleapis/python-storage/issues/1553)) ([a38ca19](https://github.com/googleapis/python-storage/commit/a38ca1977694def98f65ae7239e300a987bbd262)) +* Use separate header object for each upload in Transfer Manager MPU ([#1595](https://github.com/googleapis/python-storage/issues/1595)) ([0d867bd](https://github.com/googleapis/python-storage/commit/0d867bd4f405d2dbeca1edfc8072080c5a96c1cd)) + ## [3.4.1](https://github.com/googleapis/python-storage/compare/v3.4.0...v3.5.0) (2025-10-08) ### Bug Fixes diff --git a/google/cloud/storage/_experimental/asyncio/retry/base_strategy.py b/google/cloud/storage/_experimental/asyncio/retry/base_strategy.py index 8a06608b2..e32125069 100644 --- a/google/cloud/storage/_experimental/asyncio/retry/base_strategy.py +++ b/google/cloud/storage/_experimental/asyncio/retry/base_strategy.py @@ -1,6 +1,7 @@ import abc from typing import Any, Iterable + class _BaseResumptionStrategy(abc.ABC): """Abstract base class defining the interface for a bidi stream resumption strategy. diff --git a/google/cloud/storage/_experimental/asyncio/retry/reads_resumption_strategy.py b/google/cloud/storage/_experimental/asyncio/retry/reads_resumption_strategy.py index 9c82cf7f0..d5d080358 100644 --- a/google/cloud/storage/_experimental/asyncio/retry/reads_resumption_strategy.py +++ b/google/cloud/storage/_experimental/asyncio/retry/reads_resumption_strategy.py @@ -7,9 +7,13 @@ ) from google.cloud._storage_v2.types.storage import BidiReadObjectRedirectedError + class _DownloadState: """A helper class to track the state of a single range download.""" - def __init__(self, initial_offset: int, initial_length: int, user_buffer: IO[bytes]): + + def __init__( + self, initial_offset: int, initial_length: int, user_buffer: IO[bytes] + ): self.initial_offset = initial_offset self.initial_length = initial_length self.user_buffer = user_buffer @@ -42,7 +46,9 @@ def generate_requests(self, state: dict) -> List[storage_v2.ReadRange]: pending_requests.append(new_request) return pending_requests - def update_state_from_response(self, response: storage_v2.BidiReadObjectResponse, state: dict) -> None: + def update_state_from_response( + self, response: storage_v2.BidiReadObjectResponse, state: dict + ) -> None: """Processes a server response, performs integrity checks, and updates state.""" for object_data_range in response.object_data_ranges: read_id = object_data_range.read_range.read_id @@ -62,8 +68,13 @@ def update_state_from_response(self, response: storage_v2.BidiReadObjectResponse # Final Byte Count Verification if object_data_range.range_end: read_state.is_complete = True - if read_state.initial_length != 0 and read_state.bytes_written != read_state.initial_length: - raise DataCorruption(response, f"Byte count mismatch for read_id {read_id}") + if ( + read_state.initial_length != 0 + and read_state.bytes_written != read_state.initial_length + ): + raise DataCorruption( + response, f"Byte count mismatch for read_id {read_id}" + ) async def recover_state_on_failure(self, error: Exception, state: Any) -> None: """Handles BidiReadObjectRedirectedError for reads.""" @@ -71,4 +82,4 @@ async def recover_state_on_failure(self, error: Exception, state: Any) -> None: # and store it on the shared state object. cause = getattr(error, "cause", error) if isinstance(cause, BidiReadObjectRedirectedError): - state['routing_token'] = cause.routing_token + state["routing_token"] = cause.routing_token diff --git a/google/cloud/storage/version.py b/google/cloud/storage/version.py index 71133df01..13194aa56 100644 --- a/google/cloud/storage/version.py +++ b/google/cloud/storage/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "3.4.1" +__version__ = "3.5.0" diff --git a/tests/unit/asyncio/retry/test_reads_resumption_strategy.py b/tests/unit/asyncio/retry/test_reads_resumption_strategy.py index fdde74f62..e6b343f86 100644 --- a/tests/unit/asyncio/retry/test_reads_resumption_strategy.py +++ b/tests/unit/asyncio/retry/test_reads_resumption_strategy.py @@ -107,7 +107,9 @@ def test_update_state_processes_single_chunk_successfully(self): response = storage_v2.BidiReadObjectResponse( object_data_ranges=[ storage_v2.types.ObjectRangeData( - read_range=storage_v2.ReadRange(read_id=_READ_ID, read_offset=0, read_length=len(data)), + read_range=storage_v2.ReadRange( + read_id=_READ_ID, read_offset=0, read_length=len(data) + ), checksummed_data=storage_v2.ChecksummedData(content=data), ) ] @@ -130,7 +132,9 @@ def test_update_state_from_response_offset_mismatch(self): response = storage_v2.BidiReadObjectResponse( object_data_ranges=[ storage_v2.types.ObjectRangeData( - read_range=storage_v2.ReadRange(read_id=_READ_ID, read_offset=0, read_length=4), + read_range=storage_v2.ReadRange( + read_id=_READ_ID, read_offset=0, read_length=4 + ), checksummed_data=storage_v2.ChecksummedData(content=b"data"), ) ] @@ -149,7 +153,9 @@ def test_update_state_from_response_final_byte_count_mismatch(self): response = storage_v2.BidiReadObjectResponse( object_data_ranges=[ storage_v2.types.ObjectRangeData( - read_range=storage_v2.ReadRange(read_id=_READ_ID, read_offset=0, read_length=4), + read_range=storage_v2.ReadRange( + read_id=_READ_ID, read_offset=0, read_length=4 + ), checksummed_data=storage_v2.ChecksummedData(content=b"data"), range_end=True, ) @@ -171,7 +177,9 @@ def test_update_state_from_response_completes_download(self): response = storage_v2.BidiReadObjectResponse( object_data_ranges=[ storage_v2.types.ObjectRangeData( - read_range=storage_v2.ReadRange(read_id=_READ_ID, read_offset=0, read_length=len(data)), + read_range=storage_v2.ReadRange( + read_id=_READ_ID, read_offset=0, read_length=len(data) + ), checksummed_data=storage_v2.ChecksummedData(content=data), range_end=True, ) @@ -195,7 +203,9 @@ def test_update_state_from_response_completes_download_zero_length(self): response = storage_v2.BidiReadObjectResponse( object_data_ranges=[ storage_v2.types.ObjectRangeData( - read_range=storage_v2.ReadRange(read_id=_READ_ID, read_offset=0, read_length=len(data)), + read_range=storage_v2.ReadRange( + read_id=_READ_ID, read_offset=0, read_length=len(data) + ), checksummed_data=storage_v2.ChecksummedData(content=data), range_end=True, ) @@ -215,9 +225,7 @@ async def test_recover_state_on_failure_handles_redirect(self): self.assertIsNone(state.get("routing_token")) dummy_token = "dummy-routing-token" - redirect_error = BidiReadObjectRedirectedError( - routing_token=dummy_token - ) + redirect_error = BidiReadObjectRedirectedError(routing_token=dummy_token) final_error = exceptions.RetryError("Retry failed", cause=redirect_error)