Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7b31946

Browse filesBrowse files
authored
feat: add default user agent for grpc (#1726)
Adding a default gcloud-python/{__version__} for grpc client just like the json client
1 parent e730bf5 commit 7b31946
Copy full SHA for 7b31946

File tree

Expand file treeCollapse file tree

2 files changed

+74
-35
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

2 files changed

+74
-35
lines changed
Open diff view settings
Collapse file

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

Copy file name to clipboardExpand all lines: google/cloud/storage/_experimental/asyncio/async_grpc_client.py
+13-6Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,22 @@
1818
from google.cloud._storage_v2.services.storage.transports.base import (
1919
DEFAULT_CLIENT_INFO,
2020
)
21+
from google.cloud.storage import __version__
2122

2223

2324
class AsyncGrpcClient:
2425
"""An asynchronous client for interacting with Google Cloud Storage using the gRPC API.
25-
2626
:type credentials: :class:`~google.auth.credentials.Credentials`
2727
:param credentials: (Optional) The OAuth2 Credentials to use for this
2828
client. If not passed, falls back to the default
2929
inferred from the environment.
30-
3130
:type client_info: :class:`~google.api_core.client_info.ClientInfo`
3231
:param client_info:
3332
The client info used to send a user-agent string along with API
3433
requests. If ``None``, then default info will be used.
35-
3634
:type client_options: :class:`~google.api_core.client_options.ClientOptions`
3735
:param client_options: (Optional) Client options used to set user options
3836
on the client.
39-
4037
:type attempt_direct_path: bool
4138
:param attempt_direct_path:
4239
(Optional) Whether to attempt to use DirectPath for gRPC connections.
@@ -51,6 +48,18 @@ def __init__(
5148
*,
5249
attempt_direct_path=True,
5350
):
51+
if client_info is None:
52+
client_info = DEFAULT_CLIENT_INFO
53+
else:
54+
client_info = client_info
55+
client_info.client_library_version = __version__
56+
# TODO: When metrics all use gccl, this should be removed #9552
57+
if client_info.user_agent is None: # pragma: no branch
58+
client_info.user_agent = ""
59+
agent_version = f"gcloud-python/{__version__}"
60+
if agent_version not in client_info.user_agent:
61+
client_info.user_agent += f" {agent_version} "
62+
5463
self._grpc_client = self._create_async_grpc_client(
5564
credentials=credentials,
5665
client_info=client_info,
@@ -69,8 +78,6 @@ def _create_async_grpc_client(
6978
"grpc_asyncio"
7079
)
7180

72-
if client_info is None:
73-
client_info = DEFAULT_CLIENT_INFO
7481
primary_user_agent = client_info.to_user_agent()
7582

7683
channel = transport_cls.create_channel(
Collapse file

‎tests/unit/asyncio/test_async_grpc_client.py‎

Copy file name to clipboardExpand all lines: tests/unit/asyncio/test_async_grpc_client.py
+61-29Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,8 @@
1616
from google.auth import credentials as auth_credentials
1717
from google.auth.credentials import AnonymousCredentials
1818
from google.api_core import client_info as client_info_lib
19+
from google.cloud.storage import __version__
1920
from google.cloud.storage._experimental.asyncio import async_grpc_client
20-
from google.cloud.storage._experimental.asyncio.async_grpc_client import (
21-
DEFAULT_CLIENT_INFO,
22-
)
2321

2422

2523
def _make_credentials(spec=None):
@@ -36,16 +34,20 @@ def test_constructor_default_options(self, mock_async_storage_client):
3634
mock_async_storage_client.get_transport_class.return_value = mock_transport_cls
3735
mock_creds = _make_credentials()
3836

39-
primary_user_agent = DEFAULT_CLIENT_INFO.to_user_agent()
40-
expected_options = (("grpc.primary_user_agent", primary_user_agent),)
41-
4237
# Act
4338
async_grpc_client.AsyncGrpcClient(credentials=mock_creds)
4439

4540
# Assert
4641
mock_async_storage_client.get_transport_class.assert_called_once_with(
4742
"grpc_asyncio"
4843
)
44+
kwargs = mock_async_storage_client.call_args.kwargs
45+
client_info = kwargs["client_info"]
46+
agent_version = f"gcloud-python/{__version__}"
47+
assert agent_version in client_info.user_agent
48+
primary_user_agent = client_info.to_user_agent()
49+
expected_options = (("grpc.primary_user_agent", primary_user_agent),)
50+
4951
mock_transport_cls.create_channel.assert_called_once_with(
5052
attempt_direct_path=True,
5153
credentials=mock_creds,
@@ -54,11 +56,8 @@ def test_constructor_default_options(self, mock_async_storage_client):
5456
mock_channel = mock_transport_cls.create_channel.return_value
5557
mock_transport_cls.assert_called_once_with(channel=mock_channel)
5658
mock_transport = mock_transport_cls.return_value
57-
mock_async_storage_client.assert_called_once_with(
58-
transport=mock_transport,
59-
client_options=None,
60-
client_info=DEFAULT_CLIENT_INFO,
61-
)
59+
assert kwargs["transport"] is mock_transport
60+
assert kwargs["client_options"] is None
6261

6362
@mock.patch("google.cloud._storage_v2.StorageAsyncClient")
6463
def test_constructor_with_client_info(self, mock_async_storage_client):
@@ -73,6 +72,8 @@ def test_constructor_with_client_info(self, mock_async_storage_client):
7372
credentials=mock_creds, client_info=client_info
7473
)
7574

75+
agent_version = f"gcloud-python/{__version__}"
76+
assert agent_version in client_info.user_agent
7677
primary_user_agent = client_info.to_user_agent()
7778
expected_options = (("grpc.primary_user_agent", primary_user_agent),)
7879

@@ -92,7 +93,11 @@ def test_constructor_disables_directpath(self, mock_async_storage_client):
9293
credentials=mock_creds, attempt_direct_path=False
9394
)
9495

95-
primary_user_agent = DEFAULT_CLIENT_INFO.to_user_agent()
96+
kwargs = mock_async_storage_client.call_args.kwargs
97+
client_info = kwargs["client_info"]
98+
agent_version = f"gcloud-python/{__version__}"
99+
assert agent_version in client_info.user_agent
100+
primary_user_agent = client_info.to_user_agent()
96101
expected_options = (("grpc.primary_user_agent", primary_user_agent),)
97102

98103
mock_transport_cls.create_channel.assert_called_once_with(
@@ -109,43 +114,43 @@ def test_grpc_client_property(self, mock_grpc_gapic_client):
109114
mock_transport_cls = mock.MagicMock()
110115
mock_grpc_gapic_client.get_transport_class.return_value = mock_transport_cls
111116
channel_sentinel = mock.sentinel.channel
112-
113117
mock_transport_cls.create_channel.return_value = channel_sentinel
114-
mock_transport_cls.return_value = mock.sentinel.transport
118+
mock_transport_instance = mock.sentinel.transport
119+
mock_transport_cls.return_value = mock_transport_instance
115120

116121
mock_creds = _make_credentials()
117-
mock_client_info = mock.MagicMock(spec=client_info_lib.ClientInfo)
118-
mock_client_info.to_user_agent.return_value = "test-user-agent"
122+
# Use a real ClientInfo instance instead of a mock to properly test user agent logic
123+
client_info = client_info_lib.ClientInfo(user_agent="test-user-agent")
119124
mock_client_options = mock.sentinel.client_options
120125
mock_attempt_direct_path = mock.sentinel.attempt_direct_path
121126

122127
# Act
123128
client = async_grpc_client.AsyncGrpcClient(
124129
credentials=mock_creds,
125-
client_info=mock_client_info,
130+
client_info=client_info,
126131
client_options=mock_client_options,
127132
attempt_direct_path=mock_attempt_direct_path,
128133
)
134+
retrieved_client = client.grpc_client # This is what is being tested
129135

130-
mock_grpc_gapic_client.get_transport_class.return_value = mock_transport_cls
136+
# Assert - verify that gcloud-python agent version was added
137+
agent_version = f"gcloud-python/{__version__}"
138+
assert agent_version in client_info.user_agent
139+
# Also verify original user_agent is still there
140+
assert "test-user-agent" in client_info.user_agent
131141

132-
mock_transport_cls.create_channel.return_value = channel_sentinel
133-
mock_transport_instance = mock.sentinel.transport
134-
mock_transport_cls.return_value = mock_transport_instance
135-
136-
retrieved_client = client.grpc_client
142+
primary_user_agent = client_info.to_user_agent()
143+
expected_options = (("grpc.primary_user_agent", primary_user_agent),)
137144

138-
# Assert
139-
expected_options = (("grpc.primary_user_agent", "test-user-agent"),)
140145
mock_transport_cls.create_channel.assert_called_once_with(
141146
attempt_direct_path=mock_attempt_direct_path,
142147
credentials=mock_creds,
143148
options=expected_options,
144149
)
145-
mock_transport_cls.assere_with(channel=channel_sentinel)
150+
mock_transport_cls.assert_called_once_with(channel=channel_sentinel)
146151
mock_grpc_gapic_client.assert_called_once_with(
147152
transport=mock_transport_instance,
148-
client_info=mock_client_info,
153+
client_info=client_info,
149154
client_options=mock_client_options,
150155
)
151156
assert retrieved_client is mock_grpc_gapic_client.return_value
@@ -168,12 +173,39 @@ def test_grpc_client_with_anon_creds(self, mock_grpc_gapic_client):
168173
# Assert
169174
assert retrieved_client is mock_grpc_gapic_client.return_value
170175

171-
primary_user_agent = DEFAULT_CLIENT_INFO.to_user_agent()
176+
kwargs = mock_grpc_gapic_client.call_args.kwargs
177+
client_info = kwargs["client_info"]
178+
agent_version = f"gcloud-python/{__version__}"
179+
assert agent_version in client_info.user_agent
180+
primary_user_agent = client_info.to_user_agent()
172181
expected_options = (("grpc.primary_user_agent", primary_user_agent),)
173182

174183
mock_transport_cls.create_channel.assert_called_once_with(
175184
attempt_direct_path=True,
176185
credentials=anonymous_creds,
177186
options=expected_options,
178187
)
179-
mock_transport_cls.assert_called_once_with(channel=channel_sentinel)
188+
189+
@mock.patch("google.cloud._storage_v2.StorageAsyncClient")
190+
def test_user_agent_with_custom_client_info(self, mock_async_storage_client):
191+
"""Test that gcloud-python user agent is appended to existing user agent.
192+
193+
Regression test similar to test__http.py::TestConnection::test_duplicate_user_agent
194+
"""
195+
mock_transport_cls = mock.MagicMock()
196+
mock_async_storage_client.get_transport_class.return_value = mock_transport_cls
197+
mock_creds = _make_credentials()
198+
199+
# Create a client_info with an existing user_agent
200+
client_info = client_info_lib.ClientInfo(user_agent="custom-app/1.0")
201+
202+
# Act
203+
async_grpc_client.AsyncGrpcClient(
204+
credentials=mock_creds,
205+
client_info=client_info,
206+
)
207+
208+
# Assert - verify that gcloud-python version was appended
209+
agent_version = f"gcloud-python/{__version__}"
210+
expected_user_agent = f"custom-app/1.0 {agent_version} "
211+
assert client_info.user_agent == expected_user_agent

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.