diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index 44c78f7..bc893c9 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,4 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:4e1991042fe54b991db9ca17c8fb386e61b22fe4d1472a568bf0fcac85dcf5d3 + digest: sha256:8a5d3f6a2e43ed8293f34e06a2f56931d1e88a2694c3bb11b15df4eb256ad163 +# created: 2022-04-06T10:30:21.687684602Z diff --git a/.github/auto-label.yaml b/.github/auto-label.yaml new file mode 100644 index 0000000..41bff0b --- /dev/null +++ b/.github/auto-label.yaml @@ -0,0 +1,15 @@ +# Copyright 2022 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. +requestsize: + enabled: true diff --git a/.github/release-please.yml b/.github/release-please.yml index 466597e..29601ad 100644 --- a/.github/release-please.yml +++ b/.github/release-please.yml @@ -1,2 +1,11 @@ releaseType: python handleGHRelease: true +# NOTE: this section is generated by synthtool.languages.python +# See https://github.com/googleapis/synthtool/blob/master/synthtool/languages/python.py +branches: +- branch: v1 + handleGHRelease: true + releaseType: python +- branch: v0 + handleGHRelease: true + releaseType: python diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 62eb5a7..46d2371 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/psf/black - rev: 19.10b0 + rev: 22.3.0 hooks: - id: black - repo: https://gitlab.com/pycqa/flake8 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d4c3cd..46358b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-cloud-core/#history +## [2.3.0](https://github.com/googleapis/python-cloud-core/compare/v2.2.3...v2.3.0) (2022-04-06) + + +### Features + +* allow extra_api_info to be passed to api request ([#183](https://github.com/googleapis/python-cloud-core/issues/183)) ([3f8e058](https://github.com/googleapis/python-cloud-core/commit/3f8e0581e631cde3163278fff326fd4831a75e75)) + ### [2.2.3](https://github.com/googleapis/python-cloud-core/compare/v2.2.2...v2.2.3) (2022-03-07) diff --git a/docs/conf.py b/docs/conf.py index 67ceda5..f105599 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -314,7 +314,13 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (root_doc, "google-cloud-core", "google-cloud-core Documentation", [author], 1,) + ( + root_doc, + "google-cloud-core", + "google-cloud-core Documentation", + [author], + 1, + ) ] # If true, show URL addresses after external links. @@ -355,7 +361,10 @@ intersphinx_mapping = { "python": ("https://python.readthedocs.org/en/latest/", None), "google-auth": ("https://googleapis.dev/python/google-auth/latest/", None), - "google.api_core": ("https://googleapis.dev/python/google-api-core/latest/", None,), + "google.api_core": ( + "https://googleapis.dev/python/google-api-core/latest/", + None, + ), "grpc": ("https://grpc.github.io/grpc/python/", None), "proto-plus": ("https://proto-plus-python.readthedocs.io/en/latest/", None), "protobuf": ("https://googleapis.dev/python/protobuf/latest/", None), diff --git a/google/cloud/_helpers/__init__.py b/google/cloud/_helpers/__init__.py index 291f587..3b9d04d 100644 --- a/google/cloud/_helpers/__init__.py +++ b/google/cloud/_helpers/__init__.py @@ -85,13 +85,11 @@ def __init__(self): self._stack = [] def __iter__(self): - """Iterate the stack in LIFO order. - """ + """Iterate the stack in LIFO order.""" return iter(reversed(self._stack)) def push(self, resource): - """Push a resource onto our stack. - """ + """Push a resource onto our stack.""" self._stack.append(resource) def pop(self): @@ -284,7 +282,7 @@ def _rfc3339_nanos_to_datetime(dt_str): micros = 0 else: scale = 9 - len(fraction) - nanos = int(fraction) * (10 ** scale) + nanos = int(fraction) * (10**scale) micros = nanos // 1000 return bare_seconds.replace(microsecond=micros, tzinfo=UTC) @@ -416,8 +414,8 @@ def _datetime_to_pb_timestamp(when): :returns: A timestamp protobuf corresponding to the object. """ ms_value = _microseconds_from_datetime(when) - seconds, micros = divmod(ms_value, 10 ** 6) - nanos = micros * 10 ** 3 + seconds, micros = divmod(ms_value, 10**6) + nanos = micros * 10**3 return timestamp_pb2.Timestamp(seconds=seconds, nanos=nanos) diff --git a/google/cloud/_http/__init__.py b/google/cloud/_http/__init__.py index 4c3ed96..af25e65 100644 --- a/google/cloud/_http/__init__.py +++ b/google/cloud/_http/__init__.py @@ -282,6 +282,7 @@ def _make_request( headers=None, target_object=None, timeout=_DEFAULT_TIMEOUT, + extra_api_info=None, ): """A low level method to send a request to the API. @@ -317,6 +318,10 @@ def _make_request( Can also be passed as a tuple (connect_timeout, read_timeout). See :meth:`requests.Session.request` documentation for details. + :type extra_api_info: string + :param extra_api_info: (optional) Extra api info to be appended to + the X-Goog-API-Client header + :rtype: :class:`requests.Response` :returns: The HTTP response. """ @@ -327,7 +332,10 @@ def _make_request( if content_type: headers["Content-Type"] = content_type - headers[CLIENT_INFO_HEADER] = self.user_agent + if extra_api_info: + headers[CLIENT_INFO_HEADER] = f"{self.user_agent} {extra_api_info}" + else: + headers[CLIENT_INFO_HEADER] = self.user_agent headers["User-Agent"] = self.user_agent return self._do_request( @@ -385,6 +393,7 @@ def api_request( expect_json=True, _target_object=None, timeout=_DEFAULT_TIMEOUT, + extra_api_info=None, ): """Make a request over the HTTP transport to the API. @@ -446,6 +455,10 @@ def api_request( Can also be passed as a tuple (connect_timeout, read_timeout). See :meth:`requests.Session.request` documentation for details. + :type extra_api_info: string + :param extra_api_info: (optional) Extra api info to be appended to + the X-Goog-API-Client header + :raises ~google.cloud.exceptions.GoogleCloudError: if the response code is not 200 OK. :raises ValueError: if the response content type is not JSON. @@ -474,6 +487,7 @@ def api_request( headers=headers, target_object=_target_object, timeout=timeout, + extra_api_info=extra_api_info, ) if not 200 <= response.status_code < 300: diff --git a/google/cloud/client/__init__.py b/google/cloud/client/__init__.py index d77e200..93e5d22 100644 --- a/google/cloud/client/__init__.py +++ b/google/cloud/client/__init__.py @@ -209,7 +209,8 @@ def _http(self): """ if self._http_internal is None: self._http_internal = google.auth.transport.requests.AuthorizedSession( - self._credentials, refresh_timeout=_CREDENTIALS_REFRESH_TIMEOUT, + self._credentials, + refresh_timeout=_CREDENTIALS_REFRESH_TIMEOUT, ) self._http_internal.configure_mtls_channel(self._client_cert_source) return self._http_internal @@ -254,7 +255,8 @@ def __init__(self, project=None, credentials=None): # https://github.com/googleapis/python-cloud-core/issues/27 if project is None: project = os.getenv( - environment_vars.PROJECT, os.getenv(environment_vars.LEGACY_PROJECT), + environment_vars.PROJECT, + os.getenv(environment_vars.LEGACY_PROJECT), ) # Project set on explicit credentials overrides discovery from diff --git a/google/cloud/version.py b/google/cloud/version.py index 75ec9d3..999199f 100644 --- a/google/cloud/version.py +++ b/google/cloud/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.2.3" +__version__ = "2.3.0" diff --git a/noxfile.py b/noxfile.py index 8fc781f..90baaf0 100644 --- a/noxfile.py +++ b/noxfile.py @@ -19,7 +19,7 @@ import nox -BLACK_VERSION = "black==19.10b0" +BLACK_VERSION = "black==22.3.0" BLACK_PATHS = ["docs", "google", "tests", "noxfile.py", "setup.py"] DEFAULT_PYTHON_VERSION = "3.7" @@ -43,7 +43,10 @@ def mypy(session): """Run type-checking.""" session.install(".", "mypy") session.install( - "types-setuptools", "types-requests", "types-mock", "types-protobuf", + "types-setuptools", + "types-requests", + "types-mock", + "types-protobuf", ) session.run("mypy", "google", "tests") @@ -143,7 +146,10 @@ def docfx(session): session.install("-e", ".") session.install( - "sphinx==4.0.1", "alabaster", "recommonmark", "gcp-sphinx-docfx-yaml", + "sphinx==4.0.1", + "alabaster", + "recommonmark", + "gcp-sphinx-docfx-yaml", ) shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) diff --git a/owlbot.py b/owlbot.py index b24dd44..49c6c49 100644 --- a/owlbot.py +++ b/owlbot.py @@ -16,6 +16,7 @@ import synthtool as s from synthtool import gcp +from synthtool.languages import python common = gcp.CommonTemplates() @@ -38,4 +39,6 @@ ], ) +python.configure_previous_major_version_branches() + s.shell.run(["nox", "-s", "blacken"], hide_output=False) diff --git a/tests/unit/test__helpers.py b/tests/unit/test__helpers.py index db0fa5b..edb5cf3 100644 --- a/tests/unit/test__helpers.py +++ b/tests/unit/test__helpers.py @@ -553,7 +553,9 @@ def test_success(self): out_message = self._call_fut(date_pb2.Date, in_message_any) self.assertEqual(in_message, out_message) - def test_failure(self,): + def test_failure( + self, + ): from google.protobuf import any_pb2 from google.type import date_pb2 from google.type import timeofday_pb2 @@ -636,7 +638,7 @@ def test_with_negative_microseconds(self): result = self._call_fut(timedelta_val) self.assertIsInstance(result, duration_pb2.Duration) self.assertEqual(result.seconds, seconds - 1) - self.assertEqual(result.nanos, 10 ** 9 + 1000 * microseconds) + self.assertEqual(result.nanos, 10**9 + 1000 * microseconds) def test_with_negative_seconds(self): import datetime @@ -648,7 +650,7 @@ def test_with_negative_seconds(self): result = self._call_fut(timedelta_val) self.assertIsInstance(result, duration_pb2.Duration) self.assertEqual(result.seconds, seconds + 1) - self.assertEqual(result.nanos, -(10 ** 9 - 1000 * microseconds)) + self.assertEqual(result.nanos, -(10**9 - 1000 * microseconds)) class Test__duration_pb_to_timedelta(unittest.TestCase): diff --git a/tests/unit/test__http.py b/tests/unit/test__http.py index ab03fac..faee193 100644 --- a/tests/unit/test__http.py +++ b/tests/unit/test__http.py @@ -575,6 +575,31 @@ def test_api_request_w_timeout(self): timeout=(2.2, 3.3), ) + def test_api_request_w_extra_api_info(self): + from google.cloud._http import CLIENT_INFO_HEADER + + session = make_requests_session([self.EMPTY_JSON_RESPONSE]) + client = mock.Mock(_http=session, spec=["_http"]) + conn = self._make_mock_one(client) + + EXTRA_API_INFO = "gccl-invocation-id/testing-id-123" + result = conn.api_request("GET", "/", extra_api_info=EXTRA_API_INFO) + + self.assertEqual(result, {}) + + expected_headers = { + "Accept-Encoding": "gzip", + "User-Agent": conn.user_agent, + CLIENT_INFO_HEADER: f"{conn.user_agent} {EXTRA_API_INFO}", + } + session.request.assert_called_once_with( + method="GET", + url=mock.ANY, + headers=expected_headers, + data=None, + timeout=self._get_default_timeout(), + ) + def test_api_request_w_404(self): from google.cloud import exceptions diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index febfcfe..fb35da4 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -234,7 +234,8 @@ def test_ctor_defaults_wo_envvar(self): environ = {} patch_env = mock.patch("os.environ", new=environ) patch_default = mock.patch( - "google.cloud.client._determine_default_project", return_value=None, + "google.cloud.client._determine_default_project", + return_value=None, ) with patch_env: with patch_default as patched: @@ -268,7 +269,8 @@ def test_ctor_defaults_w_legacy_envvar(self): def test_ctor_w_explicit_project(self): explicit_project = "explicit-project-456" patch_default = mock.patch( - "google.cloud.client._determine_default_project", return_value=None, + "google.cloud.client._determine_default_project", + return_value=None, ) with patch_default as patched: client = self._make_one(project=explicit_project) @@ -280,7 +282,8 @@ def test_ctor_w_explicit_project(self): def test_ctor_w_explicit_project_bytes(self): explicit_project = b"explicit-project-456" patch_default = mock.patch( - "google.cloud.client._determine_default_project", return_value=None, + "google.cloud.client._determine_default_project", + return_value=None, ) with patch_default as patched: client = self._make_one(project=explicit_project) @@ -292,7 +295,8 @@ def test_ctor_w_explicit_project_bytes(self): def test_ctor_w_explicit_project_invalid(self): explicit_project = object() patch_default = mock.patch( - "google.cloud.client._determine_default_project", return_value=None, + "google.cloud.client._determine_default_project", + return_value=None, ) with patch_default as patched: with self.assertRaises(ValueError): @@ -331,7 +335,8 @@ def test_ctor_w_explicit_credentials_w_project(self): project = "credentials-project-456" credentials = self._make_credentials(project_id=project) patch_default = mock.patch( - "google.cloud.client._determine_default_project", return_value=None, + "google.cloud.client._determine_default_project", + return_value=None, ) with patch_default as patched: client = self._make_one(credentials=credentials)