Skip to content

Conversation

@chandra-siri
Copy link
Collaborator

feat: add context manager to mrd

@product-auto-label product-auto-label bot added size: m Pull request size is medium. api: storage Issues related to the googleapis/python-storage API. labels Jan 26, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @chandra-siri, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces an asynchronous context manager for the AsyncMultiRangeDownloader class, enhancing its usability and ensuring proper resource management in asynchronous operations. By enabling the use of async with statements, the PR simplifies the lifecycle of download operations, automatically handling the opening and closing of the underlying gRPC connection, which leads to more robust and readable code.

Highlights

  • Asynchronous Context Manager Implementation: The AsyncMultiRangeDownloader class now implements the asynchronous context manager protocol by adding __aenter__ and __aexit__ methods. This allows the class to be used with async with statements, automatically handling the opening and closing of the underlying bidi-gRPC connection.
  • Simplified Resource Management in Tests: System tests have been updated to utilize the new async with syntax for AsyncMultiRangeDownloader, replacing explicit open() and close() calls. This demonstrates the intended usage and simplifies resource lifecycle management.
  • Unit Test Coverage for Context Manager: A new unit test, test_async_context_manager_calls_open_and_close, has been added to verify that the __aenter__ method correctly calls open() and __aexit__ correctly calls close() when the AsyncMultiRangeDownloader is used as an asynchronous context manager.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a convenient async context manager for AsyncMultiRangeDownloader, which is a great improvement for ensuring resources are properly managed. The implementation is correct and is accompanied by a new unit test and an update to a system test.

One suggestion for further improvement is to refactor the other system tests in tests/system/test_zonal.py that use AsyncMultiRangeDownloader to also adopt the new async with syntax. This would improve consistency and readability across the test suite.

I've also added a comment with suggestions to enhance the unit tests for the new context manager by covering some important edge cases.

Comment on lines +417 to +444
async def test_async_context_manager_calls_open_and_close(
self, mock_grpc_client, mock_close, mock_open
):
# Arrange
mrd = AsyncMultiRangeDownloader(
mock_grpc_client, _TEST_BUCKET_NAME, _TEST_OBJECT_NAME
)

# To simulate the behavior of open and close changing the stream state
async def open_side_effect():
mrd._is_stream_open = True

async def close_side_effect():
mrd._is_stream_open = False

mock_open.side_effect = open_side_effect
mock_close.side_effect = close_side_effect
mrd._is_stream_open = False

# Act
async with mrd as downloader:
# Assert
mock_open.assert_called_once()
assert downloader == mrd
assert mrd.is_stream_open

mock_close.assert_called_once()
assert not mrd.is_stream_open
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This is a good test for the happy path of the async context manager. To make it more robust, it would be beneficial to also test the behavior in edge cases.

Specifically, consider adding tests for:

  1. When open() fails (e.g., raises an exception). In this scenario, close() should not be called.
  2. When an exception is raised from within the async with block. In this case, close() should still be called to ensure cleanup.

Here are some examples of how you could structure these tests:

Test for open() failure:

@mock.patch("...")
@pytest.mark.asyncio
async def test_context_manager_no_close_on_open_failure(self, mock_grpc_client, mock_close, mock_open):
    mock_open.side_effect = ValueError("Failed to open")
    mrd = AsyncMultiRangeDownloader(
        mock_grpc_client, _TEST_BUCKET_NAME, _TEST_OBJECT_NAME
    )
    
    with pytest.raises(ValueError, match="Failed to open"):
        async with mrd:
            pytest.fail("This block should not be executed.")

    mock_open.assert_called_once()
    mock_close.assert_not_called()

Test for exception within the block:

@mock.patch("...")
@pytest.mark.asyncio
async def test_context_manager_closes_on_exception(self, mock_grpc_client, mock_close, mock_open):
    mrd = AsyncMultiRangeDownloader(
        mock_grpc_client, _TEST_BUCKET_NAME, _TEST_OBJECT_NAME
    )
    # set up side effects for open/close as in the existing test
    async def open_side_effect():
        mrd._is_stream_open = True
    mock_open.side_effect = open_side_effect
    
    with pytest.raises(RuntimeError, match="Oops"):
        async with mrd:
            raise RuntimeError("Oops")

    mock_open.assert_called_once()
    mock_close.assert_called_once()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: storage Issues related to the googleapis/python-storage API. size: m Pull request size is medium.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant