- On January 1, 2020 this library will no longer support Python 2 on the latest released version. - Previously released library versions will continue to be available. For more information please + As of January 1, 2020 this library no longer supports Python 2 on the latest released version. + Library versions released prior to that date will continue to be available. For more information please visit Python 2 support on Google Cloud.
{% block body %} {% endblock %} diff --git a/synth.metadata b/synth.metadata index b301f5f..77b4f46 100644 --- a/synth.metadata +++ b/synth.metadata @@ -4,14 +4,14 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/python-cloud-core.git", - "sha": "2f13d19babd66fea6185684a456573fdf8c1b784" + "sha": "5076ce7330e594fa0e1223e92eef61629508be71" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "cd522c3b4dde821766d95c80ae5aeb43d7a41170" + "sha": "303271797a360f8a439203413f13a160f2f5b3b4" } } ] From f727aba432d4726cce72da5f74e8be6adb945a80 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 4 Aug 2020 15:31:03 -0400 Subject: [PATCH 7/9] feat: add support for Python 3.8 (#17) Closes #16 --- noxfile.py | 2 +- setup.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/noxfile.py b/noxfile.py index d6ad371..9856032 100644 --- a/noxfile.py +++ b/noxfile.py @@ -47,7 +47,7 @@ def default(session): ) -@nox.session(python=["2.7", "3.5", "3.6", "3.7"]) +@nox.session(python=["2.7", "3.5", "3.6", "3.7", "3.8"]) def unit(session): """Default unit test session.""" default(session) diff --git a/setup.py b/setup.py index beef8c8..e6987bd 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,8 @@ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", - 'Programming Language :: Python :: 3.7', + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Operating System :: OS Independent", "Topic :: Internet", ], @@ -80,7 +81,7 @@ namespace_packages=namespaces, install_requires=dependencies, extras_require=extras, - python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*', + python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*", include_package_data=True, zip_safe=False, ) From a1e11e1f81a2a7e17b87fe4321eb497f7f3ccdc5 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Tue, 4 Aug 2020 12:42:23 -0700 Subject: [PATCH 8/9] feat: add quota_project, credentials file, and scopes options (#15) Co-authored-by: Tres Seaver --- google/cloud/client.py | 43 +++++++++++++---- tests/unit/test_client.py | 99 ++++++++++++++++++++++++++------------- 2 files changed, 99 insertions(+), 43 deletions(-) diff --git a/google/cloud/client.py b/google/cloud/client.py index f204ba8..6b9117f 100644 --- a/google/cloud/client.py +++ b/google/cloud/client.py @@ -20,6 +20,8 @@ import six +import google.api_core.client_options +import google.api_core.exceptions import google.auth import google.auth.credentials import google.auth.transport.requests @@ -102,6 +104,8 @@ class Client(_ClientFactoryMixin): (Optional) The OAuth2 Credentials to use for this client. If not passed (and if no ``_http`` object is passed), falls back to the default inferred from the environment. + client_options (google.api_core.client_options.ClientOptions): + (Optional) Custom options for the client. _http (requests.Session): (Optional) HTTP object to make requests. Can be any object that defines ``request()`` with the same interface as @@ -123,16 +127,35 @@ class Client(_ClientFactoryMixin): Needs to be set by subclasses. """ - def __init__(self, credentials=None, _http=None): - if credentials is not None and not isinstance( - credentials, google.auth.credentials.Credentials - ): + def __init__(self, credentials=None, _http=None, client_options=None): + if isinstance(client_options, dict): + client_options = google.api_core.client_options.from_dict(client_options) + if client_options is None: + client_options = google.api_core.client_options.ClientOptions() + + if credentials and client_options.credentials_file: + raise google.api_core.exceptions.DuplicateCredentialArgs( + "'credentials' and 'client_options.credentials_file' are mutually exclusive.") + + if credentials and not isinstance(credentials, google.auth.credentials.Credentials): raise ValueError(_GOOGLE_AUTH_CREDENTIALS_HELP) - if credentials is None and _http is None: - credentials, _ = google.auth.default() + + scopes = client_options.scopes or self.SCOPE + + # if no http is provided, credentials must exist + if not _http and credentials is None: + if client_options.credentials_file: + credentials, _ = google.auth.load_credentials_from_file( + client_options.credentials_file, scopes=scopes) + else: + credentials, _ = google.auth.default(scopes=scopes) + self._credentials = google.auth.credentials.with_scopes_if_required( - credentials, self.SCOPE - ) + credentials, scopes=scopes) + + if client_options.quota_project_id: + self._credentials = self._credentials.with_quota_project(client_options.quota_project_id) + self._http_internal = _http def __getstate__(self): @@ -222,6 +245,6 @@ class ClientWithProject(Client, _ClientProjectMixin): _SET_PROJECT = True # Used by from_service_account_json() - def __init__(self, project=None, credentials=None, _http=None): + def __init__(self, project=None, credentials=None, client_options=None, _http=None): _ClientProjectMixin.__init__(self, project=project) - Client.__init__(self, credentials=credentials, _http=_http) + Client.__init__(self, credentials=credentials, client_options=client_options, _http=_http) diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 7aec69f..59e2ea0 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -50,14 +50,14 @@ def _make_one(self, *args, **kw): def test_unpickleable(self): import pickle - CREDENTIALS = _make_credentials() + credentials = _make_credentials() HTTP = object() - client_obj = self._make_one(credentials=CREDENTIALS, _http=HTTP) + client_obj = self._make_one(credentials=credentials, _http=HTTP) with self.assertRaises(pickle.PicklingError): pickle.dumps(client_obj) - def test_constructor_defaults(self): + def test_ctor_defaults(self): credentials = _make_credentials() patch = mock.patch("google.auth.default", return_value=(credentials, None)) @@ -66,9 +66,9 @@ def test_constructor_defaults(self): self.assertIs(client_obj._credentials, credentials) self.assertIsNone(client_obj._http_internal) - default.assert_called_once_with() + default.assert_called_once_with(scopes=None) - def test_constructor_explicit(self): + def test_ctor_explicit(self): credentials = _make_credentials() http = mock.sentinel.http client_obj = self._make_one(credentials=credentials, _http=http) @@ -76,12 +76,71 @@ def test_constructor_explicit(self): self.assertIs(client_obj._credentials, credentials) self.assertIs(client_obj._http_internal, http) - def test_constructor_bad_credentials(self): + def test_ctor_client_options_w_conflicting_creds(self): + from google.api_core.exceptions import DuplicateCredentialArgs + + credentials = _make_credentials() + client_options = {'credentials_file': '/path/to/creds.json'} + with self.assertRaises(DuplicateCredentialArgs): + self._make_one(credentials=credentials, client_options=client_options) + + def test_ctor_bad_credentials(self): credentials = mock.sentinel.credentials with self.assertRaises(ValueError): self._make_one(credentials=credentials) + def test_ctor_client_options_w_creds_file_scopes(self): + credentials = _make_credentials() + credentials_file = '/path/to/creds.json' + scopes = ['SCOPE1', 'SCOPE2'] + client_options = {'credentials_file': credentials_file, 'scopes': scopes} + + patch = mock.patch("google.auth.load_credentials_from_file", return_value=(credentials, None)) + with patch as load_credentials_from_file: + client_obj = self._make_one(client_options=client_options) + + self.assertIs(client_obj._credentials, credentials) + self.assertIsNone(client_obj._http_internal) + load_credentials_from_file.assert_called_once_with(credentials_file, scopes=scopes) + + def test_ctor_client_options_w_quota_project(self): + credentials = _make_credentials() + quota_project_id = 'quota-project-123' + client_options = {'quota_project_id': quota_project_id} + + client_obj = self._make_one(credentials=credentials, client_options=client_options) + + self.assertIs(client_obj._credentials, credentials.with_quota_project.return_value) + credentials.with_quota_project.assert_called_once_with(quota_project_id) + + def test_ctor__http_property_existing(self): + credentials = _make_credentials() + http = object() + client = self._make_one(credentials=credentials, _http=http) + self.assertIs(client._http_internal, http) + self.assertIs(client._http, http) + + def test_ctor__http_property_new(self): + from google.cloud.client import _CREDENTIALS_REFRESH_TIMEOUT + + credentials = _make_credentials() + client = self._make_one(credentials=credentials) + self.assertIsNone(client._http_internal) + + authorized_session_patch = mock.patch( + "google.auth.transport.requests.AuthorizedSession", + return_value=mock.sentinel.http, + ) + with authorized_session_patch as AuthorizedSession: + self.assertIs(client._http, mock.sentinel.http) + # Check the mock. + AuthorizedSession.assert_called_once_with(credentials, refresh_timeout=_CREDENTIALS_REFRESH_TIMEOUT) + # Make sure the cached value is used on subsequent access. + self.assertIs(client._http_internal, mock.sentinel.http) + self.assertIs(client._http, mock.sentinel.http) + self.assertEqual(AuthorizedSession.call_count, 1) + def test_from_service_account_json(self): from google.cloud import _helpers @@ -114,32 +173,6 @@ def test_from_service_account_json_bad_args(self): mock.sentinel.filename, credentials=mock.sentinel.credentials ) - def test__http_property_existing(self): - credentials = _make_credentials() - http = object() - client = self._make_one(credentials=credentials, _http=http) - self.assertIs(client._http_internal, http) - self.assertIs(client._http, http) - - def test__http_property_new(self): - from google.cloud.client import _CREDENTIALS_REFRESH_TIMEOUT - credentials = _make_credentials() - client = self._make_one(credentials=credentials) - self.assertIsNone(client._http_internal) - - authorized_session_patch = mock.patch( - "google.auth.transport.requests.AuthorizedSession", - return_value=mock.sentinel.http, - ) - with authorized_session_patch as AuthorizedSession: - self.assertIs(client._http, mock.sentinel.http) - # Check the mock. - AuthorizedSession.assert_called_once_with(credentials, refresh_timeout=_CREDENTIALS_REFRESH_TIMEOUT) - # Make sure the cached value is used on subsequent access. - self.assertIs(client._http_internal, mock.sentinel.http) - self.assertIs(client._http, mock.sentinel.http) - self.assertEqual(AuthorizedSession.call_count, 1) - class TestClientWithProject(unittest.TestCase): @staticmethod @@ -167,7 +200,7 @@ def test_constructor_defaults(self): self.assertEqual(client_obj.project, project) self.assertIs(client_obj._credentials, credentials) self.assertIsNone(client_obj._http_internal) - default.assert_called_once_with() + default.assert_called_once_with(scopes=None) _determine_default_project.assert_called_once_with(None) def test_constructor_missing_project(self): From 0969ab4eb38129710c89e0579dd6f3cf3d3fa122 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2020 12:49:29 -0700 Subject: [PATCH 9/9] chore: release 1.4.0 (#18) * chore: updated CHANGELOG.md [ci skip] * chore: updated setup.cfg [ci skip] * chore: updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 8 ++++++++ setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b3d91c..2af08a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ [1]: https://pypi.org/project/google-cloud-core/#history +## [1.4.0](https://www.github.com/googleapis/python-cloud-core/compare/v1.3.0...v1.4.0) (2020-08-04) + + +### Features + +* add quota_project, credentials file, and scopes options ([#15](https://www.github.com/googleapis/python-cloud-core/issues/15)) ([a1e11e1](https://www.github.com/googleapis/python-cloud-core/commit/a1e11e1f81a2a7e17b87fe4321eb497f7f3ccdc5)) +* add support for Python 3.8 ([#17](https://www.github.com/googleapis/python-cloud-core/issues/17)) ([f727aba](https://www.github.com/googleapis/python-cloud-core/commit/f727aba432d4726cce72da5f74e8be6adb945a80)), closes [#16](https://www.github.com/googleapis/python-cloud-core/issues/16) + ## 1.3.0 01-31-2020 13:30 PST diff --git a/setup.py b/setup.py index e6987bd..eea8633 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ name = "google-cloud-core" description = "Google Cloud API client core library" -version = "1.3.0" +version = "1.4.0" # Should be one of: # 'Development Status :: 3 - Alpha' # 'Development Status :: 4 - Beta'