- 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 26ad9977..95ac3553 100644 --- a/synth.metadata +++ b/synth.metadata @@ -4,14 +4,14 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/python-api-core.git", - "sha": "98f03c8dc6f47379bdc8ded8efd24b577e95326d" + "sha": "c890675dc9ebc084f105be81dc81c048f4f599ea" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "cd522c3b4dde821766d95c80ae5aeb43d7a41170" + "sha": "303271797a360f8a439203413f13a160f2f5b3b4" } } ] From e2d9a7b209b7dfab300dc848fabbae8f42a2ab19 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 20 Jul 2020 13:43:47 -0700 Subject: [PATCH 4/6] feat: allow quota project to be passed to create_channel (#58) * feat: allow quota project to be passed to create_channel * chore: update test name * chore: lint and increase auth lib version * chore: fix lint * Update setup.py --- google/api_core/grpc_helpers.py | 24 +++++++++++++++++++++--- google/api_core/grpc_helpers_async.py | 13 +++++++++++-- setup.py | 2 +- tests/asyncio/test_grpc_helpers_async.py | 17 +++++++++++++++++ tests/unit/test_grpc_helpers.py | 23 +++++++++++++++++++++++ 5 files changed, 73 insertions(+), 6 deletions(-) diff --git a/google/api_core/grpc_helpers.py b/google/api_core/grpc_helpers.py index 2203968e..dfc8442d 100644 --- a/google/api_core/grpc_helpers.py +++ b/google/api_core/grpc_helpers.py @@ -176,7 +176,12 @@ def wrap_errors(callable_): return _wrap_unary_errors(callable_) -def _create_composite_credentials(credentials=None, credentials_file=None, scopes=None, ssl_credentials=None): +def _create_composite_credentials( + credentials=None, + credentials_file=None, + scopes=None, + ssl_credentials=None, + quota_project_id=None): """Create the composite credentials for secure channels. Args: @@ -191,6 +196,7 @@ def _create_composite_credentials(credentials=None, credentials_file=None, scope are passed to :func:`google.auth.default`. ssl_credentials (grpc.ChannelCredentials): Optional SSL channel credentials. This can be used to specify different certificates. + quota_project_id (str): An optional project to use for billing and quota. Returns: grpc.ChannelCredentials: The composed channel credentials object. @@ -210,6 +216,9 @@ def _create_composite_credentials(credentials=None, credentials_file=None, scope else: credentials, _ = google.auth.default(scopes=scopes) + if quota_project_id: + credentials = credentials.with_quota_project(quota_project_id) + request = google.auth.transport.requests.Request() # Create the metadata plugin for inserting the authorization header. @@ -229,7 +238,14 @@ def _create_composite_credentials(credentials=None, credentials_file=None, scope ) -def create_channel(target, credentials=None, scopes=None, ssl_credentials=None, credentials_file=None, **kwargs): +def create_channel( + target, + credentials=None, + scopes=None, + ssl_credentials=None, + credentials_file=None, + quota_project_id=None, + **kwargs): """Create a secure channel with credentials. Args: @@ -245,6 +261,7 @@ def create_channel(target, credentials=None, scopes=None, ssl_credentials=None, credentials_file (str): A file with credentials that can be loaded with :func:`google.auth.load_credentials_from_file`. This argument is mutually exclusive with credentials. + quota_project_id (str): An optional project to use for billing and quota. kwargs: Additional key-word args passed to :func:`grpc_gcp.secure_channel` or :func:`grpc.secure_channel`. @@ -259,7 +276,8 @@ def create_channel(target, credentials=None, scopes=None, ssl_credentials=None, credentials=credentials, credentials_file=credentials_file, scopes=scopes, - ssl_credentials=ssl_credentials + ssl_credentials=ssl_credentials, + quota_project_id=quota_project_id, ) if HAS_GRPC_GCP: diff --git a/google/api_core/grpc_helpers_async.py b/google/api_core/grpc_helpers_async.py index 1dfe8b9a..9a994e9f 100644 --- a/google/api_core/grpc_helpers_async.py +++ b/google/api_core/grpc_helpers_async.py @@ -206,7 +206,14 @@ def wrap_errors(callable_): return _wrap_stream_errors(callable_) -def create_channel(target, credentials=None, scopes=None, ssl_credentials=None, credentials_file=None, **kwargs): +def create_channel( + target, + credentials=None, + scopes=None, + ssl_credentials=None, + credentials_file=None, + quota_project_id=None, + **kwargs): """Create an AsyncIO secure channel with credentials. Args: @@ -222,6 +229,7 @@ def create_channel(target, credentials=None, scopes=None, ssl_credentials=None, credentials_file (str): A file with credentials that can be loaded with :func:`google.auth.load_credentials_from_file`. This argument is mutually exclusive with credentials. + quota_project_id (str): An optional project to use for billing and quota. kwargs: Additional key-word args passed to :func:`aio.secure_channel`. Returns: @@ -235,7 +243,8 @@ def create_channel(target, credentials=None, scopes=None, ssl_credentials=None, credentials=credentials, credentials_file=credentials_file, scopes=scopes, - ssl_credentials=ssl_credentials + ssl_credentials=ssl_credentials, + quota_project_id=quota_project_id, ) return aio.secure_channel(target, composite_credentials, **kwargs) diff --git a/setup.py b/setup.py index efa4a12a..6e82e54e 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ dependencies = [ "googleapis-common-protos >= 1.6.0, < 2.0dev", "protobuf >= 3.12.0", - "google-auth >= 1.18.0, < 2.0dev", + "google-auth >= 1.19.1, < 2.0dev", "requests >= 2.18.0, < 3.0.0dev", "setuptools >= 34.0.0", "six >= 1.10.0", diff --git a/tests/asyncio/test_grpc_helpers_async.py b/tests/asyncio/test_grpc_helpers_async.py index d56c4c60..924a74ca 100644 --- a/tests/asyncio/test_grpc_helpers_async.py +++ b/tests/asyncio/test_grpc_helpers_async.py @@ -363,6 +363,23 @@ def test_create_channel_explicit_scoped(grpc_secure_channel, composite_creds_cal grpc_secure_channel.assert_called_once_with(target, composite_creds) +@mock.patch("grpc.composite_channel_credentials") +@mock.patch("grpc.experimental.aio.secure_channel") +def test_create_channel_explicit_with_quota_project(grpc_secure_channel, composite_creds_call): + target = "example.com:443" + composite_creds = composite_creds_call.return_value + + credentials = mock.create_autospec(google.auth.credentials.Credentials, instance=True) + + channel = grpc_helpers_async.create_channel( + target, credentials=credentials, quota_project_id="project-foo" + ) + + credentials.with_quota_project.assert_called_once_with("project-foo") + assert channel is grpc_secure_channel.return_value + grpc_secure_channel.assert_called_once_with(target, composite_creds) + + @mock.patch("grpc.composite_channel_credentials") @mock.patch("grpc.experimental.aio.secure_channel") @mock.patch( diff --git a/tests/unit/test_grpc_helpers.py b/tests/unit/test_grpc_helpers.py index e2f36662..f8fed403 100644 --- a/tests/unit/test_grpc_helpers.py +++ b/tests/unit/test_grpc_helpers.py @@ -335,6 +335,29 @@ def test_create_channel_explicit_scoped(grpc_secure_channel, composite_creds_cal grpc_secure_channel.assert_called_once_with(target, composite_creds) +@mock.patch("grpc.composite_channel_credentials") +@mock.patch("grpc.secure_channel") +def test_create_channel_explicit_with_quota_project(grpc_secure_channel, composite_creds_call): + target = "example.com:443" + composite_creds = composite_creds_call.return_value + + credentials = mock.create_autospec(google.auth.credentials.Credentials, instance=True) + + channel = grpc_helpers.create_channel( + target, + credentials=credentials, + quota_project_id="project-foo" + ) + + credentials.with_quota_project.assert_called_once_with("project-foo") + + assert channel is grpc_secure_channel.return_value + if grpc_helpers.HAS_GRPC_GCP: + grpc_secure_channel.assert_called_once_with(target, composite_creds, None) + else: + grpc_secure_channel.assert_called_once_with(target, composite_creds) + + @mock.patch("grpc.composite_channel_credentials") @mock.patch("grpc.secure_channel") @mock.patch( From 2010373b27536d1191175624b297a709d70153fa Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 21 Jul 2020 10:09:37 -0700 Subject: [PATCH 5/6] fix: _determine_timeout problem handling float type timeout (#64) --- .gitignore | 3 ++- google/api_core/gapic_v1/method.py | 14 ++++++++------ tests/unit/gapic/test_method.py | 24 +++++++++++++++++++++++- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index b87e1ed5..157bfb33 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ pip-log.txt .nox .cache .pytest_cache +pytype_output # Mac @@ -57,4 +58,4 @@ system_tests/local_test_setup # Make sure a generated file isn't accidentally committed. pylintrc -pylintrc.test \ No newline at end of file +pylintrc.test diff --git a/google/api_core/gapic_v1/method.py b/google/api_core/gapic_v1/method.py index 49982c03..8bf82569 100644 --- a/google/api_core/gapic_v1/method.py +++ b/google/api_core/gapic_v1/method.py @@ -61,6 +61,13 @@ def _determine_timeout(default_timeout, specified_timeout, retry): Returns: Optional[Timeout]: The timeout to apply to the method or ``None``. """ + # If timeout is specified as a number instead of a Timeout instance, + # convert it to a ConstantTimeout. + if isinstance(specified_timeout, (int, float)): + specified_timeout = timeout.ConstantTimeout(specified_timeout) + if isinstance(default_timeout, (int, float)): + default_timeout = timeout.ConstantTimeout(default_timeout) + if specified_timeout is DEFAULT: specified_timeout = default_timeout @@ -78,12 +85,7 @@ def _determine_timeout(default_timeout, specified_timeout, retry): else: return default_timeout - # If timeout is specified as a number instead of a Timeout instance, - # convert it to a ConstantTimeout. - if isinstance(specified_timeout, (int, float)): - return timeout.ConstantTimeout(specified_timeout) - else: - return specified_timeout + return specified_timeout class _GapicCallable(object): diff --git a/tests/unit/gapic/test_method.py b/tests/unit/gapic/test_method.py index 0f9bee93..1ae27de0 100644 --- a/tests/unit/gapic/test_method.py +++ b/tests/unit/gapic/test_method.py @@ -32,6 +32,27 @@ def _utcnow_monotonic(): curr_value += delta +def test__determine_timeout(): + # Check _determine_timeout always returns a Timeout object. + timeout_type_timeout = timeout.ConstantTimeout(600.0) + returned_timeout = google.api_core.gapic_v1.method._determine_timeout( + 600.0, 600.0, None + ) + assert isinstance(returned_timeout, timeout.ConstantTimeout) + returned_timeout = google.api_core.gapic_v1.method._determine_timeout( + 600.0, timeout_type_timeout, None + ) + assert isinstance(returned_timeout, timeout.ConstantTimeout) + returned_timeout = google.api_core.gapic_v1.method._determine_timeout( + timeout_type_timeout, 600.0, None + ) + assert isinstance(returned_timeout, timeout.ConstantTimeout) + returned_timeout = google.api_core.gapic_v1.method._determine_timeout( + timeout_type_timeout, timeout_type_timeout, None + ) + assert isinstance(returned_timeout, timeout.ConstantTimeout) + + def test_wrap_method_basic(): method = mock.Mock(spec=["__call__"], return_value=42) @@ -154,7 +175,8 @@ def test_wrap_method_with_default_retry_and_timeout_using_sentinel(unusued_sleep @mock.patch("time.sleep") def test_wrap_method_with_overriding_retry_and_timeout(unusued_sleep): - method = mock.Mock(spec=["__call__"], side_effect=[exceptions.NotFound(None), 42]) + method = mock.Mock(spec=["__call__"], side_effect=[ + exceptions.NotFound(None), 42]) default_retry = retry.Retry() default_timeout = timeout.ConstantTimeout(60) wrapped_method = google.api_core.gapic_v1.method.wrap_method( From 805c3643f50ec11514a24ba98588fca2223c8d5b Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2020 11:16:19 -0700 Subject: [PATCH 6/6] chore: release 1.22.0 (#62) * updated CHANGELOG.md [ci skip] * updated setup.cfg [ci skip] * updated setup.py Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 17 +++++++++++++++++ setup.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d4ea78e..ba764fa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ [1]: https://pypi.org/project/google-api-core/#history +## [1.22.0](https://www.github.com/googleapis/python-api-core/compare/v1.21.0...v1.22.0) (2020-07-21) + + +### Features + +* allow quota project to be passed to create_channel ([#58](https://www.github.com/googleapis/python-api-core/issues/58)) ([e2d9a7b](https://www.github.com/googleapis/python-api-core/commit/e2d9a7b209b7dfab300dc848fabbae8f42a2ab19)) + + +### Bug Fixes + +* _determine_timeout problem handling float type timeout ([#64](https://www.github.com/googleapis/python-api-core/issues/64)) ([2010373](https://www.github.com/googleapis/python-api-core/commit/2010373b27536d1191175624b297a709d70153fa)) + + +### Documentation + +* change the documentation for using 'six.moves.collections_abc.Mapping' instead of 'dict' in 'client_options.from_dict()' ([#53](https://www.github.com/googleapis/python-api-core/issues/53)) ([c890675](https://www.github.com/googleapis/python-api-core/commit/c890675dc9ebc084f105be81dc81c048f4f599ea)) + ## [1.21.0](https://www.github.com/googleapis/python-api-core/compare/v1.20.1...v1.21.0) (2020-06-18) diff --git a/setup.py b/setup.py index 6e82e54e..b1884dfe 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ name = "google-api-core" description = "Google API client core library" -version = "1.21.0" +version = "1.22.0" # Should be one of: # 'Development Status :: 3 - Alpha' # 'Development Status :: 4 - Beta'