Skip to content

Commit ac57b8d

Browse files
authored
feat(experimental): add async grpc client (#1537)
* feat(experimental): add async grpc client * minor changes * remove unused import * update the imports for _storage_v2 * change the default directpath value to True * add more comprehensive unit tests * adding helper methods
1 parent 5674587 commit ac57b8d

File tree

2 files changed

+174
-0
lines changed

2 files changed

+174
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""An async client for interacting with Google Cloud Storage using the gRPC API."""
16+
17+
from google.cloud import _storage_v2 as storage_v2
18+
19+
20+
class AsyncGrpcClient:
21+
"""An asynchronous client for interacting with Google Cloud Storage using the gRPC API.
22+
23+
:type credentials: :class:`~google.auth.credentials.Credentials`
24+
:param credentials: (Optional) The OAuth2 Credentials to use for this
25+
client. If not passed, falls back to the default
26+
inferred from the environment.
27+
28+
:type client_info: :class:`~google.api_core.client_info.ClientInfo`
29+
:param client_info:
30+
The client info used to send a user-agent string along with API
31+
requests. If ``None``, then default info will be used.
32+
33+
:type client_options: :class:`~google.api_core.client_options.ClientOptions` or :class:`dict`
34+
:param client_options: (Optional) Client options used to set user options
35+
on the client.
36+
37+
:type attempt_direct_path: bool
38+
:param attempt_direct_path:
39+
(Optional) Whether to attempt to use DirectPath for gRPC connections.
40+
Defaults to ``True``.
41+
"""
42+
43+
def __init__(
44+
self,
45+
credentials=None,
46+
client_info=None,
47+
client_options=None,
48+
*,
49+
attempt_direct_path=True,
50+
):
51+
self._grpc_client = self._create_async_grpc_client(
52+
credentials=credentials,
53+
client_info=client_info,
54+
client_options=client_options,
55+
attempt_direct_path=attempt_direct_path,
56+
)
57+
58+
def _create_async_grpc_client(
59+
self,
60+
credentials=None,
61+
client_info=None,
62+
client_options=None,
63+
attempt_direct_path=True,
64+
):
65+
transport_cls = storage_v2.StorageAsyncClient.get_transport_class(
66+
"grpc_asyncio"
67+
)
68+
channel = transport_cls.create_channel(attempt_direct_path=attempt_direct_path)
69+
transport = transport_cls(credentials=credentials, channel=channel)
70+
71+
return storage_v2.StorageAsyncClient(
72+
credentials=credentials,
73+
transport=transport,
74+
client_info=client_info,
75+
client_options=client_options,
76+
)
77+
78+
@property
79+
def grpc_client(self):
80+
"""The underlying gRPC client.
81+
82+
This property gives users direct access to the `_storage_v2.StorageAsyncClient`
83+
instance. This can be useful for accessing
84+
newly added or experimental RPCs that are not yet exposed through
85+
the high-level GrpcClient.
86+
Returns:
87+
google.cloud._storage_v2.StorageAsyncClient: The configured GAPIC client.
88+
"""
89+
return self._grpc_client
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import unittest
16+
from unittest import mock
17+
from google.auth import credentials as auth_credentials
18+
19+
20+
def _make_credentials(spec=None):
21+
if spec is None:
22+
return mock.Mock(spec=auth_credentials.Credentials)
23+
return mock.Mock(spec=spec)
24+
25+
26+
class TestAsyncGrpcClient(unittest.TestCase):
27+
@mock.patch("google.cloud._storage_v2.StorageAsyncClient")
28+
def test_constructor_default_options(self, mock_async_storage_client):
29+
from google.cloud.storage._experimental import async_grpc_client
30+
31+
mock_transport_cls = mock.MagicMock()
32+
mock_async_storage_client.get_transport_class.return_value = mock_transport_cls
33+
mock_creds = _make_credentials()
34+
35+
async_grpc_client.AsyncGrpcClient(credentials=mock_creds)
36+
37+
mock_async_storage_client.get_transport_class.assert_called_once_with(
38+
"grpc_asyncio"
39+
)
40+
mock_transport_cls.create_channel.assert_called_once_with(
41+
attempt_direct_path=True
42+
)
43+
mock_channel = mock_transport_cls.create_channel.return_value
44+
mock_transport_cls.assert_called_once_with(
45+
credentials=mock_creds, channel=mock_channel
46+
)
47+
mock_transport = mock_transport_cls.return_value
48+
mock_async_storage_client.assert_called_once_with(
49+
credentials=mock_creds,
50+
transport=mock_transport,
51+
client_options=None,
52+
client_info=None,
53+
)
54+
55+
@mock.patch("google.cloud._storage_v2.StorageAsyncClient")
56+
def test_constructor_disables_directpath(self, mock_async_storage_client):
57+
from google.cloud.storage._experimental import async_grpc_client
58+
59+
mock_transport_cls = mock.MagicMock()
60+
mock_async_storage_client.get_transport_class.return_value = mock_transport_cls
61+
mock_creds = _make_credentials()
62+
63+
async_grpc_client.AsyncGrpcClient(
64+
credentials=mock_creds, attempt_direct_path=False
65+
)
66+
67+
mock_transport_cls.create_channel.assert_called_once_with(
68+
attempt_direct_path=False
69+
)
70+
mock_channel = mock_transport_cls.create_channel.return_value
71+
mock_transport_cls.assert_called_once_with(
72+
credentials=mock_creds, channel=mock_channel
73+
)
74+
75+
@mock.patch("google.cloud._storage_v2.StorageAsyncClient")
76+
def test_grpc_client_property(self, mock_async_storage_client):
77+
from google.cloud.storage._experimental import async_grpc_client
78+
79+
mock_creds = _make_credentials()
80+
81+
client = async_grpc_client.AsyncGrpcClient(credentials=mock_creds)
82+
83+
retrieved_client = client.grpc_client
84+
85+
self.assertIs(retrieved_client, mock_async_storage_client.return_value)

0 commit comments

Comments
 (0)