From 786e9de706be03354d08ad98d1988eafb0628313 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Tue, 26 Jan 2021 07:42:21 -0800 Subject: [PATCH 1/6] chore: Add header checker config to python library synth (#138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that we have it working in [python-docs-samples](https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/.github/header-checker-lint.yml) we should consider adding it to the 🐍 libraries :) Source-Author: Leah E. Cole <6719667+leahecole@users.noreply.github.com> Source-Date: Mon Jan 25 13:24:08 2021 -0800 Source-Repo: googleapis/synthtool Source-Sha: 573f7655311b553a937f9123bee17bf78497db95 Source-Link: https://github.com/googleapis/synthtool/commit/573f7655311b553a937f9123bee17bf78497db95 --- .github/header-checker-lint.yml | 15 +++++++++++++++ synth.metadata | 5 +++-- 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 .github/header-checker-lint.yml diff --git a/.github/header-checker-lint.yml b/.github/header-checker-lint.yml new file mode 100644 index 00000000..fc281c05 --- /dev/null +++ b/.github/header-checker-lint.yml @@ -0,0 +1,15 @@ +{"allowedCopyrightHolders": ["Google LLC"], + "allowedLicenses": ["Apache-2.0", "MIT", "BSD-3"], + "ignoreFiles": ["**/requirements.txt", "**/requirements-test.txt"], + "sourceFileExtensions": [ + "ts", + "js", + "java", + "sh", + "Dockerfile", + "yaml", + "py", + "html", + "txt" + ] +} \ No newline at end of file diff --git a/synth.metadata b/synth.metadata index fd863114..16eedf99 100644 --- a/synth.metadata +++ b/synth.metadata @@ -4,14 +4,14 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/python-api-core.git", - "sha": "232dab0ad3ef2cca0edfe707d8f90ca0ea200ba2" + "sha": "5e8923f541b8482a05ab46554b108a2efcc615a9" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "ba960d730416fe05c50547e975ce79fcee52c671" + "sha": "573f7655311b553a937f9123bee17bf78497db95" } } ], @@ -21,6 +21,7 @@ ".github/ISSUE_TEMPLATE/feature_request.md", ".github/ISSUE_TEMPLATE/support_request.md", ".github/PULL_REQUEST_TEMPLATE.md", + ".github/header-checker-lint.yml", ".github/release-please.yml", ".github/snippet-bot.yml", ".gitignore", From 471d59467afacfd5c0f2e17f1930be4a17c3e1d6 Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Fri, 29 Jan 2021 08:35:24 -0800 Subject: [PATCH 2/6] build: migrate to flakybot (#140) --- .kokoro/test-samples.sh | 8 ++++---- .kokoro/trampoline_v2.sh | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.kokoro/test-samples.sh b/.kokoro/test-samples.sh index cbdb07ed..c0b4bdc8 100755 --- a/.kokoro/test-samples.sh +++ b/.kokoro/test-samples.sh @@ -87,11 +87,11 @@ for file in samples/**/requirements.txt; do python3.6 -m nox -s "$RUN_TESTS_SESSION" EXIT=$? - # If this is a periodic build, send the test log to the Build Cop Bot. - # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/buildcop. + # If this is a periodic build, send the test log to the FlakyBot. + # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/flakybot. if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then - chmod +x $KOKORO_GFILE_DIR/linux_amd64/buildcop - $KOKORO_GFILE_DIR/linux_amd64/buildcop + chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot + $KOKORO_GFILE_DIR/linux_amd64/flakybot fi if [[ $EXIT -ne 0 ]]; then diff --git a/.kokoro/trampoline_v2.sh b/.kokoro/trampoline_v2.sh index 719bcd5b..4af6cdc2 100755 --- a/.kokoro/trampoline_v2.sh +++ b/.kokoro/trampoline_v2.sh @@ -159,7 +159,7 @@ if [[ -n "${KOKORO_BUILD_ID:-}" ]]; then "KOKORO_GITHUB_COMMIT" "KOKORO_GITHUB_PULL_REQUEST_NUMBER" "KOKORO_GITHUB_PULL_REQUEST_COMMIT" - # For Build Cop Bot + # For FlakyBot "KOKORO_GITHUB_COMMIT_URL" "KOKORO_GITHUB_PULL_REQUEST_URL" ) From db48ea3f8f69cd5039cdc2e35a0a3604d731bc70 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Fri, 29 Jan 2021 17:22:03 -0800 Subject: [PATCH 3/6] build: migrate to flakybot (#141) This PR was generated using Autosynth. :rainbow: Synth log will be available here: https://source.cloud.google.com/results/invocations/fe3713d3-5a91-469f-8d32-daadd024abff/targets - [ ] To automatically regenerate this PR, check this box. Source-Link: https://github.com/googleapis/synthtool/commit/d1bb9173100f62c0cfc8f3138b62241e7f47ca6a --- synth.metadata | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/synth.metadata b/synth.metadata index 16eedf99..f4b30cf6 100644 --- a/synth.metadata +++ b/synth.metadata @@ -4,14 +4,14 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/python-api-core.git", - "sha": "5e8923f541b8482a05ab46554b108a2efcc615a9" + "sha": "786e9de706be03354d08ad98d1988eafb0628313" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "573f7655311b553a937f9123bee17bf78497db95" + "sha": "d1bb9173100f62c0cfc8f3138b62241e7f47ca6a" } } ], From 94c76e0873e5b2f42331d5b1ad286c1e63b61395 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Fri, 5 Feb 2021 10:26:54 -0700 Subject: [PATCH 4/6] feat: allow default_host and default_scopes to be passed to create_channel (#134) Add `default_host` and `default_scopes` parameters to `create_channel` so self-signed JWTs can be used. --- google/api_core/grpc_helpers.py | 77 ++++++++- google/api_core/grpc_helpers_async.py | 7 + setup.py | 1 + testing/constraints-3.6.txt | 1 + tests/asyncio/test_grpc_helpers_async.py | 174 ++++++++++++++++++-- tests/unit/test_grpc_helpers.py | 195 +++++++++++++++++++++-- 6 files changed, 429 insertions(+), 26 deletions(-) diff --git a/google/api_core/grpc_helpers.py b/google/api_core/grpc_helpers.py index 0ccbe126..5937f186 100644 --- a/google/api_core/grpc_helpers.py +++ b/google/api_core/grpc_helpers.py @@ -17,6 +17,8 @@ import collections import grpc +from packaging import version +import pkg_resources import six from google.api_core import exceptions @@ -33,6 +35,20 @@ except ImportError: HAS_GRPC_GCP = False +try: + # google.auth.__version__ was added in 1.26.0 + _GOOGLE_AUTH_VERSION = google.auth.__version__ +except AttributeError: + try: # try pkg_resources if it is available + _GOOGLE_AUTH_VERSION = pkg_resources.get_distribution("google-auth").version + except pkg_resources.DistributionNotFound: # pragma: NO COVER + _GOOGLE_AUTH_VERSION = None + +if _GOOGLE_AUTH_VERSION is not None and version.parse(_GOOGLE_AUTH_VERSION) >= version.parse("1.25.0"): + _GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST = True +else: + _GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST = False + # The list of gRPC Callable interfaces that return iterators. _STREAM_WRAP_CLASSES = (grpc.UnaryStreamMultiCallable, grpc.StreamStreamMultiCallable) @@ -179,9 +195,11 @@ def wrap_errors(callable_): def _create_composite_credentials( credentials=None, credentials_file=None, + default_scopes=None, scopes=None, ssl_credentials=None, - quota_project_id=None): + quota_project_id=None, + default_host=None): """Create the composite credentials for secure channels. Args: @@ -191,12 +209,16 @@ def _create_composite_credentials( 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. + default_scopes (Sequence[str]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. scopes (Sequence[str]): A optional list of scopes needed for this service. These are only used when credentials are not specified and 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. + default_host (str): The default endpoint. e.g., "pubsub.googleapis.com". Returns: grpc.ChannelCredentials: The composed channel credentials object. @@ -210,11 +232,38 @@ def _create_composite_credentials( ) if credentials_file: - credentials, _ = google.auth.load_credentials_from_file(credentials_file, scopes=scopes) + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if _GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + credentials, _ = google.auth.load_credentials_from_file( + credentials_file, + scopes=scopes, + default_scopes=default_scopes + ) + else: + credentials, _ = google.auth.load_credentials_from_file( + credentials_file, + scopes=scopes or default_scopes, + ) elif credentials: - credentials = google.auth.credentials.with_scopes_if_required(credentials, scopes) + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if _GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + credentials = google.auth.credentials.with_scopes_if_required( + credentials, + scopes=scopes, + default_scopes=default_scopes + ) + else: + credentials = google.auth.credentials.with_scopes_if_required( + credentials, + scopes=scopes or default_scopes, + ) + else: - credentials, _ = google.auth.default(scopes=scopes) + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if _GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + credentials, _ = google.auth.default(scopes=scopes, default_scopes=default_scopes) + else: + credentials, _ = google.auth.default(scopes=scopes or default_scopes) if quota_project_id and isinstance(credentials, google.auth.credentials.CredentialsWithQuotaProject): credentials = credentials.with_quota_project(quota_project_id) @@ -222,9 +271,16 @@ def _create_composite_credentials( request = google.auth.transport.requests.Request() # Create the metadata plugin for inserting the authorization header. - metadata_plugin = google.auth.transport.grpc.AuthMetadataPlugin( - credentials, request - ) + + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if _GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + metadata_plugin = google.auth.transport.grpc.AuthMetadataPlugin( + credentials, request, default_host=default_host, + ) + else: + metadata_plugin = google.auth.transport.grpc.AuthMetadataPlugin( + credentials, request + ) # Create a set of grpc.CallCredentials using the metadata plugin. google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin) @@ -245,6 +301,8 @@ def create_channel( ssl_credentials=None, credentials_file=None, quota_project_id=None, + default_scopes=None, + default_host=None, **kwargs): """Create a secure channel with credentials. @@ -262,6 +320,9 @@ def create_channel( :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. + default_scopes (Sequence[str]): Default scopes passed by a Google client + library. Use 'scopes' for user-defined scopes. + default_host (str): The default endpoint. e.g., "pubsub.googleapis.com". kwargs: Additional key-word args passed to :func:`grpc_gcp.secure_channel` or :func:`grpc.secure_channel`. @@ -275,9 +336,11 @@ def create_channel( composite_credentials = _create_composite_credentials( credentials=credentials, credentials_file=credentials_file, + default_scopes=default_scopes, scopes=scopes, ssl_credentials=ssl_credentials, quota_project_id=quota_project_id, + default_host=default_host, ) if HAS_GRPC_GCP: diff --git a/google/api_core/grpc_helpers_async.py b/google/api_core/grpc_helpers_async.py index 9a994e9f..14eb5a13 100644 --- a/google/api_core/grpc_helpers_async.py +++ b/google/api_core/grpc_helpers_async.py @@ -213,6 +213,8 @@ def create_channel( ssl_credentials=None, credentials_file=None, quota_project_id=None, + default_scopes=None, + default_host=None, **kwargs): """Create an AsyncIO secure channel with credentials. @@ -230,6 +232,9 @@ def create_channel( :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. + default_scopes (Sequence[str]): Default scopes passed by a Google client + library. Use 'scopes' for user-defined scopes. + default_host (str): The default endpoint. e.g., "pubsub.googleapis.com". kwargs: Additional key-word args passed to :func:`aio.secure_channel`. Returns: @@ -243,8 +248,10 @@ def create_channel( credentials=credentials, credentials_file=credentials_file, scopes=scopes, + default_scopes=default_scopes, ssl_credentials=ssl_credentials, quota_project_id=quota_project_id, + default_host=default_host ) return aio.secure_channel(target, composite_credentials, **kwargs) diff --git a/setup.py b/setup.py index 30adb954..5de5aafb 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ "google-auth >= 1.21.1, < 2.0dev", "requests >= 2.18.0, < 3.0.0dev", "setuptools >= 40.3.0", + "packaging >= 14.3", "six >= 1.13.0", "pytz", 'futures >= 3.2.0; python_version < "3.2"', diff --git a/testing/constraints-3.6.txt b/testing/constraints-3.6.txt index 2d498173..1fcd1934 100644 --- a/testing/constraints-3.6.txt +++ b/testing/constraints-3.6.txt @@ -10,6 +10,7 @@ protobuf==3.12.0 google-auth==1.21.1 requests==2.18.0 setuptools==40.3.0 +packaging==14.3 six==1.13.0 grpcio==1.29.0 grpcio-gcp==0.2.2 diff --git a/tests/asyncio/test_grpc_helpers_async.py b/tests/asyncio/test_grpc_helpers_async.py index 766e11a9..3461cbe8 100644 --- a/tests/asyncio/test_grpc_helpers_async.py +++ b/tests/asyncio/test_grpc_helpers_async.py @@ -18,6 +18,7 @@ import pytest from google.api_core import exceptions +from google.api_core import grpc_helpers from google.api_core import grpc_helpers_async import google.auth.credentials @@ -263,6 +264,7 @@ def test_wrap_errors_streaming(wrap_stream_errors): @mock.patch("grpc.composite_channel_credentials") @mock.patch( "google.auth.default", + autospec=True, return_value=(mock.sentinel.credentials, mock.sentinel.projet), ) @mock.patch("grpc.experimental.aio.secure_channel") @@ -273,7 +275,45 @@ def test_create_channel_implicit(grpc_secure_channel, default, composite_creds_c channel = grpc_helpers_async.create_channel(target) assert channel is grpc_secure_channel.return_value - default.assert_called_once_with(scopes=None) + + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + default.assert_called_once_with(scopes=None, default_scopes=None) + else: + default.assert_called_once_with(scopes=None) + grpc_secure_channel.assert_called_once_with(target, composite_creds) + + +@mock.patch("google.auth.transport.grpc.AuthMetadataPlugin", autospec=True) +@mock.patch( + "google.auth.transport.requests.Request", + autospec=True, + return_value=mock.sentinel.Request +) +@mock.patch("grpc.composite_channel_credentials") +@mock.patch( + "google.auth.default", + autospec=True, + return_value=(mock.sentinel.credentials, mock.sentinel.projet), +) +@mock.patch("grpc.experimental.aio.secure_channel") +def test_create_channel_implicit_with_default_host(grpc_secure_channel, default, composite_creds_call, request, auth_metadata_plugin): + target = "example.com:443" + default_host = "example.com" + composite_creds = composite_creds_call.return_value + + channel = grpc_helpers_async.create_channel(target, default_host=default_host) + + assert channel is grpc_secure_channel.return_value + + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + default.assert_called_once_with(scopes=None, default_scopes=None) + auth_metadata_plugin.assert_called_once_with(mock.sentinel.credentials, mock.sentinel.Request, default_host=default_host) + else: + default.assert_called_once_with(scopes=None) + auth_metadata_plugin.assert_called_once_with(mock.sentinel.credentials, mock.sentinel.Request) + grpc_secure_channel.assert_called_once_with(target, composite_creds) @@ -292,7 +332,12 @@ def test_create_channel_implicit_with_ssl_creds( grpc_helpers_async.create_channel(target, ssl_credentials=ssl_creds) - default.assert_called_once_with(scopes=None) + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + default.assert_called_once_with(scopes=None, default_scopes=None) + else: + default.assert_called_once_with(scopes=None) + composite_creds_call.assert_called_once_with(ssl_creds, mock.ANY) composite_creds = composite_creds_call.return_value grpc_secure_channel.assert_called_once_with(target, composite_creds) @@ -301,6 +346,7 @@ def test_create_channel_implicit_with_ssl_creds( @mock.patch("grpc.composite_channel_credentials") @mock.patch( "google.auth.default", + autospec=True, return_value=(mock.sentinel.credentials, mock.sentinel.projet), ) @mock.patch("grpc.experimental.aio.secure_channel") @@ -313,7 +359,39 @@ def test_create_channel_implicit_with_scopes( channel = grpc_helpers_async.create_channel(target, scopes=["one", "two"]) assert channel is grpc_secure_channel.return_value - default.assert_called_once_with(scopes=["one", "two"]) + + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + default.assert_called_once_with(scopes=["one", "two"], default_scopes=None) + else: + default.assert_called_once_with(scopes=["one", "two"]) + + grpc_secure_channel.assert_called_once_with(target, composite_creds) + + +@mock.patch("grpc.composite_channel_credentials") +@mock.patch( + "google.auth.default", + autospec=True, + return_value=(mock.sentinel.credentials, mock.sentinel.projet), +) +@mock.patch("grpc.experimental.aio.secure_channel") +def test_create_channel_implicit_with_default_scopes( + grpc_secure_channel, default, composite_creds_call +): + target = "example.com:443" + composite_creds = composite_creds_call.return_value + + channel = grpc_helpers_async.create_channel(target, default_scopes=["three", "four"]) + + assert channel is grpc_secure_channel.return_value + + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + default.assert_called_once_with(scopes=None, default_scopes=["three", "four"]) + else: + default.assert_called_once_with(scopes=["three", "four"]) + grpc_secure_channel.assert_called_once_with(target, composite_creds) @@ -331,7 +409,7 @@ def test_create_channel_explicit_with_duplicate_credentials(): @mock.patch("grpc.composite_channel_credentials") -@mock.patch("google.auth.credentials.with_scopes_if_required") +@mock.patch("google.auth.credentials.with_scopes_if_required", autospec=True) @mock.patch("grpc.experimental.aio.secure_channel") def test_create_channel_explicit(grpc_secure_channel, auth_creds, composite_creds_call): target = "example.com:443" @@ -339,7 +417,12 @@ def test_create_channel_explicit(grpc_secure_channel, auth_creds, composite_cred channel = grpc_helpers_async.create_channel(target, credentials=mock.sentinel.credentials) - auth_creds.assert_called_once_with(mock.sentinel.credentials, None) + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + auth_creds.assert_called_once_with(mock.sentinel.credentials, scopes=None, default_scopes=None) + else: + auth_creds.assert_called_once_with(mock.sentinel.credentials, scopes=None) + assert channel is grpc_secure_channel.return_value grpc_secure_channel.assert_called_once_with(target, composite_creds) @@ -357,8 +440,36 @@ def test_create_channel_explicit_scoped(grpc_secure_channel, composite_creds_cal channel = grpc_helpers_async.create_channel( target, credentials=credentials, scopes=scopes ) + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + credentials.with_scopes.assert_called_once_with(scopes, default_scopes=None) + else: + credentials.with_scopes.assert_called_once_with(scopes) + + 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") +def test_create_channel_explicit_default_scopes(grpc_secure_channel, composite_creds_call): + target = "example.com:443" + default_scopes = ["3", "4"] + composite_creds = composite_creds_call.return_value + + credentials = mock.create_autospec(google.auth.credentials.Scoped, instance=True) + credentials.requires_scopes = True + + channel = grpc_helpers_async.create_channel( + target, credentials=credentials, default_scopes=default_scopes + ) + + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + credentials.with_scopes.assert_called_once_with(scopes=None, default_scopes=default_scopes) + else: + credentials.with_scopes.assert_called_once_with(scopes=default_scopes) - credentials.with_scopes.assert_called_once_with(scopes) assert channel is grpc_secure_channel.return_value grpc_secure_channel.assert_called_once_with(target, composite_creds) @@ -384,6 +495,7 @@ def test_create_channel_explicit_with_quota_project(grpc_secure_channel, composi @mock.patch("grpc.experimental.aio.secure_channel") @mock.patch( "google.auth.load_credentials_from_file", + autospec=True, return_value=(mock.sentinel.credentials, mock.sentinel.project) ) def test_create_channnel_with_credentials_file(load_credentials_from_file, grpc_secure_channel, composite_creds_call): @@ -396,7 +508,12 @@ def test_create_channnel_with_credentials_file(load_credentials_from_file, grpc_ target, credentials_file=credentials_file ) - google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=None) + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=None, default_scopes=None) + else: + google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=None) + assert channel is grpc_secure_channel.return_value grpc_secure_channel.assert_called_once_with(target, composite_creds) @@ -405,6 +522,7 @@ def test_create_channnel_with_credentials_file(load_credentials_from_file, grpc_ @mock.patch("grpc.experimental.aio.secure_channel") @mock.patch( "google.auth.load_credentials_from_file", + autospec=True, return_value=(mock.sentinel.credentials, mock.sentinel.project) ) def test_create_channel_with_credentials_file_and_scopes(load_credentials_from_file, grpc_secure_channel, composite_creds_call): @@ -418,7 +536,40 @@ def test_create_channel_with_credentials_file_and_scopes(load_credentials_from_f target, credentials_file=credentials_file, scopes=scopes ) - google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=scopes) + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=scopes, default_scopes=None) + else: + google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=scopes) + + 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( + "google.auth.load_credentials_from_file", + autospec=True, + return_value=(mock.sentinel.credentials, mock.sentinel.project) +) +def test_create_channel_with_credentials_file_and_default_scopes(load_credentials_from_file, grpc_secure_channel, composite_creds_call): + target = "example.com:443" + default_scopes = ["3", "4"] + + credentials_file = "/path/to/credentials/file.json" + composite_creds = composite_creds_call.return_value + + channel = grpc_helpers_async.create_channel( + target, credentials_file=credentials_file, default_scopes=default_scopes + ) + + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=None, default_scopes=default_scopes) + else: + google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=default_scopes) + assert channel is grpc_secure_channel.return_value grpc_secure_channel.assert_called_once_with(target, composite_creds) @@ -434,7 +585,12 @@ def test_create_channel_without_grpc_gcp(grpc_secure_channel): grpc_helpers_async.create_channel(target, credentials=credentials, scopes=scopes) grpc_secure_channel.assert_called() - credentials.with_scopes.assert_called_once_with(scopes) + + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + credentials.with_scopes.assert_called_once_with(scopes, default_scopes=None) + else: + credentials.with_scopes.assert_called_once_with(scopes) @pytest.mark.asyncio diff --git a/tests/unit/test_grpc_helpers.py b/tests/unit/test_grpc_helpers.py index d6ec60a5..4e0ab806 100644 --- a/tests/unit/test_grpc_helpers.py +++ b/tests/unit/test_grpc_helpers.py @@ -222,6 +222,7 @@ def test_wrap_errors_streaming(wrap_stream_errors): @mock.patch("grpc.composite_channel_credentials") @mock.patch( "google.auth.default", + autospec=True, return_value=(mock.sentinel.credentials, mock.sentinel.projet), ) @mock.patch("grpc.secure_channel") @@ -232,7 +233,51 @@ def test_create_channel_implicit(grpc_secure_channel, default, composite_creds_c channel = grpc_helpers.create_channel(target) assert channel is grpc_secure_channel.return_value - default.assert_called_once_with(scopes=None) + + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + default.assert_called_once_with(scopes=None, default_scopes=None) + else: + default.assert_called_once_with(scopes=None) + + 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("google.auth.transport.grpc.AuthMetadataPlugin", autospec=True) +@mock.patch( + "google.auth.transport.requests.Request", + autospec=True, + return_value=mock.sentinel.Request +) +@mock.patch("grpc.composite_channel_credentials") +@mock.patch( + "google.auth.default", + autospec=True, + return_value=(mock.sentinel.credentials, mock.sentinel.project), +) +@mock.patch("grpc.secure_channel") +def test_create_channel_implicit_with_default_host(grpc_secure_channel, default, composite_creds_call, request, auth_metadata_plugin): + target = "example.com:443" + default_host = "example.com" + composite_creds = composite_creds_call.return_value + + channel = grpc_helpers.create_channel(target, default_host=default_host) + + assert channel is grpc_secure_channel.return_value + + # TODO: remove this if/else once google-auth >= 1.25.0 is required + print(grpc_helpers._GOOGLE_AUTH_VERSION) + print(grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST) + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + default.assert_called_once_with(scopes=None, default_scopes=None) + auth_metadata_plugin.assert_called_once_with(mock.sentinel.credentials, mock.sentinel.Request, default_host=default_host) + else: + default.assert_called_once_with(scopes=None) + auth_metadata_plugin.assert_called_once_with(mock.sentinel.credentials, mock.sentinel.Request) + if grpc_helpers.HAS_GRPC_GCP: grpc_secure_channel.assert_called_once_with(target, composite_creds, None) else: @@ -242,6 +287,7 @@ def test_create_channel_implicit(grpc_secure_channel, default, composite_creds_c @mock.patch("grpc.composite_channel_credentials") @mock.patch( "google.auth.default", + autospec=True, return_value=(mock.sentinel.credentials, mock.sentinel.projet), ) @mock.patch("grpc.secure_channel") @@ -254,7 +300,12 @@ def test_create_channel_implicit_with_ssl_creds( grpc_helpers.create_channel(target, ssl_credentials=ssl_creds) - default.assert_called_once_with(scopes=None) + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + default.assert_called_once_with(scopes=None, default_scopes=None) + else: + default.assert_called_once_with(scopes=None) + composite_creds_call.assert_called_once_with(ssl_creds, mock.ANY) composite_creds = composite_creds_call.return_value if grpc_helpers.HAS_GRPC_GCP: @@ -266,6 +317,7 @@ def test_create_channel_implicit_with_ssl_creds( @mock.patch("grpc.composite_channel_credentials") @mock.patch( "google.auth.default", + autospec=True, return_value=(mock.sentinel.credentials, mock.sentinel.projet), ) @mock.patch("grpc.secure_channel") @@ -278,7 +330,42 @@ def test_create_channel_implicit_with_scopes( channel = grpc_helpers.create_channel(target, scopes=["one", "two"]) assert channel is grpc_secure_channel.return_value - default.assert_called_once_with(scopes=["one", "two"]) + + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + default.assert_called_once_with(scopes=["one", "two"], default_scopes=None) + else: + default.assert_called_once_with(scopes=["one", "two"]) + + 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( + "google.auth.default", + autospec=True, + return_value=(mock.sentinel.credentials, mock.sentinel.projet), +) +@mock.patch("grpc.secure_channel") +def test_create_channel_implicit_with_default_scopes( + grpc_secure_channel, default, composite_creds_call +): + target = "example.com:443" + composite_creds = composite_creds_call.return_value + + channel = grpc_helpers.create_channel(target, default_scopes=["three", "four"]) + + assert channel is grpc_secure_channel.return_value + + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + default.assert_called_once_with(scopes=None, default_scopes=["three", "four"]) + else: + default.assert_called_once_with(scopes=["three", "four"]) + if grpc_helpers.HAS_GRPC_GCP: grpc_secure_channel.assert_called_once_with(target, composite_creds, None) else: @@ -297,7 +384,7 @@ def test_create_channel_explicit_with_duplicate_credentials(): @mock.patch("grpc.composite_channel_credentials") -@mock.patch("google.auth.credentials.with_scopes_if_required") +@mock.patch("google.auth.credentials.with_scopes_if_required", autospec=True) @mock.patch("grpc.secure_channel") def test_create_channel_explicit(grpc_secure_channel, auth_creds, composite_creds_call): target = "example.com:443" @@ -305,7 +392,11 @@ def test_create_channel_explicit(grpc_secure_channel, auth_creds, composite_cred channel = grpc_helpers.create_channel(target, credentials=mock.sentinel.credentials) - auth_creds.assert_called_once_with(mock.sentinel.credentials, None) + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + auth_creds.assert_called_once_with(mock.sentinel.credentials, scopes=None, default_scopes=None) + else: + auth_creds.assert_called_once_with(mock.sentinel.credentials, scopes=None) 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) @@ -327,7 +418,39 @@ def test_create_channel_explicit_scoped(grpc_secure_channel, composite_creds_cal target, credentials=credentials, scopes=scopes ) - credentials.with_scopes.assert_called_once_with(scopes) + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + credentials.with_scopes.assert_called_once_with(scopes, default_scopes=None) + else: + credentials.with_scopes.assert_called_once_with(scopes) + + 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") +def test_create_channel_explicit_default_scopes(grpc_secure_channel, composite_creds_call): + target = "example.com:443" + default_scopes = ["3", "4"] + composite_creds = composite_creds_call.return_value + + credentials = mock.create_autospec(google.auth.credentials.Scoped, instance=True) + credentials.requires_scopes = True + + channel = grpc_helpers.create_channel( + target, credentials=credentials, default_scopes=default_scopes + ) + + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + credentials.with_scopes.assert_called_once_with(scopes=None, default_scopes=default_scopes) + else: + credentials.with_scopes.assert_called_once_with(scopes=default_scopes) + 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) @@ -362,6 +485,7 @@ def test_create_channel_explicit_with_quota_project(grpc_secure_channel, composi @mock.patch("grpc.secure_channel") @mock.patch( "google.auth.load_credentials_from_file", + autospec=True, return_value=(mock.sentinel.credentials, mock.sentinel.project) ) def test_create_channel_with_credentials_file(load_credentials_from_file, grpc_secure_channel, composite_creds_call): @@ -374,7 +498,11 @@ def test_create_channel_with_credentials_file(load_credentials_from_file, grpc_s target, credentials_file=credentials_file ) - google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=None) + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=None, default_scopes=None) + else: + google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=None) assert channel is grpc_secure_channel.return_value if grpc_helpers.HAS_GRPC_GCP: @@ -387,6 +515,7 @@ def test_create_channel_with_credentials_file(load_credentials_from_file, grpc_s @mock.patch("grpc.secure_channel") @mock.patch( "google.auth.load_credentials_from_file", + autospec=True, return_value=(mock.sentinel.credentials, mock.sentinel.project) ) def test_create_channel_with_credentials_file_and_scopes(load_credentials_from_file, grpc_secure_channel, composite_creds_call): @@ -400,7 +529,43 @@ def test_create_channel_with_credentials_file_and_scopes(load_credentials_from_f target, credentials_file=credentials_file, scopes=scopes ) - google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=scopes) + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=scopes, default_scopes=None) + else: + google.auth.load_credentials_from_file.assert_called_once_with(credentials_file, scopes=scopes) + + 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( + "google.auth.load_credentials_from_file", + autospec=True, + return_value=(mock.sentinel.credentials, mock.sentinel.project) +) +def test_create_channel_with_credentials_file_and_default_scopes(load_credentials_from_file, grpc_secure_channel, composite_creds_call): + target = "example.com:443" + default_scopes = ["3", "4"] + + credentials_file = "/path/to/credentials/file.json" + composite_creds = composite_creds_call.return_value + + channel = grpc_helpers.create_channel( + target, credentials_file=credentials_file, default_scopes=default_scopes + ) + + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + load_credentials_from_file.assert_called_once_with(credentials_file, scopes=None, default_scopes=default_scopes) + else: + load_credentials_from_file.assert_called_once_with(credentials_file, scopes=default_scopes) + 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) @@ -421,7 +586,12 @@ def test_create_channel_with_grpc_gcp(grpc_gcp_secure_channel): grpc_helpers.create_channel(target, credentials=credentials, scopes=scopes) grpc_gcp_secure_channel.assert_called() - credentials.with_scopes.assert_called_once_with(scopes) + + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + credentials.with_scopes.assert_called_once_with(scopes, default_scopes=None) + else: + credentials.with_scopes.assert_called_once_with(scopes) @pytest.mark.skipif(grpc_helpers.HAS_GRPC_GCP, reason="grpc_gcp module not available") @@ -435,7 +605,12 @@ def test_create_channel_without_grpc_gcp(grpc_secure_channel): grpc_helpers.create_channel(target, credentials=credentials, scopes=scopes) grpc_secure_channel.assert_called() - credentials.with_scopes.assert_called_once_with(scopes) + + # TODO: remove this if/else once google-auth >= 1.25.0 is required + if grpc_helpers._GOOGLE_AUTH_HAS_DEFAULT_SCOPES_AND_DEFAULT_HOST: + credentials.with_scopes.assert_called_once_with(scopes, default_scopes=None) + else: + credentials.with_scopes.assert_called_once_with(scopes) class TestChannelStub(object): From 911e82f594391f94206e5ab72c8968d56c5e4bfa Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Mon, 8 Feb 2021 08:29:23 -0800 Subject: [PATCH 5/6] chore(python): include py.typed files in release (#145) A py.typed file must be included in the released package for it to be considered typed by type checkers. https://www.python.org/dev/peps/pep-0561/#packaging-type-information. See https://github.com/googleapis/python-secret-manager/issues/79 Source-Author: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Source-Date: Fri Feb 5 17:32:06 2021 -0700 Source-Repo: googleapis/synthtool Source-Sha: 33366574ffb9e11737b3547eb6f020ecae0536e8 Source-Link: https://github.com/googleapis/synthtool/commit/33366574ffb9e11737b3547eb6f020ecae0536e8 --- MANIFEST.in | 4 ++-- synth.metadata | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index e9e29d12..e783f4c6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -16,10 +16,10 @@ # Generated by synthtool. DO NOT EDIT! include README.rst LICENSE -recursive-include google *.json *.proto +recursive-include google *.json *.proto py.typed recursive-include tests * global-exclude *.py[co] global-exclude __pycache__ # Exclude scripts for samples readmegen -prune scripts/readme-gen \ No newline at end of file +prune scripts/readme-gen diff --git a/synth.metadata b/synth.metadata index f4b30cf6..fec849e7 100644 --- a/synth.metadata +++ b/synth.metadata @@ -4,14 +4,14 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/python-api-core.git", - "sha": "786e9de706be03354d08ad98d1988eafb0628313" + "sha": "94c76e0873e5b2f42331d5b1ad286c1e63b61395" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "d1bb9173100f62c0cfc8f3138b62241e7f47ca6a" + "sha": "33366574ffb9e11737b3547eb6f020ecae0536e8" } } ], From 7273090a011c8b840d2e90e29dda3fc8f0eed792 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 8 Feb 2021 09:29:57 -0700 Subject: [PATCH 6/6] chore: release 1.26.0 (#142) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- CHANGELOG.md | 7 +++++++ google/api_core/version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4ff7fd3..df88b0d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-api-core/#history +## [1.26.0](https://www.github.com/googleapis/python-api-core/compare/v1.25.1...v1.26.0) (2021-02-08) + + +### Features + +* allow default_host and default_scopes to be passed to create_channel ([#134](https://www.github.com/googleapis/python-api-core/issues/134)) ([94c76e0](https://www.github.com/googleapis/python-api-core/commit/94c76e0873e5b2f42331d5b1ad286c1e63b61395)) + ### [1.25.1](https://www.github.com/googleapis/python-api-core/compare/v1.25.0...v1.25.1) (2021-01-25) diff --git a/google/api_core/version.py b/google/api_core/version.py index 5966ec4e..7fcd17a6 100644 --- a/google/api_core/version.py +++ b/google/api_core/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.25.1" +__version__ = "1.26.0"