From 73e497e5a024e03af060643a333ed9f8bf38eef0 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 2 May 2019 14:20:27 -0400 Subject: [PATCH 01/14] Add 'user_agent' and 'extra_headers' properties to 'Connection'. Deprecate the 'USER_AGENT' and '_EXTRA_HEADERS' class-level attributes. --- core/google/cloud/_http.py | 83 ++++++++++++++++++++++++--- core/tests/unit/test__http.py | 103 ++++++++++++++++++++++++++++------ 2 files changed, 160 insertions(+), 26 deletions(-) diff --git a/core/google/cloud/_http.py b/core/google/cloud/_http.py index d7441e502995..659b1d33f402 100644 --- a/core/google/cloud/_http.py +++ b/core/google/cloud/_http.py @@ -16,6 +16,7 @@ import json import platform +import warnings from pkg_resources import get_distribution from six.moves.urllib.parse import urlencode @@ -34,6 +35,16 @@ CLIENT_INFO_HEADER = "X-Goog-API-Client" CLIENT_INFO_TEMPLATE = "gl-python/" + platform.python_version() + " gccl/{}" +_USER_AGENT_ALL_CAPS_DEPRECATED = """\ +The 'USER_AGENT' class-level attribute is deprecated. Please use +'user_agent' instead. +""" + +_EXTRA_HEADERS_ALL_CAPS_DEPRECATED = """\ +The '_EXTRA_HEADERS' class-level attribute is deprecated. Please use +'extra_headers' instead. +""" + class Connection(object): """A generic connection to Google Cloud Platform. @@ -42,16 +53,72 @@ class Connection(object): :param client: The client that owns the current connection. """ - USER_AGENT = DEFAULT_USER_AGENT - _EXTRA_HEADERS = {} - """Headers to be sent with every request. - - Intended to be over-ridden by subclasses. - """ + _user_agent = DEFAULT_USER_AGENT + _extra_headers = None def __init__(self, client): self._client = client + @property + def USER_AGENT(self): + """Deprecated: get / set user agent sent by connection. + + :rtype: str + :returns: user agent + """ + warnings.warn( + _USER_AGENT_ALL_CAPS_DEPRECATED, DeprecationWarning, stacklevel=2) + return self.user_agent + + @USER_AGENT.setter + def USER_AGENT(self, value): + warnings.warn( + _USER_AGENT_ALL_CAPS_DEPRECATED, DeprecationWarning, stacklevel=2) + self.user_agent = value + + @property + def user_agent(self): + """Get / set user agent sent by connection. + + :rtype: str + :returns: user agent + """ + return self._user_agent + + @user_agent.setter + def user_agent(self, value): + self._user_agent = value + + @property + def _EXTRA_HEADERS(self): + """Deprecated: get / set extra headers sent by connection. + + :rtype: dict + :returns: header keys / values + """ + warnings.warn( + _EXTRA_HEADERS_ALL_CAPS_DEPRECATED, DeprecationWarning, stacklevel=2) + return self.extra_headers + + @_EXTRA_HEADERS.setter + def _EXTRA_HEADERS(self, value): + warnings.warn( + _EXTRA_HEADERS_ALL_CAPS_DEPRECATED, DeprecationWarning, stacklevel=2) + self.extra_headers = value + + @property + def extra_headers(self): + """Get / set extra headers sent by connection. + + :rtype: dict + :returns: header keys / values + """ + return self._extra_headers or {} + + @extra_headers.setter + def extra_headers(self, value): + self._extra_headers = value + @property def credentials(self): """Getter for current credentials. @@ -181,13 +248,13 @@ def _make_request( :returns: The HTTP response. """ headers = headers or {} - headers.update(self._EXTRA_HEADERS) + headers.update(self.extra_headers) headers["Accept-Encoding"] = "gzip" if content_type: headers["Content-Type"] = content_type - headers["User-Agent"] = self.USER_AGENT + headers["User-Agent"] = self.user_agent return self._do_request(method, url, headers, data, target_object) diff --git a/core/tests/unit/test__http.py b/core/tests/unit/test__http.py index 7c8aec215f22..ffc1d2650714 100644 --- a/core/tests/unit/test__http.py +++ b/core/tests/unit/test__http.py @@ -14,6 +14,7 @@ import json import unittest +import warnings import mock import requests @@ -35,6 +36,81 @@ def test_constructor(self): conn = self._make_one(client) self.assertIs(conn._client, client) + def test_user_agent_all_caps_getter_deprecated(self): + client = object() + conn = self._make_one(client) + conn._user_agent = user_agent = 'testing' + + with mock.patch.object(warnings, "warn", autospec=True) as warn: + self.assertEqual(conn.USER_AGENT, user_agent) + + warn.assert_called_once_with(mock.ANY, DeprecationWarning, stacklevel=2) + + def test_user_agent_all_caps_setter_deprecated(self): + conn = self._make_one(object()) + user_agent = 'testing' + + with mock.patch.object(warnings, "warn", autospec=True) as warn: + conn.USER_AGENT = user_agent + + self.assertEqual(conn._user_agent, user_agent) + warn.assert_called_once_with(mock.ANY, DeprecationWarning, stacklevel=2) + + def test_user_agent_getter_default(self): + from pkg_resources import get_distribution + + expected = "gcloud-python/{0}".format( + get_distribution("google-cloud-core").version + ) + conn = self._make_one(object()) + self.assertEqual(conn.user_agent, expected) + + def test_user_agent_getter_overridden(self): + conn = self._make_one(object()) + expected = conn._user_agent = "testing" + self.assertEqual(conn.user_agent, expected) + + def test_user_agent_setter(self): + conn = self._make_one(object()) + expected = "testing" + conn.user_agent = expected + self.assertEqual(conn._user_agent, expected) + + def test_extra_headers_all_caps_getter_deprecated(self): + client = object() + conn = self._make_one(client) + expected = conn._extra_headers = {"foo": "bar"} + + with mock.patch.object(warnings, "warn", autospec=True) as warn: + self.assertEqual(conn._EXTRA_HEADERS, expected) + + warn.assert_called_once_with(mock.ANY, DeprecationWarning, stacklevel=2) + + def test_extra_headers_all_caps_setter_deprecated(self): + conn = self._make_one(object()) + extra_headers = {"foo": "bar"} + + with mock.patch.object(warnings, "warn", autospec=True) as warn: + conn._EXTRA_HEADERS = extra_headers + + self.assertEqual(conn._extra_headers, extra_headers) + warn.assert_called_once_with(mock.ANY, DeprecationWarning, stacklevel=2) + + def test_extra_headers_getter_default(self): + conn = self._make_one(object()) + self.assertEqual(conn.extra_headers, {}) + + def test_extra_headers_getter_overridden(self): + conn = self._make_one(object()) + expected = conn._extra_headers = {"foo": "bar"} + self.assertEqual(conn.extra_headers, expected) + + def test_extra_headers_setter(self): + conn = self._make_one(object()) + expected = {"foo": "bar"} + conn.extra_headers = expected + self.assertEqual(conn._extra_headers, expected) + def test_credentials_property(self): client = mock.Mock(spec=["_credentials"]) conn = self._make_one(client) @@ -45,15 +121,6 @@ def test_http_property(self): conn = self._make_one(client) self.assertIs(conn.http, client._http) - def test_user_agent_format(self): - from pkg_resources import get_distribution - - expected_ua = "gcloud-python/{0}".format( - get_distribution("google-cloud-core").version - ) - conn = self._make_one(object()) - self.assertEqual(conn.USER_AGENT, expected_ua) - def make_response(status=http_client.OK, content=b"", headers={}): response = requests.Response() @@ -137,7 +204,7 @@ def test__make_request_no_data_no_content_type_no_headers(self): self.assertEqual(response.status_code, http_client.OK) self.assertEqual(response.content, b"") - expected_headers = {"Accept-Encoding": "gzip", "User-Agent": conn.USER_AGENT} + expected_headers = {"Accept-Encoding": "gzip", "User-Agent": conn.user_agent} http.request.assert_called_once_with( method="GET", url=url, headers=expected_headers, data=None ) @@ -154,7 +221,7 @@ def test__make_request_w_data_no_extra_headers(self): expected_headers = { "Accept-Encoding": "gzip", "Content-Type": "application/json", - "User-Agent": conn.USER_AGENT, + "User-Agent": conn.user_agent, } http.request.assert_called_once_with( method="GET", url=url, headers=expected_headers, data=data @@ -171,7 +238,7 @@ def test__make_request_w_extra_headers(self): expected_headers = { "Accept-Encoding": "gzip", "X-Foo": "foo", - "User-Agent": conn.USER_AGENT, + "User-Agent": conn.user_agent, } http.request.assert_called_once_with( method="GET", url=url, headers=expected_headers, data=None @@ -187,7 +254,7 @@ def test_api_request_defaults(self): self.assertEqual(conn.api_request("GET", path), {}) - expected_headers = {"Accept-Encoding": "gzip", "User-Agent": conn.USER_AGENT} + expected_headers = {"Accept-Encoding": "gzip", "User-Agent": conn.user_agent} expected_url = "{base}/mock/{version}{path}".format( base=conn.API_BASE_URL, version=conn.API_VERSION, path=path ) @@ -224,7 +291,7 @@ def test_api_request_w_query_params(self): self.assertEqual(result, {}) - expected_headers = {"Accept-Encoding": "gzip", "User-Agent": conn.USER_AGENT} + expected_headers = {"Accept-Encoding": "gzip", "User-Agent": conn.user_agent} http.request.assert_called_once_with( method="GET", url=mock.ANY, headers=expected_headers, data=None ) @@ -249,7 +316,7 @@ def test_api_request_w_headers(self): expected_headers = { "Accept-Encoding": "gzip", - "User-Agent": conn.USER_AGENT, + "User-Agent": conn.user_agent, "X-Foo": "bar", } http.request.assert_called_once_with( @@ -260,7 +327,7 @@ def test_api_request_w_extra_headers(self): http = make_requests_session([self.EMPTY_JSON_RESPONSE]) client = mock.Mock(_http=http, spec=["_http"]) conn = self._make_mock_one(client) - conn._EXTRA_HEADERS = { + conn.extra_headers = { "X-Baz": "dax-quux", "X-Foo": "not-bar", # Collision with ``headers``. } @@ -271,7 +338,7 @@ def test_api_request_w_extra_headers(self): expected_headers = { "Accept-Encoding": "gzip", - "User-Agent": conn.USER_AGENT, + "User-Agent": conn.user_agent, "X-Foo": "not-bar", # The one passed-in is overridden. "X-Baz": "dax-quux", } @@ -292,7 +359,7 @@ def test_api_request_w_data(self): expected_headers = { "Accept-Encoding": "gzip", "Content-Type": "application/json", - "User-Agent": conn.USER_AGENT, + "User-Agent": conn.user_agent, } http.request.assert_called_once_with( From dffc79b7377552365dbb37591075d3b39fbe78c3 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 2 May 2019 14:30:18 -0400 Subject: [PATCH 02/14] Add 'client_info' parameter to 'Connection' ctor. --- core/google/cloud/_http.py | 11 ++++++++++- core/tests/unit/test__http.py | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/core/google/cloud/_http.py b/core/google/cloud/_http.py index 659b1d33f402..bcc78d3e0844 100644 --- a/core/google/cloud/_http.py +++ b/core/google/cloud/_http.py @@ -21,6 +21,7 @@ from pkg_resources import get_distribution from six.moves.urllib.parse import urlencode +from google.api_core.gapic_v1.client_info import ClientInfo from google.cloud import exceptions @@ -51,14 +52,22 @@ class Connection(object): :type client: :class:`~google.cloud.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. """ _user_agent = DEFAULT_USER_AGENT _extra_headers = None - def __init__(self, client): + def __init__(self, client, client_info=None): self._client = client + if client_info is None: + client_info = ClientInfo() + + self._client_info = client_info + @property def USER_AGENT(self): """Deprecated: get / set user agent sent by connection. diff --git a/core/tests/unit/test__http.py b/core/tests/unit/test__http.py index ffc1d2650714..80d3fec6e352 100644 --- a/core/tests/unit/test__http.py +++ b/core/tests/unit/test__http.py @@ -31,10 +31,19 @@ def _get_target_class(): def _make_one(self, *args, **kw): return self._get_target_class()(*args, **kw) - def test_constructor(self): + def test_constructor_defaults(self): + from google.api_core.gapic_v1.client_info import ClientInfo + client = object() conn = self._make_one(client) self.assertIs(conn._client, client) + self.assertIsInstance(conn._client_info, ClientInfo) + + def test_constructor_explicit(self): + client = object() + client_info = object() + conn = self._make_one(client, client_info=client_info) + self.assertIs(conn._client, client) def test_user_agent_all_caps_getter_deprecated(self): client = object() From 13aa6775299c121ebfcf1f68f0627ca1307658aa Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 2 May 2019 14:42:16 -0400 Subject: [PATCH 03/14] Implement 'Connection.user_agent' via its '_client_info'. --- core/google/cloud/_http.py | 4 ++-- core/tests/unit/test__http.py | 27 ++++++++------------------- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/core/google/cloud/_http.py b/core/google/cloud/_http.py index bcc78d3e0844..0e4aa2ab637c 100644 --- a/core/google/cloud/_http.py +++ b/core/google/cloud/_http.py @@ -92,11 +92,11 @@ def user_agent(self): :rtype: str :returns: user agent """ - return self._user_agent + return self._client_info.to_user_agent() @user_agent.setter def user_agent(self, value): - self._user_agent = value + self._client_info.user_agent = value @property def _EXTRA_HEADERS(self): diff --git a/core/tests/unit/test__http.py b/core/tests/unit/test__http.py index 80d3fec6e352..91e6814497e6 100644 --- a/core/tests/unit/test__http.py +++ b/core/tests/unit/test__http.py @@ -48,42 +48,31 @@ def test_constructor_explicit(self): def test_user_agent_all_caps_getter_deprecated(self): client = object() conn = self._make_one(client) - conn._user_agent = user_agent = 'testing' with mock.patch.object(warnings, "warn", autospec=True) as warn: - self.assertEqual(conn.USER_AGENT, user_agent) + self.assertEqual(conn.USER_AGENT, conn._client_info.to_user_agent()) warn.assert_called_once_with(mock.ANY, DeprecationWarning, stacklevel=2) def test_user_agent_all_caps_setter_deprecated(self): conn = self._make_one(object()) - user_agent = 'testing' + user_agent = "testing" with mock.patch.object(warnings, "warn", autospec=True) as warn: conn.USER_AGENT = user_agent - self.assertEqual(conn._user_agent, user_agent) + self.assertEqual(conn._client_info.user_agent, user_agent) warn.assert_called_once_with(mock.ANY, DeprecationWarning, stacklevel=2) - def test_user_agent_getter_default(self): - from pkg_resources import get_distribution - - expected = "gcloud-python/{0}".format( - get_distribution("google-cloud-core").version - ) - conn = self._make_one(object()) - self.assertEqual(conn.user_agent, expected) - - def test_user_agent_getter_overridden(self): + def test_user_agent_getter(self): conn = self._make_one(object()) - expected = conn._user_agent = "testing" - self.assertEqual(conn.user_agent, expected) + self.assertEqual(conn.user_agent, conn._client_info.to_user_agent()) def test_user_agent_setter(self): conn = self._make_one(object()) - expected = "testing" - conn.user_agent = expected - self.assertEqual(conn._user_agent, expected) + user_agent = "testing" + conn.user_agent = user_agent + self.assertEqual(conn._client_info.user_agent, user_agent) def test_extra_headers_all_caps_getter_deprecated(self): client = object() From cfed70bdaa4b5a5cfd1811228a5eda5c87fa5013 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 2 May 2019 14:57:21 -0400 Subject: [PATCH 04/14] Ensure 'X-Goog-API-Client' header is always passed. --- core/google/cloud/_http.py | 4 ++- core/tests/unit/test__http.py | 59 +++++++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/core/google/cloud/_http.py b/core/google/cloud/_http.py index 0e4aa2ab637c..5ffbf02881fe 100644 --- a/core/google/cloud/_http.py +++ b/core/google/cloud/_http.py @@ -122,7 +122,9 @@ def extra_headers(self): :rtype: dict :returns: header keys / values """ - return self._extra_headers or {} + result = self._extra_headers.copy() if self._extra_headers else {} + result[CLIENT_INFO_HEADER] = self.user_agent + return result @extra_headers.setter def extra_headers(self, value): diff --git a/core/tests/unit/test__http.py b/core/tests/unit/test__http.py index 91e6814497e6..236d5f964dc9 100644 --- a/core/tests/unit/test__http.py +++ b/core/tests/unit/test__http.py @@ -75,9 +75,15 @@ def test_user_agent_setter(self): self.assertEqual(conn._client_info.user_agent, user_agent) def test_extra_headers_all_caps_getter_deprecated(self): + from google.cloud._http import CLIENT_INFO_HEADER + client = object() conn = self._make_one(client) - expected = conn._extra_headers = {"foo": "bar"} + conn._extra_headers = {"foo": "bar"} + expected = { + CLIENT_INFO_HEADER: conn._client_info.to_user_agent(), + "foo": "bar", + } with mock.patch.object(warnings, "warn", autospec=True) as warn: self.assertEqual(conn._EXTRA_HEADERS, expected) @@ -95,12 +101,21 @@ def test_extra_headers_all_caps_setter_deprecated(self): warn.assert_called_once_with(mock.ANY, DeprecationWarning, stacklevel=2) def test_extra_headers_getter_default(self): + from google.cloud._http import CLIENT_INFO_HEADER + conn = self._make_one(object()) - self.assertEqual(conn.extra_headers, {}) + expected = {CLIENT_INFO_HEADER: conn._client_info.to_user_agent()} + self.assertEqual(conn.extra_headers, expected) def test_extra_headers_getter_overridden(self): + from google.cloud._http import CLIENT_INFO_HEADER + conn = self._make_one(object()) - expected = conn._extra_headers = {"foo": "bar"} + conn._extra_headers = {"foo": "bar"} + expected = { + CLIENT_INFO_HEADER: conn._client_info.to_user_agent(), + "foo": "bar", + } self.assertEqual(conn.extra_headers, expected) def test_extra_headers_setter(self): @@ -192,6 +207,8 @@ def test_build_api_url_w_extra_query_params(self): self.assertEqual(parms["qux"], ["quux", "corge"]) def test__make_request_no_data_no_content_type_no_headers(self): + from google.cloud._http import CLIENT_INFO_HEADER + http = make_requests_session([make_response()]) client = mock.Mock(_http=http, spec=["_http"]) conn = self._make_one(client) @@ -202,12 +219,18 @@ def test__make_request_no_data_no_content_type_no_headers(self): self.assertEqual(response.status_code, http_client.OK) self.assertEqual(response.content, b"") - expected_headers = {"Accept-Encoding": "gzip", "User-Agent": conn.user_agent} + expected_headers = { + "Accept-Encoding": "gzip", + "User-Agent": conn.user_agent, + CLIENT_INFO_HEADER: conn.user_agent, + } http.request.assert_called_once_with( method="GET", url=url, headers=expected_headers, data=None ) def test__make_request_w_data_no_extra_headers(self): + from google.cloud._http import CLIENT_INFO_HEADER + http = make_requests_session([make_response()]) client = mock.Mock(_http=http, spec=["_http"]) conn = self._make_one(client) @@ -220,12 +243,15 @@ def test__make_request_w_data_no_extra_headers(self): "Accept-Encoding": "gzip", "Content-Type": "application/json", "User-Agent": conn.user_agent, + CLIENT_INFO_HEADER: conn.user_agent, } http.request.assert_called_once_with( method="GET", url=url, headers=expected_headers, data=data ) def test__make_request_w_extra_headers(self): + from google.cloud._http import CLIENT_INFO_HEADER + http = make_requests_session([make_response()]) client = mock.Mock(_http=http, spec=["_http"]) conn = self._make_one(client) @@ -237,12 +263,15 @@ def test__make_request_w_extra_headers(self): "Accept-Encoding": "gzip", "X-Foo": "foo", "User-Agent": conn.user_agent, + CLIENT_INFO_HEADER: conn.user_agent, } http.request.assert_called_once_with( method="GET", url=url, headers=expected_headers, data=None ) def test_api_request_defaults(self): + from google.cloud._http import CLIENT_INFO_HEADER + http = make_requests_session( [make_response(content=b"{}", headers=self.JSON_HEADERS)] ) @@ -252,7 +281,11 @@ def test_api_request_defaults(self): self.assertEqual(conn.api_request("GET", path), {}) - expected_headers = {"Accept-Encoding": "gzip", "User-Agent": conn.user_agent} + expected_headers = { + "Accept-Encoding": "gzip", + "User-Agent": conn.user_agent, + CLIENT_INFO_HEADER: conn.user_agent, + } expected_url = "{base}/mock/{version}{path}".format( base=conn.API_BASE_URL, version=conn.API_VERSION, path=path ) @@ -280,6 +313,7 @@ def test_api_request_wo_json_expected(self): def test_api_request_w_query_params(self): from six.moves.urllib.parse import parse_qs from six.moves.urllib.parse import urlsplit + from google.cloud._http import CLIENT_INFO_HEADER http = make_requests_session([self.EMPTY_JSON_RESPONSE]) client = mock.Mock(_http=http, spec=["_http"]) @@ -289,7 +323,11 @@ def test_api_request_w_query_params(self): self.assertEqual(result, {}) - expected_headers = {"Accept-Encoding": "gzip", "User-Agent": conn.user_agent} + expected_headers = { + "Accept-Encoding": "gzip", + "User-Agent": conn.user_agent, + CLIENT_INFO_HEADER: conn.user_agent, + } http.request.assert_called_once_with( method="GET", url=mock.ANY, headers=expected_headers, data=None ) @@ -305,6 +343,8 @@ def test_api_request_w_query_params(self): self.assertEqual(parms["baz"], ["qux", "quux"]) def test_api_request_w_headers(self): + from google.cloud._http import CLIENT_INFO_HEADER + http = make_requests_session([self.EMPTY_JSON_RESPONSE]) client = mock.Mock(_http=http, spec=["_http"]) conn = self._make_mock_one(client) @@ -316,12 +356,15 @@ def test_api_request_w_headers(self): "Accept-Encoding": "gzip", "User-Agent": conn.user_agent, "X-Foo": "bar", + CLIENT_INFO_HEADER: conn.user_agent, } http.request.assert_called_once_with( method="GET", url=mock.ANY, headers=expected_headers, data=None ) def test_api_request_w_extra_headers(self): + from google.cloud._http import CLIENT_INFO_HEADER + http = make_requests_session([self.EMPTY_JSON_RESPONSE]) client = mock.Mock(_http=http, spec=["_http"]) conn = self._make_mock_one(client) @@ -339,12 +382,15 @@ def test_api_request_w_extra_headers(self): "User-Agent": conn.user_agent, "X-Foo": "not-bar", # The one passed-in is overridden. "X-Baz": "dax-quux", + CLIENT_INFO_HEADER: conn.user_agent, } http.request.assert_called_once_with( method="GET", url=mock.ANY, headers=expected_headers, data=None ) def test_api_request_w_data(self): + from google.cloud._http import CLIENT_INFO_HEADER + http = make_requests_session([self.EMPTY_JSON_RESPONSE]) client = mock.Mock(_http=http, spec=["_http"]) conn = self._make_mock_one(client) @@ -358,6 +404,7 @@ def test_api_request_w_data(self): "Accept-Encoding": "gzip", "Content-Type": "application/json", "User-Agent": conn.user_agent, + CLIENT_INFO_HEADER: conn.user_agent, } http.request.assert_called_once_with( From 78035033539d8d2e7258841586cd37d97dac02df Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 2 May 2019 15:13:01 -0400 Subject: [PATCH 05/14] Avoid relying on systest envvars in a unit test. --- bigquery/tests/unit/test_client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bigquery/tests/unit/test_client.py b/bigquery/tests/unit/test_client.py index 08c36e0ac277..6f001889164d 100644 --- a/bigquery/tests/unit/test_client.py +++ b/bigquery/tests/unit/test_client.py @@ -4518,7 +4518,9 @@ def test_list_rows_with_missing_schema(self): self.assertIsNone(rows[2].age, msg=repr(table)) def test_list_rows_error(self): - client = self._make_one() + creds = _make_credentials() + http = object() + client = self._make_one(project=self.PROJECT, credentials=creds, _http=http) # neither Table nor tableReference with self.assertRaises(TypeError): From ce60f3ab92dcf75f7d55ce0116f36501c90a0dcb Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 2 May 2019 15:13:43 -0400 Subject: [PATCH 06/14] Rely on base connection class for user_agent, extra_headers support. --- bigquery/google/cloud/bigquery/_http.py | 32 ++----------------- bigquery/tests/unit/test__http.py | 42 +++++-------------------- 2 files changed, 10 insertions(+), 64 deletions(-) diff --git a/bigquery/google/cloud/bigquery/_http.py b/bigquery/google/cloud/bigquery/_http.py index 0e5475f5f54b..78fa4a6a3ea6 100644 --- a/bigquery/google/cloud/bigquery/_http.py +++ b/bigquery/google/cloud/bigquery/_http.py @@ -28,17 +28,10 @@ class Connection(_http.JSONConnection): """ def __init__(self, client, client_info=None): - super(Connection, self).__init__(client) + super(Connection, self).__init__(client, client_info) - if client_info is None: - client_info = google.api_core.gapic_v1.client_info.ClientInfo( - gapic_version=__version__, client_library_version=__version__ - ) - else: - client_info.gapic_version = __version__ - client_info.client_library_version = __version__ - self._client_info = client_info - self._extra_headers = {} + self._client_info.gapic_version = __version__ + self._client_info.client_library_version = __version__ API_BASE_URL = "https://www.googleapis.com" """The base of the API call URL.""" @@ -48,22 +41,3 @@ def __init__(self, client, client_info=None): API_URL_TEMPLATE = "{api_base_url}/bigquery/{api_version}{path}" """A template for the URL of a particular API call.""" - - @property - def USER_AGENT(self): - return self._client_info.to_user_agent() - - @USER_AGENT.setter - def USER_AGENT(self, value): - self._client_info.user_agent = value - - @property - def _EXTRA_HEADERS(self): - self._extra_headers[ - _http.CLIENT_INFO_HEADER - ] = self._client_info.to_user_agent() - return self._extra_headers - - @_EXTRA_HEADERS.setter - def _EXTRA_HEADERS(self, value): - self._extra_headers = value diff --git a/bigquery/tests/unit/test__http.py b/bigquery/tests/unit/test__http.py index d7d25ea445a0..939b5668e1e2 100644 --- a/bigquery/tests/unit/test__http.py +++ b/bigquery/tests/unit/test__http.py @@ -57,49 +57,21 @@ def test_user_agent(self): client = mock.Mock(_http=http, spec=["_http"]) conn = self._make_one(client) - conn.USER_AGENT = "my-application/1.2.3" + conn.user_agent = "my-application/1.2.3" req_data = "req-data-boring" result = conn.api_request("GET", "/rainbow", data=req_data, expect_json=False) self.assertEqual(result, data) expected_headers = { "Accept-Encoding": "gzip", - 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( - data=req_data, headers=expected_headers, method="GET", url=expected_uri - ) - self.assertIn("my-application/1.2.3", conn.USER_AGENT) - - def test_extra_headers(self): - from google.cloud import _http as base_http - - http = mock.create_autospec(requests.Session, instance=True) - response = requests.Response() - response.status_code = 200 - data = b"brent-spiner" - response._content = data - http.request.return_value = response - client = mock.Mock(_http=http, spec=["_http"]) - - conn = self._make_one(client) - conn._EXTRA_HEADERS["x-test-header"] = "a test value" - req_data = "req-data-boring" - result = conn.api_request("GET", "/rainbow", data=req_data, expect_json=False) - self.assertEqual(result, data) - - expected_headers = { - "Accept-Encoding": "gzip", - base_http.CLIENT_INFO_HEADER: conn.USER_AGENT, - "User-Agent": conn.USER_AGENT, - "x-test-header": "a test value", + 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( data=req_data, headers=expected_headers, method="GET", url=expected_uri ) + self.assertIn("my-application/1.2.3", conn.user_agent) def test_extra_headers_replace(self): from google.cloud import _http as base_http @@ -113,15 +85,15 @@ def test_extra_headers_replace(self): client = mock.Mock(_http=http, spec=["_http"]) conn = self._make_one(client) - conn._EXTRA_HEADERS = {"x-test-header": "a test value"} + conn.extra_headers = {"x-test-header": "a test value"} req_data = "req-data-boring" result = conn.api_request("GET", "/rainbow", data=req_data, expect_json=False) self.assertEqual(result, data) expected_headers = { "Accept-Encoding": "gzip", - base_http.CLIENT_INFO_HEADER: conn.USER_AGENT, - "User-Agent": conn.USER_AGENT, + base_http.CLIENT_INFO_HEADER: conn.user_agent, + "User-Agent": conn.user_agent, "x-test-header": "a test value", } expected_uri = conn.build_api_url("/rainbow") From 551a76b505a8be6586a081966a44b4ff0bac3ff5 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 2 May 2019 19:44:47 -0400 Subject: [PATCH 07/14] Lint. --- bigquery/google/cloud/bigquery/_http.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bigquery/google/cloud/bigquery/_http.py b/bigquery/google/cloud/bigquery/_http.py index 78fa4a6a3ea6..e1c8e9fd9c86 100644 --- a/bigquery/google/cloud/bigquery/_http.py +++ b/bigquery/google/cloud/bigquery/_http.py @@ -14,7 +14,6 @@ """Create / interact with Google BigQuery connections.""" -import google.api_core.gapic_v1.client_info from google.cloud import _http from google.cloud.bigquery import __version__ From e6dc855ec64cf8d2dcca2f163c3cb562d11f64c4 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 3 May 2019 12:28:28 -0400 Subject: [PATCH 08/14] Move 'X-Goog-API-Client' header generation out of 'extra_headers'. --- core/google/cloud/_http.py | 7 +++---- core/tests/unit/test__http.py | 20 +++----------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/core/google/cloud/_http.py b/core/google/cloud/_http.py index 5ffbf02881fe..d766900256ae 100644 --- a/core/google/cloud/_http.py +++ b/core/google/cloud/_http.py @@ -58,7 +58,6 @@ class Connection(object): """ _user_agent = DEFAULT_USER_AGENT - _extra_headers = None def __init__(self, client, client_info=None): self._client = client @@ -67,6 +66,7 @@ def __init__(self, client, client_info=None): client_info = ClientInfo() self._client_info = client_info + self._extra_headers = {} @property def USER_AGENT(self): @@ -122,9 +122,7 @@ def extra_headers(self): :rtype: dict :returns: header keys / values """ - result = self._extra_headers.copy() if self._extra_headers else {} - result[CLIENT_INFO_HEADER] = self.user_agent - return result + return self._extra_headers @extra_headers.setter def extra_headers(self, value): @@ -265,6 +263,7 @@ def _make_request( if content_type: headers["Content-Type"] = content_type + headers[CLIENT_INFO_HEADER] = self.user_agent headers["User-Agent"] = self.user_agent return self._do_request(method, url, headers, data, target_object) diff --git a/core/tests/unit/test__http.py b/core/tests/unit/test__http.py index 236d5f964dc9..c00fabcf0e46 100644 --- a/core/tests/unit/test__http.py +++ b/core/tests/unit/test__http.py @@ -75,15 +75,9 @@ def test_user_agent_setter(self): self.assertEqual(conn._client_info.user_agent, user_agent) def test_extra_headers_all_caps_getter_deprecated(self): - from google.cloud._http import CLIENT_INFO_HEADER - client = object() conn = self._make_one(client) - conn._extra_headers = {"foo": "bar"} - expected = { - CLIENT_INFO_HEADER: conn._client_info.to_user_agent(), - "foo": "bar", - } + expected = conn._extra_headers = {"foo": "bar"} with mock.patch.object(warnings, "warn", autospec=True) as warn: self.assertEqual(conn._EXTRA_HEADERS, expected) @@ -101,21 +95,13 @@ def test_extra_headers_all_caps_setter_deprecated(self): warn.assert_called_once_with(mock.ANY, DeprecationWarning, stacklevel=2) def test_extra_headers_getter_default(self): - from google.cloud._http import CLIENT_INFO_HEADER - conn = self._make_one(object()) - expected = {CLIENT_INFO_HEADER: conn._client_info.to_user_agent()} + expected = {} self.assertEqual(conn.extra_headers, expected) def test_extra_headers_getter_overridden(self): - from google.cloud._http import CLIENT_INFO_HEADER - conn = self._make_one(object()) - conn._extra_headers = {"foo": "bar"} - expected = { - CLIENT_INFO_HEADER: conn._client_info.to_user_agent(), - "foo": "bar", - } + expected = conn._extra_headers = {"foo": "bar"} self.assertEqual(conn.extra_headers, expected) def test_extra_headers_setter(self): From 0276af155ae73c3e435d748eac5bed2e324e6a42 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 3 May 2019 12:34:26 -0400 Subject: [PATCH 09/14] Add unit test showing that item assingment to 'extra_headers' property works. --- core/tests/unit/test__http.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/tests/unit/test__http.py b/core/tests/unit/test__http.py index c00fabcf0e46..da038fe524ba 100644 --- a/core/tests/unit/test__http.py +++ b/core/tests/unit/test__http.py @@ -104,6 +104,12 @@ def test_extra_headers_getter_overridden(self): expected = conn._extra_headers = {"foo": "bar"} self.assertEqual(conn.extra_headers, expected) + def test_extra_headers_item_assignment(self): + conn = self._make_one(object()) + expected = {"foo": "bar"} + conn.extra_headers["foo"] = "bar" + self.assertEqual(conn._extra_headers, expected) + def test_extra_headers_setter(self): conn = self._make_one(object()) expected = {"foo": "bar"} From dfa12fe5458947fa0f60896cecedaf38847101f1 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 3 May 2019 12:51:58 -0400 Subject: [PATCH 10/14] Document the 'client_info' parameter to 'bigquery._http.Connection'. --- bigquery/google/cloud/bigquery/_http.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bigquery/google/cloud/bigquery/_http.py b/bigquery/google/cloud/bigquery/_http.py index e1c8e9fd9c86..643b24920bee 100644 --- a/bigquery/google/cloud/bigquery/_http.py +++ b/bigquery/google/cloud/bigquery/_http.py @@ -24,6 +24,9 @@ class Connection(_http.JSONConnection): :type client: :class:`~google.cloud.bigquery.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): From 85977418b1f7c350039706c9df311c169024fca9 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 3 May 2019 13:30:00 -0400 Subject: [PATCH 11/14] Create/use non-GAPIC-specific 'ClientInfo' class FBO HTTP/JSON clients. Derive the existing GAPIC class from it. --- api_core/google/api_core/client_info.py | 96 +++++++++++++++++++ .../google/api_core/gapic_v1/client_info.py | 54 +---------- api_core/tests/unit/gapic/test_client_info.py | 53 ---------- api_core/tests/unit/test_client_info.py | 69 +++++++++++++ core/google/cloud/_http.py | 2 +- core/tests/unit/test__http.py | 2 +- 6 files changed, 169 insertions(+), 107 deletions(-) create mode 100644 api_core/google/api_core/client_info.py create mode 100644 api_core/tests/unit/test_client_info.py diff --git a/api_core/google/api_core/client_info.py b/api_core/google/api_core/client_info.py new file mode 100644 index 000000000000..b196b7a987e4 --- /dev/null +++ b/api_core/google/api_core/client_info.py @@ -0,0 +1,96 @@ +# Copyright 2017 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helpers for providing client information. + +Client information is used to send information about the calling client, +such as the library and Python version, to API services. +""" + +import platform + +import pkg_resources + +_PY_VERSION = platform.python_version() +_API_CORE_VERSION = pkg_resources.get_distribution("google-api-core").version + +try: + _GRPC_VERSION = pkg_resources.get_distribution("grpcio").version +except pkg_resources.DistributionNotFound: # pragma: NO COVER + _GRPC_VERSION = None + + +class ClientInfo(object): + """Client information used to generate a user-agent for API calls. + + This user-agent information is sent along with API calls to allow the + receiving service to do analytics on which versions of Python and Google + libraries are being used. + + Args: + python_version (str): The Python interpreter version, for example, + ``'2.7.13'``. + grpc_version (Optional[str]): The gRPC library version. + api_core_version (str): The google-api-core library version. + gapic_version (Optional[str]): The sversion of gapic-generated client + library, if the library was generated by gapic. + client_library_version (Optional[str]): The version of the client + library, generally used if the client library was not generated + by gapic or if additional functionality was built on top of + a gapic client library. + user_agent (Optional[str]): Prefix to the user agent header. This is + used to supply information such as application name or partner tool. + Recommended format: ``application-or-tool-ID/major.minor.version``. + """ + + def __init__( + self, + python_version=_PY_VERSION, + grpc_version=_GRPC_VERSION, + api_core_version=_API_CORE_VERSION, + gapic_version=None, + client_library_version=None, + user_agent=None, + ): + self.python_version = python_version + self.grpc_version = grpc_version + self.api_core_version = api_core_version + self.gapic_version = gapic_version + self.client_library_version = client_library_version + self.user_agent = user_agent + + def to_user_agent(self): + """Returns the user-agent string for this client info.""" + + # Note: the order here is important as the internal metrics system + # expects these items to be in specific locations. + ua = "" + + if self.user_agent is not None: + ua += "{user_agent} " + + ua += "gl-python/{python_version} " + + if self.grpc_version is not None: + ua += "grpc/{grpc_version} " + + ua += "gax/{api_core_version} " + + if self.gapic_version is not None: + ua += "gapic/{gapic_version} " + + if self.client_library_version is not None: + ua += "gccl/{client_library_version} " + + return ua.format(**self.__dict__).strip() diff --git a/api_core/google/api_core/gapic_v1/client_info.py b/api_core/google/api_core/gapic_v1/client_info.py index 069e0194ab31..bdc2ce440de3 100644 --- a/api_core/google/api_core/gapic_v1/client_info.py +++ b/api_core/google/api_core/gapic_v1/client_info.py @@ -18,22 +18,13 @@ such as the library and Python version, to API services. """ -import platform +from google.api_core import client_info -import pkg_resources - -_PY_VERSION = platform.python_version() -_API_CORE_VERSION = pkg_resources.get_distribution("google-api-core").version - -try: - _GRPC_VERSION = pkg_resources.get_distribution("grpcio").version -except pkg_resources.DistributionNotFound: # pragma: NO COVER - _GRPC_VERSION = None METRICS_METADATA_KEY = "x-goog-api-client" -class ClientInfo(object): +class ClientInfo(client_info.ClientInfo): """Client information used to generate a user-agent for API calls. This user-agent information is sent along with API calls to allow the @@ -56,47 +47,6 @@ class ClientInfo(object): Recommended format: ``application-or-tool-ID/major.minor.version``. """ - def __init__( - self, - python_version=_PY_VERSION, - grpc_version=_GRPC_VERSION, - api_core_version=_API_CORE_VERSION, - gapic_version=None, - client_library_version=None, - user_agent=None, - ): - self.python_version = python_version - self.grpc_version = grpc_version - self.api_core_version = api_core_version - self.gapic_version = gapic_version - self.client_library_version = client_library_version - self.user_agent = user_agent - - def to_user_agent(self): - """Returns the user-agent string for this client info.""" - - # Note: the order here is important as the internal metrics system - # expects these items to be in specific locations. - ua = "" - - if self.user_agent is not None: - ua += "{user_agent} " - - ua += "gl-python/{python_version} " - - if self.grpc_version is not None: - ua += "grpc/{grpc_version} " - - ua += "gax/{api_core_version} " - - if self.gapic_version is not None: - ua += "gapic/{gapic_version} " - - if self.client_library_version is not None: - ua += "gccl/{client_library_version} " - - return ua.format(**self.__dict__).strip() - def to_grpc_metadata(self): """Returns the gRPC metadata for this client info.""" return (METRICS_METADATA_KEY, self.to_user_agent()) diff --git a/api_core/tests/unit/gapic/test_client_info.py b/api_core/tests/unit/gapic/test_client_info.py index 0cca47905988..64080ffdbccd 100644 --- a/api_core/tests/unit/gapic/test_client_info.py +++ b/api_core/tests/unit/gapic/test_client_info.py @@ -16,59 +16,6 @@ from google.api_core.gapic_v1 import client_info -def test_constructor_defaults(): - info = client_info.ClientInfo() - - assert info.python_version is not None - assert info.grpc_version is not None - assert info.api_core_version is not None - assert info.gapic_version is None - assert info.client_library_version is None - - -def test_constructor_options(): - info = client_info.ClientInfo( - python_version="1", - grpc_version="2", - api_core_version="3", - gapic_version="4", - client_library_version="5", - user_agent="6" - ) - - assert info.python_version == "1" - assert info.grpc_version == "2" - assert info.api_core_version == "3" - assert info.gapic_version == "4" - assert info.client_library_version == "5" - assert info.user_agent == "6" - - -def test_to_user_agent_minimal(): - info = client_info.ClientInfo( - python_version="1", api_core_version="2", grpc_version=None - ) - - user_agent = info.to_user_agent() - - assert user_agent == "gl-python/1 gax/2" - - -def test_to_user_agent_full(): - info = client_info.ClientInfo( - python_version="1", - grpc_version="2", - api_core_version="3", - gapic_version="4", - client_library_version="5", - user_agent="app-name/1.0", - ) - - user_agent = info.to_user_agent() - - assert user_agent == "app-name/1.0 gl-python/1 grpc/2 gax/3 gapic/4 gccl/5" - - def test_to_grpc_metadata(): info = client_info.ClientInfo() diff --git a/api_core/tests/unit/test_client_info.py b/api_core/tests/unit/test_client_info.py new file mode 100644 index 000000000000..0eb17c5feb1c --- /dev/null +++ b/api_core/tests/unit/test_client_info.py @@ -0,0 +1,69 @@ +# Copyright 2017 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from google.api_core import client_info + + +def test_constructor_defaults(): + info = client_info.ClientInfo() + + assert info.python_version is not None + assert info.grpc_version is not None + assert info.api_core_version is not None + assert info.gapic_version is None + assert info.client_library_version is None + + +def test_constructor_options(): + info = client_info.ClientInfo( + python_version="1", + grpc_version="2", + api_core_version="3", + gapic_version="4", + client_library_version="5", + user_agent="6" + ) + + assert info.python_version == "1" + assert info.grpc_version == "2" + assert info.api_core_version == "3" + assert info.gapic_version == "4" + assert info.client_library_version == "5" + assert info.user_agent == "6" + + +def test_to_user_agent_minimal(): + info = client_info.ClientInfo( + python_version="1", api_core_version="2", grpc_version=None + ) + + user_agent = info.to_user_agent() + + assert user_agent == "gl-python/1 gax/2" + + +def test_to_user_agent_full(): + info = client_info.ClientInfo( + python_version="1", + grpc_version="2", + api_core_version="3", + gapic_version="4", + client_library_version="5", + user_agent="app-name/1.0", + ) + + user_agent = info.to_user_agent() + + assert user_agent == "app-name/1.0 gl-python/1 grpc/2 gax/3 gapic/4 gccl/5" diff --git a/core/google/cloud/_http.py b/core/google/cloud/_http.py index d766900256ae..653e43138e3a 100644 --- a/core/google/cloud/_http.py +++ b/core/google/cloud/_http.py @@ -21,7 +21,7 @@ from pkg_resources import get_distribution from six.moves.urllib.parse import urlencode -from google.api_core.gapic_v1.client_info import ClientInfo +from google.api_core.client_info import ClientInfo from google.cloud import exceptions diff --git a/core/tests/unit/test__http.py b/core/tests/unit/test__http.py index da038fe524ba..d50494e8eadc 100644 --- a/core/tests/unit/test__http.py +++ b/core/tests/unit/test__http.py @@ -32,7 +32,7 @@ def _make_one(self, *args, **kw): return self._get_target_class()(*args, **kw) def test_constructor_defaults(self): - from google.api_core.gapic_v1.client_info import ClientInfo + from google.api_core.client_info import ClientInfo client = object() conn = self._make_one(client) From 08b316fd9be0baff573a7b7dcc0ed53be8fbf2ad Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 3 May 2019 13:39:12 -0400 Subject: [PATCH 12/14] Avoid use of deprecated 'USER_AGENT' attr of connection. --- bigquery/google/cloud/bigquery/client.py | 4 ++-- bigquery/tests/unit/test_client.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bigquery/google/cloud/bigquery/client.py b/bigquery/google/cloud/bigquery/client.py index db53dab9ef11..791c6e3a5de8 100644 --- a/bigquery/google/cloud/bigquery/client.py +++ b/bigquery/google/cloud/bigquery/client.py @@ -1363,7 +1363,7 @@ def _initiate_resumable_upload(self, stream, metadata, num_retries): """ chunk_size = _DEFAULT_CHUNKSIZE transport = self._http - headers = _get_upload_headers(self._connection.USER_AGENT) + headers = _get_upload_headers(self._connection.user_agent) upload_url = _RESUMABLE_URL_TEMPLATE.format(project=self.project) # TODO: modify ResumableUpload to take a retry.Retry object # that it can use for the initial RPC. @@ -1409,7 +1409,7 @@ def _do_multipart_upload(self, stream, metadata, size, num_retries): msg = _READ_LESS_THAN_SIZE.format(size, len(data)) raise ValueError(msg) - headers = _get_upload_headers(self._connection.USER_AGENT) + headers = _get_upload_headers(self._connection.user_agent) upload_url = _MULTIPART_URL_TEMPLATE.format(project=self.project) upload = MultipartUpload(upload_url, headers=headers) diff --git a/bigquery/tests/unit/test_client.py b/bigquery/tests/unit/test_client.py index 6f001889164d..13889f90d7e8 100644 --- a/bigquery/tests/unit/test_client.py +++ b/bigquery/tests/unit/test_client.py @@ -54,7 +54,7 @@ def _make_connection(*responses): from google.cloud.exceptions import NotFound mock_conn = mock.create_autospec(google.cloud.bigquery._http.Connection) - mock_conn.USER_AGENT = "testing 1.2.3" + mock_conn.user_agent = "testing 1.2.3" mock_conn.api_request.side_effect = list(responses) + [NotFound("miss")] return mock_conn @@ -2752,7 +2752,7 @@ def _initiate_resumable_upload_helper(self, num_retries=None): + "/jobs?uploadType=resumable" ) self.assertEqual(upload.upload_url, upload_url) - expected_headers = _get_upload_headers(conn.USER_AGENT) + expected_headers = _get_upload_headers(conn.user_agent) self.assertEqual(upload._headers, expected_headers) self.assertFalse(upload.finished) self.assertEqual(upload._chunk_size, _DEFAULT_CHUNKSIZE) @@ -2830,7 +2830,7 @@ def _do_multipart_upload_success_helper(self, get_boundary, num_retries=None): + b"\r\n" + b"--==0==--" ) - headers = _get_upload_headers(conn.USER_AGENT) + headers = _get_upload_headers(conn.user_agent) headers["content-type"] = b'multipart/related; boundary="==0=="' fake_transport.request.assert_called_once_with( "POST", upload_url, data=payload, headers=headers From 5a7da2e605afd8687a4a23645874f819cf11667a Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 3 May 2019 14:48:53 -0400 Subject: [PATCH 13/14] Fix class reference for client_info. --- bigquery/google/cloud/bigquery/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigquery/google/cloud/bigquery/client.py b/bigquery/google/cloud/bigquery/client.py index 791c6e3a5de8..f61c18f11bd4 100644 --- a/bigquery/google/cloud/bigquery/client.py +++ b/bigquery/google/cloud/bigquery/client.py @@ -128,7 +128,7 @@ class Client(ClientWithProject): default_query_job_config (google.cloud.bigquery.job.QueryJobConfig): (Optional) Default ``QueryJobConfig``. Will be merged into job configs passed into the ``query`` method. - client_info (google.api_core.gapic_v1.client_info.ClientInfo): + client_info (google.api_core.client_info.ClientInfo): 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 From 443a82262850d5e31dbd356d4270ae362b195542 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 7 May 2019 11:16:34 -0400 Subject: [PATCH 14/14] Document 'ClientInfo' classes. --- docs/core/client_info.rst | 11 +++++++++++ docs/core/index.rst | 7 ++++--- 2 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 docs/core/client_info.rst diff --git a/docs/core/client_info.rst b/docs/core/client_info.rst new file mode 100644 index 000000000000..e976b1863c7c --- /dev/null +++ b/docs/core/client_info.rst @@ -0,0 +1,11 @@ +Client Information Helpers +========================== + +.. automodule:: google.api_core.client_info + :members: + :show-inheritance: + +.. automodule:: google.api_core.gapic_v1.client_info + :members: + :show-inheritance: + diff --git a/docs/core/index.rst b/docs/core/index.rst index 5b44ddcb071d..45c68ad08ee2 100644 --- a/docs/core/index.rst +++ b/docs/core/index.rst @@ -5,15 +5,16 @@ Core config auth client + client_info exceptions helpers - retry - timeout - page_iterator iam operation operations_client + page_iterator path_template + retry + timeout Changelog ~~~~~~~~~