Skip to content

Commit 92b1bf9

Browse files
authored
Add client_info support to client. (#7878)
Also, formally deprecate 'user_agent' argument to 'Client'.
1 parent b7eb9ae commit 92b1bf9

File tree

4 files changed

+75
-35
lines changed

4 files changed

+75
-35
lines changed

packages/google-cloud-spanner/google/cloud/spanner_v1/client.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
* a :class:`~google.cloud.spanner_v1.instance.Instance` owns a
2424
:class:`~google.cloud.spanner_v1.database.Database`
2525
"""
26+
import warnings
2627

2728
from google.api_core.gapic_v1 import client_info
2829

@@ -36,7 +37,6 @@
3637

3738
# pylint: enable=line-too-long
3839

39-
from google.cloud._http import DEFAULT_USER_AGENT
4040
from google.cloud.client import ClientWithProject
4141
from google.cloud.spanner_v1 import __version__
4242
from google.cloud.spanner_v1._helpers import _metadata_with_prefix
@@ -45,6 +45,10 @@
4545

4646
_CLIENT_INFO = client_info.ClientInfo(client_library_version=__version__)
4747
SPANNER_ADMIN_SCOPE = "https://www.googleapis.com/auth/spanner.admin"
48+
_USER_AGENT_DEPRECATED = (
49+
"The 'user_agent' argument to 'Client' is deprecated / unused. "
50+
"Please pass an appropriate 'client_info' instead."
51+
)
4852

4953

5054
class InstanceConfig(object):
@@ -95,29 +99,44 @@ class Client(ClientWithProject):
9599
client. If not provided, defaults to the Google
96100
Application Default Credentials.
97101
102+
:type client_info: :class:`google.api_core.gapic_v1.client_info.ClientInfo`
103+
:param client_info:
104+
(Optional) The client info used to send a user-agent string along with
105+
API requests. If ``None``, then default info will be used. Generally,
106+
you only need to set this if you're developing your own library or
107+
partner tool.
108+
98109
:type user_agent: str
99-
:param user_agent: (Optional) The user agent to be used with API request.
100-
Defaults to :const:`DEFAULT_USER_AGENT`.
110+
:param user_agent:
111+
(Deprecated) The user agent to be used with API request.
112+
Not used.
101113
102114
:raises: :class:`ValueError <exceptions.ValueError>` if both ``read_only``
103115
and ``admin`` are :data:`True`
104116
"""
105117

106118
_instance_admin_api = None
107119
_database_admin_api = None
120+
user_agent = None
108121
_SET_PROJECT = True # Used by from_service_account_json()
109122

110123
SCOPE = (SPANNER_ADMIN_SCOPE,)
111124
"""The scopes required for Google Cloud Spanner."""
112125

113-
def __init__(self, project=None, credentials=None, user_agent=DEFAULT_USER_AGENT):
126+
def __init__(
127+
self, project=None, credentials=None, client_info=_CLIENT_INFO, user_agent=None
128+
):
114129
# NOTE: This API has no use for the _http argument, but sending it
115130
# will have no impact since the _http() @property only lazily
116131
# creates a working HTTP object.
117132
super(Client, self).__init__(
118133
project=project, credentials=credentials, _http=None
119134
)
120-
self.user_agent = user_agent
135+
self._client_info = client_info
136+
137+
if user_agent is not None:
138+
warnings.warn(_USER_AGENT_DEPRECATED, DeprecationWarning, stacklevel=2)
139+
self.user_agent = user_agent
121140

122141
@property
123142
def credentials(self):
@@ -153,7 +172,7 @@ def instance_admin_api(self):
153172
"""Helper for session-related API calls."""
154173
if self._instance_admin_api is None:
155174
self._instance_admin_api = InstanceAdminClient(
156-
credentials=self.credentials, client_info=_CLIENT_INFO
175+
credentials=self.credentials, client_info=self._client_info
157176
)
158177
return self._instance_admin_api
159178

@@ -162,7 +181,7 @@ def database_admin_api(self):
162181
"""Helper for session-related API calls."""
163182
if self._database_admin_api is None:
164183
self._database_admin_api = DatabaseAdminClient(
165-
credentials=self.credentials, client_info=_CLIENT_INFO
184+
credentials=self.credentials, client_info=self._client_info
166185
)
167186
return self._database_admin_api
168187

@@ -175,11 +194,7 @@ def copy(self):
175194
:rtype: :class:`.Client`
176195
:returns: A copy of the current client.
177196
"""
178-
return self.__class__(
179-
project=self.project,
180-
credentials=self._credentials,
181-
user_agent=self.user_agent,
182-
)
197+
return self.__class__(project=self.project, credentials=self._credentials)
183198

184199
def list_instance_configs(self, page_size=None, page_token=None):
185200
"""List available instance configurations for the client's project.

packages/google-cloud-spanner/google/cloud/spanner_v1/database.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,12 @@
1919
import re
2020
import threading
2121

22-
from google.api_core.gapic_v1 import client_info
2322
import google.auth.credentials
2423
from google.protobuf.struct_pb2 import Struct
2524
from google.cloud.exceptions import NotFound
2625
import six
2726

2827
# pylint: disable=ungrouped-imports
29-
from google.cloud.spanner_v1 import __version__
3028
from google.cloud.spanner_v1._helpers import _make_value_pb
3129
from google.cloud.spanner_v1._helpers import _metadata_with_prefix
3230
from google.cloud.spanner_v1.batch import Batch
@@ -46,7 +44,6 @@
4644
# pylint: enable=ungrouped-imports
4745

4846

49-
_CLIENT_INFO = client_info.ClientInfo(client_library_version=__version__)
5047
SPANNER_DATA_SCOPE = "https://www.googleapis.com/auth/spanner.data"
5148

5249

@@ -179,8 +176,9 @@ def spanner_api(self):
179176
credentials = self._instance._client.credentials
180177
if isinstance(credentials, google.auth.credentials.Scoped):
181178
credentials = credentials.with_scopes((SPANNER_DATA_SCOPE,))
179+
client_info = self._instance._client._client_info
182180
self._spanner_api = SpannerClient(
183-
credentials=credentials, client_info=_CLIENT_INFO
181+
credentials=credentials, client_info=client_info
184182
)
185183
return self._spanner_api
186184

packages/google-cloud-spanner/tests/unit/test_client.py

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,24 @@ def _make_one(self, *args, **kwargs):
4949
return self._get_target_class()(*args, **kwargs)
5050

5151
def _constructor_test_helper(
52-
self, expected_scopes, creds, user_agent=None, expected_creds=None
52+
self,
53+
expected_scopes,
54+
creds,
55+
expected_creds=None,
56+
client_info=None,
57+
user_agent=None,
5358
):
5459
from google.cloud.spanner_v1 import client as MUT
5560

56-
user_agent = user_agent or MUT.DEFAULT_USER_AGENT
61+
kwargs = {}
62+
63+
if client_info is not None:
64+
kwargs["client_info"] = expected_client_info = client_info
65+
else:
66+
expected_client_info = MUT._CLIENT_INFO
67+
5768
client = self._make_one(
58-
project=self.PROJECT, credentials=creds, user_agent=user_agent
69+
project=self.PROJECT, credentials=creds, user_agent=user_agent, **kwargs
5970
)
6071

6172
expected_creds = expected_creds or creds.with_scopes.return_value
@@ -66,6 +77,7 @@ def _constructor_test_helper(
6677
creds.with_scopes.assert_called_once_with(expected_scopes)
6778

6879
self.assertEqual(client.project, self.PROJECT)
80+
self.assertIs(client._client_info, expected_client_info)
6981
self.assertEqual(client.user_agent, user_agent)
7082

7183
def test_constructor_default_scopes(self):
@@ -75,7 +87,8 @@ def test_constructor_default_scopes(self):
7587
creds = _make_credentials()
7688
self._constructor_test_helper(expected_scopes, creds)
7789

78-
def test_constructor_custom_user_agent_and_timeout(self):
90+
@mock.patch("warnings.warn")
91+
def test_constructor_custom_user_agent_and_timeout(self, mock_warn):
7992
from google.cloud.spanner_v1 import client as MUT
8093

8194
CUSTOM_USER_AGENT = "custom-application"
@@ -84,6 +97,17 @@ def test_constructor_custom_user_agent_and_timeout(self):
8497
self._constructor_test_helper(
8598
expected_scopes, creds, user_agent=CUSTOM_USER_AGENT
8699
)
100+
mock_warn.assert_called_once_with(
101+
MUT._USER_AGENT_DEPRECATED, DeprecationWarning, stacklevel=2
102+
)
103+
104+
def test_constructor_custom_client_info(self):
105+
from google.cloud.spanner_v1 import client as MUT
106+
107+
client_info = mock.Mock()
108+
expected_scopes = (MUT.SPANNER_ADMIN_SCOPE,)
109+
creds = _make_credentials()
110+
self._constructor_test_helper(expected_scopes, creds, client_info=client_info)
87111

88112
def test_constructor_implicit_credentials(self):
89113
creds = _make_credentials()
@@ -102,10 +126,13 @@ def test_constructor_credentials_wo_create_scoped(self):
102126
self._constructor_test_helper(expected_scopes, creds)
103127

104128
def test_instance_admin_api(self):
105-
from google.cloud.spanner_v1.client import _CLIENT_INFO, SPANNER_ADMIN_SCOPE
129+
from google.cloud.spanner_v1.client import SPANNER_ADMIN_SCOPE
106130

107131
credentials = _make_credentials()
108-
client = self._make_one(project=self.PROJECT, credentials=credentials)
132+
client_info = mock.Mock()
133+
client = self._make_one(
134+
project=self.PROJECT, credentials=credentials, client_info=client_info
135+
)
109136
expected_scopes = (SPANNER_ADMIN_SCOPE,)
110137

111138
inst_module = "google.cloud.spanner_v1.client.InstanceAdminClient"
@@ -119,16 +146,19 @@ def test_instance_admin_api(self):
119146
self.assertIs(again, api)
120147

121148
instance_admin_client.assert_called_once_with(
122-
credentials=credentials.with_scopes.return_value, client_info=_CLIENT_INFO
149+
credentials=credentials.with_scopes.return_value, client_info=client_info
123150
)
124151

125152
credentials.with_scopes.assert_called_once_with(expected_scopes)
126153

127154
def test_database_admin_api(self):
128-
from google.cloud.spanner_v1.client import _CLIENT_INFO, SPANNER_ADMIN_SCOPE
155+
from google.cloud.spanner_v1.client import SPANNER_ADMIN_SCOPE
129156

130157
credentials = _make_credentials()
131-
client = self._make_one(project=self.PROJECT, credentials=credentials)
158+
client_info = mock.Mock()
159+
client = self._make_one(
160+
project=self.PROJECT, credentials=credentials, client_info=client_info
161+
)
132162
expected_scopes = (SPANNER_ADMIN_SCOPE,)
133163

134164
db_module = "google.cloud.spanner_v1.client.DatabaseAdminClient"
@@ -142,7 +172,7 @@ def test_database_admin_api(self):
142172
self.assertIs(again, api)
143173

144174
database_admin_client.assert_called_once_with(
145-
credentials=credentials.with_scopes.return_value, client_info=_CLIENT_INFO
175+
credentials=credentials.with_scopes.return_value, client_info=client_info
146176
)
147177

148178
credentials.with_scopes.assert_called_once_with(expected_scopes)
@@ -152,14 +182,11 @@ def test_copy(self):
152182
# Make sure it "already" is scoped.
153183
credentials.requires_scopes = False
154184

155-
client = self._make_one(
156-
project=self.PROJECT, credentials=credentials, user_agent=self.USER_AGENT
157-
)
185+
client = self._make_one(project=self.PROJECT, credentials=credentials)
158186

159187
new_client = client.copy()
160188
self.assertIs(new_client._credentials, client._credentials)
161189
self.assertEqual(new_client.project, client.project)
162-
self.assertEqual(new_client.user_agent, client.user_agent)
163190

164191
def test_credentials_property(self):
165192
credentials = _make_credentials()

packages/google-cloud-spanner/tests/unit/test_database.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,8 @@ def test_name_property(self):
231231
self.assertEqual(database.name, expected_name)
232232

233233
def test_spanner_api_property_w_scopeless_creds(self):
234-
from google.cloud.spanner_v1.database import _CLIENT_INFO
235-
236234
client = _Client()
235+
client_info = client._client_info = mock.Mock()
237236
credentials = client.credentials = object()
238237
instance = _Instance(self.INSTANCE_NAME, client=client)
239238
pool = _Pool()
@@ -251,12 +250,12 @@ def test_spanner_api_property_w_scopeless_creds(self):
251250
self.assertIs(again, api)
252251

253252
spanner_client.assert_called_once_with(
254-
credentials=credentials, client_info=_CLIENT_INFO
253+
credentials=credentials, client_info=client_info
255254
)
256255

257256
def test_spanner_api_w_scoped_creds(self):
258257
import google.auth.credentials
259-
from google.cloud.spanner_v1.database import _CLIENT_INFO, SPANNER_DATA_SCOPE
258+
from google.cloud.spanner_v1.database import SPANNER_DATA_SCOPE
260259

261260
class _CredentialsWithScopes(google.auth.credentials.Scoped):
262261
def __init__(self, scopes=(), source=None):
@@ -271,6 +270,7 @@ def with_scopes(self, scopes):
271270

272271
expected_scopes = (SPANNER_DATA_SCOPE,)
273272
client = _Client()
273+
client_info = client._client_info = mock.Mock()
274274
credentials = client.credentials = _CredentialsWithScopes()
275275
instance = _Instance(self.INSTANCE_NAME, client=client)
276276
pool = _Pool()
@@ -290,7 +290,7 @@ def with_scopes(self, scopes):
290290
self.assertEqual(len(spanner_client.call_args_list), 1)
291291
called_args, called_kw = spanner_client.call_args
292292
self.assertEqual(called_args, ())
293-
self.assertEqual(called_kw["client_info"], _CLIENT_INFO)
293+
self.assertEqual(called_kw["client_info"], client_info)
294294
scoped = called_kw["credentials"]
295295
self.assertEqual(scoped._scopes, expected_scopes)
296296
self.assertIs(scoped._source, credentials)

0 commit comments

Comments
 (0)