From 71977e1bc84880747434855dcf24c4f4a3e211db Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 7 May 2019 11:40:02 -0400 Subject: [PATCH 1/3] Add client_info support to client / connection. Toward #7825. --- storage/google/cloud/storage/_http.py | 14 ++++++---- storage/google/cloud/storage/client.py | 11 ++++++-- storage/tests/unit/test__http.py | 4 +-- storage/tests/unit/test_client.py | 38 ++++++++++++++++++++------ 4 files changed, 49 insertions(+), 18 deletions(-) diff --git a/storage/google/cloud/storage/_http.py b/storage/google/cloud/storage/_http.py index 9d05a8eb360c..3dac5dece2ff 100644 --- a/storage/google/cloud/storage/_http.py +++ b/storage/google/cloud/storage/_http.py @@ -19,16 +19,22 @@ from google.cloud.storage import __version__ -_CLIENT_INFO = _http.CLIENT_INFO_TEMPLATE.format(__version__) - - class Connection(_http.JSONConnection): """A connection to Google Cloud Storage via the JSON REST API. :type client: :class:`~google.cloud.storage.client.Client` :param client: The client that owns the current connection. + + :type client_info: :class:`~google.api_core.client_info.ClientInfo` + :param client_info: (Optional) instance used to generate user agent. """ + def __init__(self, client, client_info=None): + super(Connection, self).__init__(client, client_info) + + self._client_info.gapic_version = __version__ + self._client_info.client_library_version = __version__ + API_BASE_URL = _http.API_BASE_URL """The base of the API call URL.""" @@ -37,5 +43,3 @@ class Connection(_http.JSONConnection): API_URL_TEMPLATE = "{api_base_url}/storage/{api_version}{path}" """A template for the URL of a particular API call.""" - - _EXTRA_HEADERS = {_http.CLIENT_INFO_HEADER: _CLIENT_INFO} diff --git a/storage/google/cloud/storage/client.py b/storage/google/cloud/storage/client.py index ae2b9b16a611..266c41102ca3 100644 --- a/storage/google/cloud/storage/client.py +++ b/storage/google/cloud/storage/client.py @@ -51,6 +51,13 @@ class Client(ClientWithProject): ``credentials`` for the current object. This parameter should be considered private, and could change in the future. + + :type client_info: :class:`~google.api_core.client_info.ClientInfo: + :param client_info: + The client info used to send a user-agent string along with API + requests. If ``None``, then default info will be used. Generally, + you only need to set this if you're developing your own library + or partner tool. """ SCOPE = ( @@ -60,7 +67,7 @@ class Client(ClientWithProject): ) """The scopes required for authenticating as a Cloud Storage consumer.""" - def __init__(self, project=_marker, credentials=None, _http=None): + def __init__(self, project=_marker, credentials=None, _http=None, client_info=None): self._base_connection = None if project is None: no_project = True @@ -74,7 +81,7 @@ def __init__(self, project=_marker, credentials=None, _http=None): ) if no_project: self.project = None - self._connection = Connection(self) + self._connection = Connection(self, client_info=client_info) self._batch_stack = _LocalStack() @classmethod diff --git a/storage/tests/unit/test__http.py b/storage/tests/unit/test__http.py index ca9ef850b7b3..85eb87942860 100644 --- a/storage/tests/unit/test__http.py +++ b/storage/tests/unit/test__http.py @@ -48,8 +48,8 @@ def test_extra_headers(self): expected_headers = { "Accept-Encoding": "gzip", - base_http.CLIENT_INFO_HEADER: MUT._CLIENT_INFO, - "User-Agent": conn.USER_AGENT, + base_http.CLIENT_INFO_HEADER: conn.user_agent, + "User-Agent": conn.user_agent, } expected_uri = conn.build_api_url("/rainbow") http.request.assert_called_once_with( diff --git a/storage/tests/unit/test_client.py b/storage/tests/unit/test_client.py index 62854eb71caa..83daad9eff38 100644 --- a/storage/tests/unit/test_client.py +++ b/storage/tests/unit/test_client.py @@ -60,50 +60,70 @@ def _make_one(self, *args, **kw): return self._get_target_class()(*args, **kw) def test_ctor_connection_type(self): + from google.cloud._http import ClientInfo from google.cloud.storage._http import Connection PROJECT = "PROJECT" - CREDENTIALS = _make_credentials() + credentials = _make_credentials() - client = self._make_one(project=PROJECT, credentials=CREDENTIALS) + client = self._make_one(project=PROJECT, credentials=credentials) self.assertEqual(client.project, PROJECT) self.assertIsInstance(client._connection, Connection) - self.assertIs(client._connection.credentials, CREDENTIALS) + self.assertIs(client._connection.credentials, credentials) self.assertIsNone(client.current_batch) self.assertEqual(list(client._batch_stack), []) + self.assertIsInstance(client._connection._client_info, ClientInfo) def test_ctor_wo_project(self): from google.cloud.storage._http import Connection PROJECT = "PROJECT" - CREDENTIALS = _make_credentials() + credentials = _make_credentials() ddp_patch = mock.patch( "google.cloud.client._determine_default_project", return_value=PROJECT ) with ddp_patch: - client = self._make_one(credentials=CREDENTIALS) + client = self._make_one(credentials=credentials) self.assertEqual(client.project, PROJECT) self.assertIsInstance(client._connection, Connection) - self.assertIs(client._connection.credentials, CREDENTIALS) + self.assertIs(client._connection.credentials, credentials) self.assertIsNone(client.current_batch) self.assertEqual(list(client._batch_stack), []) def test_ctor_w_project_explicit_none(self): from google.cloud.storage._http import Connection - CREDENTIALS = _make_credentials() + credentials = _make_credentials() - client = self._make_one(project=None, credentials=CREDENTIALS) + client = self._make_one(project=None, credentials=credentials) + + self.assertIsNone(client.project) + self.assertIsInstance(client._connection, Connection) + self.assertIs(client._connection.credentials, credentials) + self.assertIsNone(client.current_batch) + self.assertEqual(list(client._batch_stack), []) + + def test_ctor_w_client_info(self): + from google.cloud._http import ClientInfo + from google.cloud.storage._http import Connection + + credentials = _make_credentials() + client_info = ClientInfo() + + client = self._make_one( + project=None, credentials=credentials, client_info=client_info + ) self.assertIsNone(client.project) self.assertIsInstance(client._connection, Connection) - self.assertIs(client._connection.credentials, CREDENTIALS) + self.assertIs(client._connection.credentials, credentials) self.assertIsNone(client.current_batch) self.assertEqual(list(client._batch_stack), []) + self.assertIs(client._connection._client_info, client_info) def test_create_anonymous_client(self): from google.auth.credentials import AnonymousCredentials From 3b8f4e04fb721195ea72f90a5132a44675af2f65 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 7 May 2019 12:33:19 -0400 Subject: [PATCH 2/3] Docstring typo. --- storage/google/cloud/storage/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/google/cloud/storage/client.py b/storage/google/cloud/storage/client.py index 266c41102ca3..8541cc51469c 100644 --- a/storage/google/cloud/storage/client.py +++ b/storage/google/cloud/storage/client.py @@ -52,7 +52,7 @@ class Client(ClientWithProject): This parameter should be considered private, and could change in the future. - :type client_info: :class:`~google.api_core.client_info.ClientInfo: + :type client_info: :class:`~google.api_core.client_info.ClientInfo` :param client_info: The client info used to send a user-agent string along with API requests. If ``None``, then default info will be used. Generally, From f83e81b1c52e6f9ea64405474ae887210521dd98 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 7 May 2019 12:34:17 -0400 Subject: [PATCH 3/3] Lint. --- storage/tests/unit/test__http.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/storage/tests/unit/test__http.py b/storage/tests/unit/test__http.py index 85eb87942860..01b200bae9b2 100644 --- a/storage/tests/unit/test__http.py +++ b/storage/tests/unit/test__http.py @@ -29,9 +29,7 @@ def _make_one(self, *args, **kw): def test_extra_headers(self): import requests - from google.cloud import _http as base_http - from google.cloud.storage import _http as MUT http = mock.create_autospec(requests.Session, instance=True) response = requests.Response()