From 94a006ea95f692e431cda4cea9e5042f494c0704 Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Tue, 30 Mar 2021 15:58:32 -0700 Subject: [PATCH] fix: fix retry deadlines (#74) feat: add `from_service_account_info` --- .coveragerc | 20 +- .github/header-checker-lint.yml | 15 + .gitignore | 4 +- .kokoro/build.sh | 26 +- .kokoro/docs/docs-presubmit.cfg | 11 + .kokoro/samples/python3.6/periodic-head.cfg | 11 + .kokoro/samples/python3.7/periodic-head.cfg | 11 + .kokoro/samples/python3.8/periodic-head.cfg | 11 + .kokoro/test-samples-against-head.sh | 28 ++ .kokoro/test-samples-impl.sh | 102 +++++ .kokoro/test-samples.sh | 96 +---- .pre-commit-config.yaml | 2 +- .trampolinerc | 1 + CONTRIBUTING.rst | 22 +- LICENSE | 7 +- MANIFEST.in | 4 +- docs/_static/custom.css | 7 +- docs/recommender_v1/recommender.rst | 11 + docs/recommender_v1/services.rst | 6 +- docs/recommender_v1/types.rst | 1 + docs/recommender_v1beta1/recommender.rst | 11 + docs/recommender_v1beta1/services.rst | 6 +- docs/recommender_v1beta1/types.rst | 1 + .../services/recommender/async_client.py | 92 +++-- .../services/recommender/client.py | 143 ++++--- .../services/recommender/pagers.py | 43 ++- .../services/recommender/transports/base.py | 22 +- .../services/recommender/transports/grpc.py | 112 +++--- .../recommender/transports/grpc_asyncio.py | 120 +++--- google/cloud/recommender_v1/types/__init__.py | 36 +- google/cloud/recommender_v1/types/insight.py | 16 +- .../recommender_v1/types/recommendation.py | 36 +- .../types/recommender_service.py | 12 +- .../services/recommender/async_client.py | 92 +++-- .../services/recommender/client.py | 143 ++++--- .../services/recommender/pagers.py | 43 ++- .../services/recommender/transports/base.py | 22 +- .../services/recommender/transports/grpc.py | 112 +++--- .../recommender/transports/grpc_asyncio.py | 120 +++--- .../recommender_v1beta1/types/__init__.py | 36 +- .../recommender_v1beta1/types/insight.py | 16 +- .../types/recommendation.py | 36 +- .../types/recommender_service.py | 12 +- noxfile.py | 59 ++- renovate.json | 3 +- setup.py | 2 +- synth.metadata | 123 +----- synth.py | 1 + testing/constraints-3.6.txt | 9 + testing/constraints-3.7.txt | 2 + testing/constraints-3.8.txt | 2 + testing/constraints-3.9.txt | 2 + tests/unit/gapic/recommender_v1/__init__.py | 15 + .../gapic/recommender_v1/test_recommender.py | 353 ++++++++++++++---- .../gapic/recommender_v1beta1/__init__.py | 15 + .../recommender_v1beta1/test_recommender.py | 353 ++++++++++++++---- 56 files changed, 1613 insertions(+), 1004 deletions(-) create mode 100644 .github/header-checker-lint.yml create mode 100644 .kokoro/samples/python3.6/periodic-head.cfg create mode 100644 .kokoro/samples/python3.7/periodic-head.cfg create mode 100644 .kokoro/samples/python3.8/periodic-head.cfg create mode 100755 .kokoro/test-samples-against-head.sh create mode 100755 .kokoro/test-samples-impl.sh create mode 100644 docs/recommender_v1/recommender.rst create mode 100644 docs/recommender_v1beta1/recommender.rst create mode 100644 testing/constraints-3.6.txt create mode 100644 testing/constraints-3.7.txt create mode 100644 testing/constraints-3.8.txt create mode 100644 testing/constraints-3.9.txt diff --git a/.coveragerc b/.coveragerc index e1eed34..50fa7f7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,27 +1,11 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2020 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 -# -# https://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. - -# Generated by synthtool. DO NOT EDIT! [run] branch = True [report] fail_under = 100 show_missing = True -omit = google/cloud/recommender/__init__.py +omit = + google/cloud/recommender/__init__.py exclude_lines = # Re-enable the standard pragma pragma: NO COVER diff --git a/.github/header-checker-lint.yml b/.github/header-checker-lint.yml new file mode 100644 index 0000000..fc281c0 --- /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/.gitignore b/.gitignore index b9daa52..b4243ce 100644 --- a/.gitignore +++ b/.gitignore @@ -50,8 +50,10 @@ docs.metadata # Virtual environment env/ + +# Test logs coverage.xml -sponge_log.xml +*sponge_log.xml # System test environment variables. system_tests/local_test_setup diff --git a/.kokoro/build.sh b/.kokoro/build.sh index 2957bf6..7c14027 100755 --- a/.kokoro/build.sh +++ b/.kokoro/build.sh @@ -15,7 +15,11 @@ set -eo pipefail -cd github/python-recommender +if [[ -z "${PROJECT_ROOT:-}" ]]; then + PROJECT_ROOT="github/python-recommender" +fi + +cd "${PROJECT_ROOT}" # Disable buffering, so that the logs stream through. export PYTHONUNBUFFERED=1 @@ -30,16 +34,26 @@ export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/service-account.json export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.json") # Remove old nox -python3.6 -m pip uninstall --yes --quiet nox-automation +python3 -m pip uninstall --yes --quiet nox-automation # Install nox -python3.6 -m pip install --upgrade --quiet nox -python3.6 -m nox --version +python3 -m pip install --upgrade --quiet nox +python3 -m nox --version + +# If this is a continuous 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 = *"continuous"* ]]; then + cleanup() { + chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot + $KOKORO_GFILE_DIR/linux_amd64/flakybot + } + trap cleanup EXIT HUP +fi # If NOX_SESSION is set, it only runs the specified session, # otherwise run all the sessions. if [[ -n "${NOX_SESSION:-}" ]]; then - python3.6 -m nox -s "${NOX_SESSION:-}" + python3 -m nox -s ${NOX_SESSION:-} else - python3.6 -m nox + python3 -m nox fi diff --git a/.kokoro/docs/docs-presubmit.cfg b/.kokoro/docs/docs-presubmit.cfg index 1118107..7083e4b 100644 --- a/.kokoro/docs/docs-presubmit.cfg +++ b/.kokoro/docs/docs-presubmit.cfg @@ -15,3 +15,14 @@ env_vars: { key: "TRAMPOLINE_IMAGE_UPLOAD" value: "false" } + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-recommender/.kokoro/build.sh" +} + +# Only run this nox session. +env_vars: { + key: "NOX_SESSION" + value: "docs docfx" +} diff --git a/.kokoro/samples/python3.6/periodic-head.cfg b/.kokoro/samples/python3.6/periodic-head.cfg new file mode 100644 index 0000000..f9cfcd3 --- /dev/null +++ b/.kokoro/samples/python3.6/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-pubsub/.kokoro/test-samples-against-head.sh" +} diff --git a/.kokoro/samples/python3.7/periodic-head.cfg b/.kokoro/samples/python3.7/periodic-head.cfg new file mode 100644 index 0000000..f9cfcd3 --- /dev/null +++ b/.kokoro/samples/python3.7/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-pubsub/.kokoro/test-samples-against-head.sh" +} diff --git a/.kokoro/samples/python3.8/periodic-head.cfg b/.kokoro/samples/python3.8/periodic-head.cfg new file mode 100644 index 0000000..f9cfcd3 --- /dev/null +++ b/.kokoro/samples/python3.8/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-pubsub/.kokoro/test-samples-against-head.sh" +} diff --git a/.kokoro/test-samples-against-head.sh b/.kokoro/test-samples-against-head.sh new file mode 100755 index 0000000..befb5b5 --- /dev/null +++ b/.kokoro/test-samples-against-head.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Copyright 2020 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 +# +# https://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. + +# A customized test runner for samples. +# +# For periodic builds, you can specify this file for testing against head. + +# `-e` enables the script to automatically fail when a command fails +# `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero +set -eo pipefail +# Enables `**` to include files nested inside sub-folders +shopt -s globstar + +cd github/python-recommender + +exec .kokoro/test-samples-impl.sh diff --git a/.kokoro/test-samples-impl.sh b/.kokoro/test-samples-impl.sh new file mode 100755 index 0000000..cf5de74 --- /dev/null +++ b/.kokoro/test-samples-impl.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# Copyright 2021 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 +# +# https://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. + + +# `-e` enables the script to automatically fail when a command fails +# `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero +set -eo pipefail +# Enables `**` to include files nested inside sub-folders +shopt -s globstar + +# Exit early if samples directory doesn't exist +if [ ! -d "./samples" ]; then + echo "No tests run. `./samples` not found" + exit 0 +fi + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +# Debug: show build environment +env | grep KOKORO + +# Install nox +python3.6 -m pip install --upgrade --quiet nox + +# Use secrets acessor service account to get secrets +if [[ -f "${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" ]]; then + gcloud auth activate-service-account \ + --key-file="${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" \ + --project="cloud-devrel-kokoro-resources" +fi + +# This script will create 3 files: +# - testing/test-env.sh +# - testing/service-account.json +# - testing/client-secrets.json +./scripts/decrypt-secrets.sh + +source ./testing/test-env.sh +export GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/testing/service-account.json + +# For cloud-run session, we activate the service account for gcloud sdk. +gcloud auth activate-service-account \ + --key-file "${GOOGLE_APPLICATION_CREDENTIALS}" + +export GOOGLE_CLIENT_SECRETS=$(pwd)/testing/client-secrets.json + +echo -e "\n******************** TESTING PROJECTS ********************" + +# Switch to 'fail at end' to allow all tests to complete before exiting. +set +e +# Use RTN to return a non-zero value if the test fails. +RTN=0 +ROOT=$(pwd) +# Find all requirements.txt in the samples directory (may break on whitespace). +for file in samples/**/requirements.txt; do + cd "$ROOT" + # Navigate to the project folder. + file=$(dirname "$file") + cd "$file" + + echo "------------------------------------------------------------" + echo "- testing $file" + echo "------------------------------------------------------------" + + # Use nox to execute the tests for the project. + python3.6 -m nox -s "$RUN_TESTS_SESSION" + EXIT=$? + + # 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/flakybot + $KOKORO_GFILE_DIR/linux_amd64/flakybot + fi + + if [[ $EXIT -ne 0 ]]; then + RTN=1 + echo -e "\n Testing failed: Nox returned a non-zero exit code. \n" + else + echo -e "\n Testing completed.\n" + fi + +done +cd "$ROOT" + +# Workaround for Kokoro permissions issue: delete secrets +rm testing/{test-env.sh,client-secrets.json,service-account.json} + +exit "$RTN" diff --git a/.kokoro/test-samples.sh b/.kokoro/test-samples.sh index 3bf4255..6187ab1 100755 --- a/.kokoro/test-samples.sh +++ b/.kokoro/test-samples.sh @@ -13,6 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +# The default test runner for samples. +# +# For periodic builds, we rewinds the repo to the latest release, and +# run test-samples-impl.sh. # `-e` enables the script to automatically fail when a command fails # `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero @@ -24,87 +28,19 @@ cd github/python-recommender # Run periodic samples tests at latest release if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then + # preserving the test runner implementation. + cp .kokoro/test-samples-impl.sh "${TMPDIR}/test-samples-impl.sh" + echo "--- IMPORTANT IMPORTANT IMPORTANT ---" + echo "Now we rewind the repo back to the latest release..." LATEST_RELEASE=$(git describe --abbrev=0 --tags) git checkout $LATEST_RELEASE -fi - -# Exit early if samples directory doesn't exist -if [ ! -d "./samples" ]; then - echo "No tests run. `./samples` not found" - exit 0 -fi - -# Disable buffering, so that the logs stream through. -export PYTHONUNBUFFERED=1 - -# Debug: show build environment -env | grep KOKORO - -# Install nox -python3.6 -m pip install --upgrade --quiet nox - -# Use secrets acessor service account to get secrets -if [[ -f "${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" ]]; then - gcloud auth activate-service-account \ - --key-file="${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" \ - --project="cloud-devrel-kokoro-resources" -fi - -# This script will create 3 files: -# - testing/test-env.sh -# - testing/service-account.json -# - testing/client-secrets.json -./scripts/decrypt-secrets.sh - -source ./testing/test-env.sh -export GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/testing/service-account.json - -# For cloud-run session, we activate the service account for gcloud sdk. -gcloud auth activate-service-account \ - --key-file "${GOOGLE_APPLICATION_CREDENTIALS}" - -export GOOGLE_CLIENT_SECRETS=$(pwd)/testing/client-secrets.json - -echo -e "\n******************** TESTING PROJECTS ********************" - -# Switch to 'fail at end' to allow all tests to complete before exiting. -set +e -# Use RTN to return a non-zero value if the test fails. -RTN=0 -ROOT=$(pwd) -# Find all requirements.txt in the samples directory (may break on whitespace). -for file in samples/**/requirements.txt; do - cd "$ROOT" - # Navigate to the project folder. - file=$(dirname "$file") - cd "$file" - - echo "------------------------------------------------------------" - echo "- testing $file" - echo "------------------------------------------------------------" - - # Use nox to execute the tests for the project. - python3.6 -m nox -s "$RUN_TESTS_SESSION" - EXIT=$? - - # 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/flakybot - $KOKORO_GFILE_DIR/linux_amd64/flakybot + echo "The current head is: " + echo $(git rev-parse --verify HEAD) + echo "--- IMPORTANT IMPORTANT IMPORTANT ---" + # move back the test runner implementation if there's no file. + if [ ! -f .kokoro/test-samples-impl.sh ]; then + cp "${TMPDIR}/test-samples-impl.sh" .kokoro/test-samples-impl.sh fi +fi - if [[ $EXIT -ne 0 ]]; then - RTN=1 - echo -e "\n Testing failed: Nox returned a non-zero exit code. \n" - else - echo -e "\n Testing completed.\n" - fi - -done -cd "$ROOT" - -# Workaround for Kokoro permissions issue: delete secrets -rm testing/{test-env.sh,client-secrets.json,service-account.json} - -exit "$RTN" +exec .kokoro/test-samples-impl.sh diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a9024b1..32302e4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,6 +12,6 @@ repos: hooks: - id: black - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + rev: 3.9.0 hooks: - id: flake8 diff --git a/.trampolinerc b/.trampolinerc index 995ee29..383b6ec 100644 --- a/.trampolinerc +++ b/.trampolinerc @@ -24,6 +24,7 @@ required_envvars+=( pass_down_envvars+=( "STAGING_BUCKET" "V2_STAGING_BUCKET" + "NOX_SESSION" ) # Prevent unintentional override on the default image. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 576bfde..f7310d8 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -70,9 +70,14 @@ We use `nox `__ to instrument our tests. - To test your changes, run unit tests with ``nox``:: $ nox -s unit-2.7 - $ nox -s unit-3.7 + $ nox -s unit-3.8 $ ... +- Args to pytest can be passed through the nox command separated by a `--`. For + example, to run a single test:: + + $ nox -s unit-3.8 -- -k + .. note:: The unit tests and system tests are described in the @@ -93,8 +98,12 @@ On Debian/Ubuntu:: ************ Coding Style ************ +- We use the automatic code formatter ``black``. You can run it using + the nox session ``blacken``. This will eliminate many lint errors. Run via:: + + $ nox -s blacken -- PEP8 compliance, with exceptions defined in the linter configuration. +- PEP8 compliance is required, with exceptions defined in the linter configuration. If you have ``nox`` installed, you can test that you have not introduced any non-compliant code via:: @@ -133,13 +142,18 @@ Running System Tests - To run system tests, you can execute:: - $ nox -s system-3.7 + # Run all system tests + $ nox -s system-3.8 $ nox -s system-2.7 + # Run a single system test + $ nox -s system-3.8 -- -k + + .. note:: System tests are only configured to run under Python 2.7 and - Python 3.7. For expediency, we do not run them in older versions + Python 3.8. For expediency, we do not run them in older versions of Python 3. This alone will not run the tests. You'll need to change some local diff --git a/LICENSE b/LICENSE index a8ee855..d645695 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ - Apache License + + Apache License Version 2.0, January 2004 - https://www.apache.org/licenses/ + http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION @@ -192,7 +193,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - https://www.apache.org/licenses/LICENSE-2.0 + 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, diff --git a/MANIFEST.in b/MANIFEST.in index e9e29d1..e783f4c 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/docs/_static/custom.css b/docs/_static/custom.css index 0abaf22..bcd37bb 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -1,4 +1,9 @@ div#python2-eol { border-color: red; border-width: medium; -} \ No newline at end of file +} + +/* Ensure minimum width for 'Parameters' / 'Returns' column */ +dl.field-list > dt { + min-width: 100px +} diff --git a/docs/recommender_v1/recommender.rst b/docs/recommender_v1/recommender.rst new file mode 100644 index 0000000..b05f944 --- /dev/null +++ b/docs/recommender_v1/recommender.rst @@ -0,0 +1,11 @@ +Recommender +----------------------------- + +.. automodule:: google.cloud.recommender_v1.services.recommender + :members: + :inherited-members: + + +.. automodule:: google.cloud.recommender_v1.services.recommender.pagers + :members: + :inherited-members: diff --git a/docs/recommender_v1/services.rst b/docs/recommender_v1/services.rst index 64345c5..d23296a 100644 --- a/docs/recommender_v1/services.rst +++ b/docs/recommender_v1/services.rst @@ -1,6 +1,6 @@ Services for Google Cloud Recommender v1 API ============================================ +.. toctree:: + :maxdepth: 2 -.. automodule:: google.cloud.recommender_v1.services.recommender - :members: - :inherited-members: + recommender diff --git a/docs/recommender_v1/types.rst b/docs/recommender_v1/types.rst index 99c254c..dd13661 100644 --- a/docs/recommender_v1/types.rst +++ b/docs/recommender_v1/types.rst @@ -3,4 +3,5 @@ Types for Google Cloud Recommender v1 API .. automodule:: google.cloud.recommender_v1.types :members: + :undoc-members: :show-inheritance: diff --git a/docs/recommender_v1beta1/recommender.rst b/docs/recommender_v1beta1/recommender.rst new file mode 100644 index 0000000..416d238 --- /dev/null +++ b/docs/recommender_v1beta1/recommender.rst @@ -0,0 +1,11 @@ +Recommender +----------------------------- + +.. automodule:: google.cloud.recommender_v1beta1.services.recommender + :members: + :inherited-members: + + +.. automodule:: google.cloud.recommender_v1beta1.services.recommender.pagers + :members: + :inherited-members: diff --git a/docs/recommender_v1beta1/services.rst b/docs/recommender_v1beta1/services.rst index 5b4764d..5341142 100644 --- a/docs/recommender_v1beta1/services.rst +++ b/docs/recommender_v1beta1/services.rst @@ -1,6 +1,6 @@ Services for Google Cloud Recommender v1beta1 API ================================================= +.. toctree:: + :maxdepth: 2 -.. automodule:: google.cloud.recommender_v1beta1.services.recommender - :members: - :inherited-members: + recommender diff --git a/docs/recommender_v1beta1/types.rst b/docs/recommender_v1beta1/types.rst index c16ac11..16ab2a8 100644 --- a/docs/recommender_v1beta1/types.rst +++ b/docs/recommender_v1beta1/types.rst @@ -3,4 +3,5 @@ Types for Google Cloud Recommender v1beta1 API .. automodule:: google.cloud.recommender_v1beta1.types :members: + :undoc-members: :show-inheritance: diff --git a/google/cloud/recommender_v1/services/recommender/async_client.py b/google/cloud/recommender_v1/services/recommender/async_client.py index 2302ff9..071901c 100644 --- a/google/cloud/recommender_v1/services/recommender/async_client.py +++ b/google/cloud/recommender_v1/services/recommender/async_client.py @@ -90,7 +90,36 @@ class RecommenderAsyncClient: RecommenderClient.parse_common_location_path ) - from_service_account_file = RecommenderClient.from_service_account_file + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + RecommenderAsyncClient: The constructed client. + """ + return RecommenderClient.from_service_account_info.__func__(RecommenderAsyncClient, info, *args, **kwargs) # type: ignore + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + RecommenderAsyncClient: The constructed client. + """ + return RecommenderClient.from_service_account_file.__func__(RecommenderAsyncClient, filename, *args, **kwargs) # type: ignore + from_service_account_json = from_service_account_file @property @@ -168,7 +197,7 @@ async def list_insights( type. Args: - request (:class:`~.recommender_service.ListInsightsRequest`): + request (:class:`google.cloud.recommender_v1.types.ListInsightsRequest`): The request object. Request for the `ListInsights` method. parent (:class:`str`): @@ -183,6 +212,7 @@ async def list_insights( https://cloud.google.com/about/locations/ INSIGHT_TYPE_ID refers to supported insight types: https://cloud.google.com/recommender/docs/insights/insight-types.) + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -194,8 +224,8 @@ async def list_insights( sent along with the request as metadata. Returns: - ~.pagers.ListInsightsAsyncPager: - Response to the ``ListInsights`` method. + google.cloud.recommender_v1.services.recommender.pagers.ListInsightsAsyncPager: + Response to the ListInsights method. Iterating over this object will yield results and resolve additional pages automatically. @@ -230,6 +260,7 @@ async def list_insights( predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=60.0, ), default_timeout=60.0, client_info=DEFAULT_CLIENT_INFO, @@ -266,7 +297,7 @@ async def get_insight( permission for the specified insight type. Args: - request (:class:`~.recommender_service.GetInsightRequest`): + request (:class:`google.cloud.recommender_v1.types.GetInsightRequest`): The request object. Request to the `GetInsight` method. name (:class:`str`): Required. Name of the insight. @@ -281,7 +312,7 @@ async def get_insight( sent along with the request as metadata. Returns: - ~.insight.Insight: + google.cloud.recommender_v1.types.Insight: An insight along with the information used to derive the insight. The insight may have associated recomendations as @@ -317,6 +348,7 @@ async def get_insight( predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=60.0, ), default_timeout=60.0, client_info=DEFAULT_CLIENT_INFO, @@ -357,7 +389,7 @@ async def mark_insight_accepted( specified insight. Args: - request (:class:`~.recommender_service.MarkInsightAcceptedRequest`): + request (:class:`google.cloud.recommender_v1.types.MarkInsightAcceptedRequest`): The request object. Request for the `MarkInsightAccepted` method. name (:class:`str`): @@ -365,15 +397,17 @@ async def mark_insight_accepted( This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - state_metadata (:class:`Sequence[~.recommender_service.MarkInsightAcceptedRequest.StateMetadataEntry]`): + state_metadata (:class:`Sequence[google.cloud.recommender_v1.types.MarkInsightAcceptedRequest.StateMetadataEntry]`): Optional. State properties user wish to include with this state. Full replace of the current state_metadata. + This corresponds to the ``state_metadata`` field on the ``request`` instance; if ``request`` is provided, this should not be set. etag (:class:`str`): Required. Fingerprint of the Insight. Provides optimistic locking. + This corresponds to the ``etag`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -385,7 +419,7 @@ async def mark_insight_accepted( sent along with the request as metadata. Returns: - ~.insight.Insight: + google.cloud.recommender_v1.types.Insight: An insight along with the information used to derive the insight. The insight may have associated recomendations as @@ -449,7 +483,7 @@ async def list_recommendations( recommender.*.list IAM permission for the specified recommender. Args: - request (:class:`~.recommender_service.ListRecommendationsRequest`): + request (:class:`google.cloud.recommender_v1.types.ListRecommendationsRequest`): The request object. Request for the `ListRecommendations` method. parent (:class:`str`): @@ -464,6 +498,7 @@ async def list_recommendations( https://cloud.google.com/about/locations/ RECOMMENDER_ID refers to supported recommenders: https://cloud.google.com/recommender/docs/recommenders. + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -471,7 +506,8 @@ async def list_recommendations( Filter expression to restrict the recommendations returned. Supported filter fields: state_info.state Eg: \`state_info.state:"DISMISSED" or - state_info.state:"FAILED". + state_info.state:"FAILED" + This corresponds to the ``filter`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -483,8 +519,8 @@ async def list_recommendations( sent along with the request as metadata. Returns: - ~.pagers.ListRecommendationsAsyncPager: - Response to the ``ListRecommendations`` method. + google.cloud.recommender_v1.services.recommender.pagers.ListRecommendationsAsyncPager: + Response to the ListRecommendations method. Iterating over this object will yield results and resolve additional pages automatically. @@ -521,6 +557,7 @@ async def list_recommendations( predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=60.0, ), default_timeout=60.0, client_info=DEFAULT_CLIENT_INFO, @@ -557,7 +594,7 @@ async def get_recommendation( recommender.*.get IAM permission for the specified recommender. Args: - request (:class:`~.recommender_service.GetRecommendationRequest`): + request (:class:`google.cloud.recommender_v1.types.GetRecommendationRequest`): The request object. Request to the `GetRecommendation` method. name (:class:`str`): @@ -573,7 +610,7 @@ async def get_recommendation( sent along with the request as metadata. Returns: - ~.recommendation.Recommendation: + google.cloud.recommender_v1.types.Recommendation: A recommendation along with a suggested action. E.g., a rightsizing recommendation for an underutilized VM, @@ -609,6 +646,7 @@ async def get_recommendation( predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=60.0, ), default_timeout=60.0, client_info=DEFAULT_CLIENT_INFO, @@ -652,7 +690,7 @@ async def mark_recommendation_claimed( specified recommender. Args: - request (:class:`~.recommender_service.MarkRecommendationClaimedRequest`): + request (:class:`google.cloud.recommender_v1.types.MarkRecommendationClaimedRequest`): The request object. Request for the `MarkRecommendationClaimed` Method. name (:class:`str`): @@ -660,11 +698,12 @@ async def mark_recommendation_claimed( This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - state_metadata (:class:`Sequence[~.recommender_service.MarkRecommendationClaimedRequest.StateMetadataEntry]`): + state_metadata (:class:`Sequence[google.cloud.recommender_v1.types.MarkRecommendationClaimedRequest.StateMetadataEntry]`): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex ``/^[a-zA-Z0-9_./-]{0,255}$/``. + This corresponds to the ``state_metadata`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -672,6 +711,7 @@ async def mark_recommendation_claimed( Required. Fingerprint of the Recommendation. Provides optimistic locking. + This corresponds to the ``etag`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -683,7 +723,7 @@ async def mark_recommendation_claimed( sent along with the request as metadata. Returns: - ~.recommendation.Recommendation: + google.cloud.recommender_v1.types.Recommendation: A recommendation along with a suggested action. E.g., a rightsizing recommendation for an underutilized VM, @@ -759,7 +799,7 @@ async def mark_recommendation_succeeded( specified recommender. Args: - request (:class:`~.recommender_service.MarkRecommendationSucceededRequest`): + request (:class:`google.cloud.recommender_v1.types.MarkRecommendationSucceededRequest`): The request object. Request for the `MarkRecommendationSucceeded` Method. name (:class:`str`): @@ -767,11 +807,12 @@ async def mark_recommendation_succeeded( This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - state_metadata (:class:`Sequence[~.recommender_service.MarkRecommendationSucceededRequest.StateMetadataEntry]`): + state_metadata (:class:`Sequence[google.cloud.recommender_v1.types.MarkRecommendationSucceededRequest.StateMetadataEntry]`): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex ``/^[a-zA-Z0-9_./-]{0,255}$/``. + This corresponds to the ``state_metadata`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -779,6 +820,7 @@ async def mark_recommendation_succeeded( Required. Fingerprint of the Recommendation. Provides optimistic locking. + This corresponds to the ``etag`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -790,7 +832,7 @@ async def mark_recommendation_succeeded( sent along with the request as metadata. Returns: - ~.recommendation.Recommendation: + google.cloud.recommender_v1.types.Recommendation: A recommendation along with a suggested action. E.g., a rightsizing recommendation for an underutilized VM, @@ -866,7 +908,7 @@ async def mark_recommendation_failed( specified recommender. Args: - request (:class:`~.recommender_service.MarkRecommendationFailedRequest`): + request (:class:`google.cloud.recommender_v1.types.MarkRecommendationFailedRequest`): The request object. Request for the `MarkRecommendationFailed` Method. name (:class:`str`): @@ -874,11 +916,12 @@ async def mark_recommendation_failed( This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - state_metadata (:class:`Sequence[~.recommender_service.MarkRecommendationFailedRequest.StateMetadataEntry]`): + state_metadata (:class:`Sequence[google.cloud.recommender_v1.types.MarkRecommendationFailedRequest.StateMetadataEntry]`): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex ``/^[a-zA-Z0-9_./-]{0,255}$/``. + This corresponds to the ``state_metadata`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -886,6 +929,7 @@ async def mark_recommendation_failed( Required. Fingerprint of the Recommendation. Provides optimistic locking. + This corresponds to the ``etag`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -897,7 +941,7 @@ async def mark_recommendation_failed( sent along with the request as metadata. Returns: - ~.recommendation.Recommendation: + google.cloud.recommender_v1.types.Recommendation: A recommendation along with a suggested action. E.g., a rightsizing recommendation for an underutilized VM, diff --git a/google/cloud/recommender_v1/services/recommender/client.py b/google/cloud/recommender_v1/services/recommender/client.py index 7139bd5..4310b24 100644 --- a/google/cloud/recommender_v1/services/recommender/client.py +++ b/google/cloud/recommender_v1/services/recommender/client.py @@ -118,6 +118,22 @@ def _get_default_mtls_endpoint(api_endpoint): DEFAULT_ENDPOINT ) + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + RecommenderClient: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_info(info) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + @classmethod def from_service_account_file(cls, filename: str, *args, **kwargs): """Creates an instance of this client using the provided credentials @@ -130,7 +146,7 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): kwargs: Additional arguments to pass to the constructor. Returns: - {@api.name}: The constructed client. + RecommenderClient: The constructed client. """ credentials = service_account.Credentials.from_service_account_file(filename) kwargs["credentials"] = credentials @@ -296,10 +312,10 @@ def __init__( credentials identify the application to the service; if none are specified, the client will attempt to ascertain the credentials from the environment. - transport (Union[str, ~.RecommenderTransport]): The + transport (Union[str, RecommenderTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (client_options_lib.ClientOptions): Custom options for the + client_options (google.api_core.client_options.ClientOptions): Custom options for the client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT @@ -335,21 +351,17 @@ def __init__( util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) ) - ssl_credentials = None + client_cert_source_func = None is_mtls = False if use_client_cert: if client_options.client_cert_source: - import grpc # type: ignore - - cert, key = client_options.client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) is_mtls = True + client_cert_source_func = client_options.client_cert_source else: - creds = SslCredentials() - is_mtls = creds.is_mtls - ssl_credentials = creds.ssl_credentials if is_mtls else None + is_mtls = mtls.has_default_client_cert_source() + client_cert_source_func = ( + mtls.default_client_cert_source() if is_mtls else None + ) # Figure out which api endpoint to use. if client_options.api_endpoint is not None: @@ -392,7 +404,7 @@ def __init__( credentials_file=client_options.credentials_file, host=api_endpoint, scopes=client_options.scopes, - ssl_channel_credentials=ssl_credentials, + client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, ) @@ -411,10 +423,10 @@ def list_insights( type. Args: - request (:class:`~.recommender_service.ListInsightsRequest`): + request (google.cloud.recommender_v1.types.ListInsightsRequest): The request object. Request for the `ListInsights` method. - parent (:class:`str`): + parent (str): Required. The container resource on which to execute the request. Acceptable formats: @@ -426,6 +438,7 @@ def list_insights( https://cloud.google.com/about/locations/ INSIGHT_TYPE_ID refers to supported insight types: https://cloud.google.com/recommender/docs/insights/insight-types.) + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -437,8 +450,8 @@ def list_insights( sent along with the request as metadata. Returns: - ~.pagers.ListInsightsPager: - Response to the ``ListInsights`` method. + google.cloud.recommender_v1.services.recommender.pagers.ListInsightsPager: + Response to the ListInsights method. Iterating over this object will yield results and resolve additional pages automatically. @@ -502,9 +515,9 @@ def get_insight( permission for the specified insight type. Args: - request (:class:`~.recommender_service.GetInsightRequest`): + request (google.cloud.recommender_v1.types.GetInsightRequest): The request object. Request to the `GetInsight` method. - name (:class:`str`): + name (str): Required. Name of the insight. This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this @@ -517,7 +530,7 @@ def get_insight( sent along with the request as metadata. Returns: - ~.insight.Insight: + google.cloud.recommender_v1.types.Insight: An insight along with the information used to derive the insight. The insight may have associated recomendations as @@ -586,23 +599,25 @@ def mark_insight_accepted( specified insight. Args: - request (:class:`~.recommender_service.MarkInsightAcceptedRequest`): + request (google.cloud.recommender_v1.types.MarkInsightAcceptedRequest): The request object. Request for the `MarkInsightAccepted` method. - name (:class:`str`): + name (str): Required. Name of the insight. This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - state_metadata (:class:`Sequence[~.recommender_service.MarkInsightAcceptedRequest.StateMetadataEntry]`): + state_metadata (Sequence[google.cloud.recommender_v1.types.MarkInsightAcceptedRequest.StateMetadataEntry]): Optional. State properties user wish to include with this state. Full replace of the current state_metadata. + This corresponds to the ``state_metadata`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - etag (:class:`str`): + etag (str): Required. Fingerprint of the Insight. Provides optimistic locking. + This corresponds to the ``etag`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -614,7 +629,7 @@ def mark_insight_accepted( sent along with the request as metadata. Returns: - ~.insight.Insight: + google.cloud.recommender_v1.types.Insight: An insight along with the information used to derive the insight. The insight may have associated recomendations as @@ -643,12 +658,11 @@ def mark_insight_accepted( if name is not None: request.name = name + if state_metadata is not None: + request.state_metadata = state_metadata if etag is not None: request.etag = etag - if state_metadata: - request.state_metadata.update(state_metadata) - # Wrap the RPC method; this adds retry and timeout information, # and friendly error handling. rpc = self._transport._wrapped_methods[self._transport.mark_insight_accepted] @@ -679,10 +693,10 @@ def list_recommendations( recommender.*.list IAM permission for the specified recommender. Args: - request (:class:`~.recommender_service.ListRecommendationsRequest`): + request (google.cloud.recommender_v1.types.ListRecommendationsRequest): The request object. Request for the `ListRecommendations` method. - parent (:class:`str`): + parent (str): Required. The container resource on which to execute the request. Acceptable formats: @@ -694,14 +708,16 @@ def list_recommendations( https://cloud.google.com/about/locations/ RECOMMENDER_ID refers to supported recommenders: https://cloud.google.com/recommender/docs/recommenders. + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - filter (:class:`str`): + filter (str): Filter expression to restrict the recommendations returned. Supported filter fields: state_info.state Eg: \`state_info.state:"DISMISSED" or - state_info.state:"FAILED". + state_info.state:"FAILED" + This corresponds to the ``filter`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -713,8 +729,8 @@ def list_recommendations( sent along with the request as metadata. Returns: - ~.pagers.ListRecommendationsPager: - Response to the ``ListRecommendations`` method. + google.cloud.recommender_v1.services.recommender.pagers.ListRecommendationsPager: + Response to the ListRecommendations method. Iterating over this object will yield results and resolve additional pages automatically. @@ -780,10 +796,10 @@ def get_recommendation( recommender.*.get IAM permission for the specified recommender. Args: - request (:class:`~.recommender_service.GetRecommendationRequest`): + request (google.cloud.recommender_v1.types.GetRecommendationRequest): The request object. Request to the `GetRecommendation` method. - name (:class:`str`): + name (str): Required. Name of the recommendation. This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this @@ -796,7 +812,7 @@ def get_recommendation( sent along with the request as metadata. Returns: - ~.recommendation.Recommendation: + google.cloud.recommender_v1.types.Recommendation: A recommendation along with a suggested action. E.g., a rightsizing recommendation for an underutilized VM, @@ -868,26 +884,28 @@ def mark_recommendation_claimed( specified recommender. Args: - request (:class:`~.recommender_service.MarkRecommendationClaimedRequest`): + request (google.cloud.recommender_v1.types.MarkRecommendationClaimedRequest): The request object. Request for the `MarkRecommendationClaimed` Method. - name (:class:`str`): + name (str): Required. Name of the recommendation. This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - state_metadata (:class:`Sequence[~.recommender_service.MarkRecommendationClaimedRequest.StateMetadataEntry]`): + state_metadata (Sequence[google.cloud.recommender_v1.types.MarkRecommendationClaimedRequest.StateMetadataEntry]): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex ``/^[a-zA-Z0-9_./-]{0,255}$/``. + This corresponds to the ``state_metadata`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - etag (:class:`str`): + etag (str): Required. Fingerprint of the Recommendation. Provides optimistic locking. + This corresponds to the ``etag`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -899,7 +917,7 @@ def mark_recommendation_claimed( sent along with the request as metadata. Returns: - ~.recommendation.Recommendation: + google.cloud.recommender_v1.types.Recommendation: A recommendation along with a suggested action. E.g., a rightsizing recommendation for an underutilized VM, @@ -930,12 +948,11 @@ def mark_recommendation_claimed( if name is not None: request.name = name + if state_metadata is not None: + request.state_metadata = state_metadata if etag is not None: request.etag = etag - if state_metadata: - request.state_metadata.update(state_metadata) - # Wrap the RPC method; this adds retry and timeout information, # and friendly error handling. rpc = self._transport._wrapped_methods[ @@ -980,26 +997,28 @@ def mark_recommendation_succeeded( specified recommender. Args: - request (:class:`~.recommender_service.MarkRecommendationSucceededRequest`): + request (google.cloud.recommender_v1.types.MarkRecommendationSucceededRequest): The request object. Request for the `MarkRecommendationSucceeded` Method. - name (:class:`str`): + name (str): Required. Name of the recommendation. This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - state_metadata (:class:`Sequence[~.recommender_service.MarkRecommendationSucceededRequest.StateMetadataEntry]`): + state_metadata (Sequence[google.cloud.recommender_v1.types.MarkRecommendationSucceededRequest.StateMetadataEntry]): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex ``/^[a-zA-Z0-9_./-]{0,255}$/``. + This corresponds to the ``state_metadata`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - etag (:class:`str`): + etag (str): Required. Fingerprint of the Recommendation. Provides optimistic locking. + This corresponds to the ``etag`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -1011,7 +1030,7 @@ def mark_recommendation_succeeded( sent along with the request as metadata. Returns: - ~.recommendation.Recommendation: + google.cloud.recommender_v1.types.Recommendation: A recommendation along with a suggested action. E.g., a rightsizing recommendation for an underutilized VM, @@ -1042,12 +1061,11 @@ def mark_recommendation_succeeded( if name is not None: request.name = name + if state_metadata is not None: + request.state_metadata = state_metadata if etag is not None: request.etag = etag - if state_metadata: - request.state_metadata.update(state_metadata) - # Wrap the RPC method; this adds retry and timeout information, # and friendly error handling. rpc = self._transport._wrapped_methods[ @@ -1092,26 +1110,28 @@ def mark_recommendation_failed( specified recommender. Args: - request (:class:`~.recommender_service.MarkRecommendationFailedRequest`): + request (google.cloud.recommender_v1.types.MarkRecommendationFailedRequest): The request object. Request for the `MarkRecommendationFailed` Method. - name (:class:`str`): + name (str): Required. Name of the recommendation. This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - state_metadata (:class:`Sequence[~.recommender_service.MarkRecommendationFailedRequest.StateMetadataEntry]`): + state_metadata (Sequence[google.cloud.recommender_v1.types.MarkRecommendationFailedRequest.StateMetadataEntry]): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex ``/^[a-zA-Z0-9_./-]{0,255}$/``. + This corresponds to the ``state_metadata`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - etag (:class:`str`): + etag (str): Required. Fingerprint of the Recommendation. Provides optimistic locking. + This corresponds to the ``etag`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -1123,7 +1143,7 @@ def mark_recommendation_failed( sent along with the request as metadata. Returns: - ~.recommendation.Recommendation: + google.cloud.recommender_v1.types.Recommendation: A recommendation along with a suggested action. E.g., a rightsizing recommendation for an underutilized VM, @@ -1152,12 +1172,11 @@ def mark_recommendation_failed( if name is not None: request.name = name + if state_metadata is not None: + request.state_metadata = state_metadata if etag is not None: request.etag = etag - if state_metadata: - request.state_metadata.update(state_metadata) - # Wrap the RPC method; this adds retry and timeout information, # and friendly error handling. rpc = self._transport._wrapped_methods[ diff --git a/google/cloud/recommender_v1/services/recommender/pagers.py b/google/cloud/recommender_v1/services/recommender/pagers.py index 86f9944..e51870d 100644 --- a/google/cloud/recommender_v1/services/recommender/pagers.py +++ b/google/cloud/recommender_v1/services/recommender/pagers.py @@ -15,7 +15,16 @@ # limitations under the License. # -from typing import Any, AsyncIterable, Awaitable, Callable, Iterable, Sequence, Tuple +from typing import ( + Any, + AsyncIterable, + Awaitable, + Callable, + Iterable, + Sequence, + Tuple, + Optional, +) from google.cloud.recommender_v1.types import insight from google.cloud.recommender_v1.types import recommendation @@ -26,7 +35,7 @@ class ListInsightsPager: """A pager for iterating through ``list_insights`` requests. This class thinly wraps an initial - :class:`~.recommender_service.ListInsightsResponse` object, and + :class:`google.cloud.recommender_v1.types.ListInsightsResponse` object, and provides an ``__iter__`` method to iterate through its ``insights`` field. @@ -35,7 +44,7 @@ class ListInsightsPager: through the ``insights`` field on the corresponding responses. - All the usual :class:`~.recommender_service.ListInsightsResponse` + All the usual :class:`google.cloud.recommender_v1.types.ListInsightsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -53,9 +62,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.recommender_service.ListInsightsRequest`): + request (google.cloud.recommender_v1.types.ListInsightsRequest): The initial request object. - response (:class:`~.recommender_service.ListInsightsResponse`): + response (google.cloud.recommender_v1.types.ListInsightsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. @@ -88,7 +97,7 @@ class ListInsightsAsyncPager: """A pager for iterating through ``list_insights`` requests. This class thinly wraps an initial - :class:`~.recommender_service.ListInsightsResponse` object, and + :class:`google.cloud.recommender_v1.types.ListInsightsResponse` object, and provides an ``__aiter__`` method to iterate through its ``insights`` field. @@ -97,7 +106,7 @@ class ListInsightsAsyncPager: through the ``insights`` field on the corresponding responses. - All the usual :class:`~.recommender_service.ListInsightsResponse` + All the usual :class:`google.cloud.recommender_v1.types.ListInsightsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -115,9 +124,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.recommender_service.ListInsightsRequest`): + request (google.cloud.recommender_v1.types.ListInsightsRequest): The initial request object. - response (:class:`~.recommender_service.ListInsightsResponse`): + response (google.cloud.recommender_v1.types.ListInsightsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. @@ -154,7 +163,7 @@ class ListRecommendationsPager: """A pager for iterating through ``list_recommendations`` requests. This class thinly wraps an initial - :class:`~.recommender_service.ListRecommendationsResponse` object, and + :class:`google.cloud.recommender_v1.types.ListRecommendationsResponse` object, and provides an ``__iter__`` method to iterate through its ``recommendations`` field. @@ -163,7 +172,7 @@ class ListRecommendationsPager: through the ``recommendations`` field on the corresponding responses. - All the usual :class:`~.recommender_service.ListRecommendationsResponse` + All the usual :class:`google.cloud.recommender_v1.types.ListRecommendationsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -181,9 +190,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.recommender_service.ListRecommendationsRequest`): + request (google.cloud.recommender_v1.types.ListRecommendationsRequest): The initial request object. - response (:class:`~.recommender_service.ListRecommendationsResponse`): + response (google.cloud.recommender_v1.types.ListRecommendationsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. @@ -216,7 +225,7 @@ class ListRecommendationsAsyncPager: """A pager for iterating through ``list_recommendations`` requests. This class thinly wraps an initial - :class:`~.recommender_service.ListRecommendationsResponse` object, and + :class:`google.cloud.recommender_v1.types.ListRecommendationsResponse` object, and provides an ``__aiter__`` method to iterate through its ``recommendations`` field. @@ -225,7 +234,7 @@ class ListRecommendationsAsyncPager: through the ``recommendations`` field on the corresponding responses. - All the usual :class:`~.recommender_service.ListRecommendationsResponse` + All the usual :class:`google.cloud.recommender_v1.types.ListRecommendationsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -245,9 +254,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.recommender_service.ListRecommendationsRequest`): + request (google.cloud.recommender_v1.types.ListRecommendationsRequest): The initial request object. - response (:class:`~.recommender_service.ListRecommendationsResponse`): + response (google.cloud.recommender_v1.types.ListRecommendationsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. diff --git a/google/cloud/recommender_v1/services/recommender/transports/base.py b/google/cloud/recommender_v1/services/recommender/transports/base.py index a8a8541..9148199 100644 --- a/google/cloud/recommender_v1/services/recommender/transports/base.py +++ b/google/cloud/recommender_v1/services/recommender/transports/base.py @@ -71,10 +71,10 @@ def __init__( scope (Optional[Sequence[str]]): A list of scopes. quota_project_id (Optional[str]): An optional project to use for billing and quota. - client_info (google.api_core.gapic_v1.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 + client_info (google.api_core.gapic_v1.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 client library. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. @@ -82,6 +82,9 @@ def __init__( host += ":443" self._host = host + # Save the scopes. + self._scopes = scopes or self.AUTH_SCOPES + # If no credentials are provided, then determine the appropriate # defaults. if credentials and credentials_file: @@ -91,20 +94,17 @@ def __init__( if credentials_file is not None: credentials, _ = auth.load_credentials_from_file( - credentials_file, scopes=scopes, quota_project_id=quota_project_id + credentials_file, scopes=self._scopes, quota_project_id=quota_project_id ) elif credentials is None: credentials, _ = auth.default( - scopes=scopes, quota_project_id=quota_project_id + scopes=self._scopes, quota_project_id=quota_project_id ) # Save the credentials. self._credentials = credentials - # Lifted into its own function so it can be stubbed out during tests. - self._prep_wrapped_messages(client_info) - def _prep_wrapped_messages(self, client_info): # Precompute the wrapped methods. self._wrapped_methods = { @@ -117,6 +117,7 @@ def _prep_wrapped_messages(self, client_info): predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=60.0, ), default_timeout=60.0, client_info=client_info, @@ -130,6 +131,7 @@ def _prep_wrapped_messages(self, client_info): predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=60.0, ), default_timeout=60.0, client_info=client_info, @@ -148,6 +150,7 @@ def _prep_wrapped_messages(self, client_info): predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=60.0, ), default_timeout=60.0, client_info=client_info, @@ -161,6 +164,7 @@ def _prep_wrapped_messages(self, client_info): predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=60.0, ), default_timeout=60.0, client_info=client_info, diff --git a/google/cloud/recommender_v1/services/recommender/transports/grpc.py b/google/cloud/recommender_v1/services/recommender/transports/grpc.py index 1f1bd87..0dc6294 100644 --- a/google/cloud/recommender_v1/services/recommender/transports/grpc.py +++ b/google/cloud/recommender_v1/services/recommender/transports/grpc.py @@ -63,6 +63,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -93,6 +94,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -107,72 +112,60 @@ def __init__( google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` and ``credentials_file`` are passed. """ + self._grpc_channel = None self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) if channel: - # Sanity check: Ensure that channel and credentials are not both - # provided. + # Ignore credentials if a channel was passed. credentials = False - # If a channel was explicitly provided, set it. self._grpc_channel = channel self._ssl_channel_credentials = None - elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( - api_mtls_endpoint - if ":" in api_mtls_endpoint - else api_mtls_endpoint + ":443" - ) + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) - - # Create SSL credentials with client_cert_source or application - # default SSL credentials. - if client_cert_source: - cert, key = client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) else: - ssl_credentials = SslCredentials().ssl_credentials - - # create a new channel. The provided one is ignored. - self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, - credentials_file=credentials_file, - ssl_credentials=ssl_credentials, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - options=[ - ("grpc.max_send_message_length", -1), - ("grpc.max_receive_message_length", -1), - ], - ) - self._ssl_channel_credentials = ssl_credentials - else: - host = host if ":" in host else host + ":443" + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) + # The base transport sets the host, credentials and scopes + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + client_info=client_info, + ) - # create a new channel. The provided one is ignored. + if not self._grpc_channel: self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, + self._host, + credentials=self._credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, - scopes=scopes or self.AUTH_SCOPES, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, options=[ ("grpc.max_send_message_length", -1), @@ -180,17 +173,8 @@ def __init__( ], ) - self._stubs = {} # type: Dict[str, Callable] - - # Run the base constructor. - super().__init__( - host=host, - credentials=credentials, - credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - client_info=client_info, - ) + # Wrap messages. This must be done after self._grpc_channel exists + self._prep_wrapped_messages(client_info) @classmethod def create_channel( @@ -204,7 +188,7 @@ def create_channel( ) -> grpc.Channel: """Create and return a gRPC channel object. Args: - address (Optional[str]): The host for the channel to use. + host (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If diff --git a/google/cloud/recommender_v1/services/recommender/transports/grpc_asyncio.py b/google/cloud/recommender_v1/services/recommender/transports/grpc_asyncio.py index 4098244..d7b2804 100644 --- a/google/cloud/recommender_v1/services/recommender/transports/grpc_asyncio.py +++ b/google/cloud/recommender_v1/services/recommender/transports/grpc_asyncio.py @@ -67,7 +67,7 @@ def create_channel( ) -> aio.Channel: """Create and return a gRPC AsyncIO channel object. Args: - address (Optional[str]): The host for the channel to use. + host (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If @@ -107,6 +107,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -138,12 +139,16 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. - client_info (google.api_core.gapic_v1.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 + client_info (google.api_core.gapic_v1.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 client library. Raises: @@ -152,72 +157,60 @@ def __init__( google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` and ``credentials_file`` are passed. """ + self._grpc_channel = None self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) if channel: - # Sanity check: Ensure that channel and credentials are not both - # provided. + # Ignore credentials if a channel was passed. credentials = False - # If a channel was explicitly provided, set it. self._grpc_channel = channel self._ssl_channel_credentials = None - elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( - api_mtls_endpoint - if ":" in api_mtls_endpoint - else api_mtls_endpoint + ":443" - ) + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) - - # Create SSL credentials with client_cert_source or application - # default SSL credentials. - if client_cert_source: - cert, key = client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) else: - ssl_credentials = SslCredentials().ssl_credentials - - # create a new channel. The provided one is ignored. - self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, - credentials_file=credentials_file, - ssl_credentials=ssl_credentials, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - options=[ - ("grpc.max_send_message_length", -1), - ("grpc.max_receive_message_length", -1), - ], - ) - self._ssl_channel_credentials = ssl_credentials - else: - host = host if ":" in host else host + ":443" + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) + # The base transport sets the host, credentials and scopes + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + client_info=client_info, + ) - # create a new channel. The provided one is ignored. + if not self._grpc_channel: self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, + self._host, + credentials=self._credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, - scopes=scopes or self.AUTH_SCOPES, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, options=[ ("grpc.max_send_message_length", -1), @@ -225,17 +218,8 @@ def __init__( ], ) - # Run the base constructor. - super().__init__( - host=host, - credentials=credentials, - credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - client_info=client_info, - ) - - self._stubs = {} + # Wrap messages. This must be done after self._grpc_channel exists + self._prep_wrapped_messages(client_info) @property def grpc_channel(self) -> aio.Channel: diff --git a/google/cloud/recommender_v1/types/__init__.py b/google/cloud/recommender_v1/types/__init__.py index f75c4f2..395fe1a 100644 --- a/google/cloud/recommender_v1/types/__init__.py +++ b/google/cloud/recommender_v1/types/__init__.py @@ -20,47 +20,47 @@ InsightStateInfo, ) from .recommendation import ( - Recommendation, - RecommendationContent, - OperationGroup, - Operation, - ValueMatcher, CostProjection, Impact, + Operation, + OperationGroup, + Recommendation, + RecommendationContent, RecommendationStateInfo, + ValueMatcher, ) from .recommender_service import ( + GetInsightRequest, + GetRecommendationRequest, ListInsightsRequest, ListInsightsResponse, - GetInsightRequest, - MarkInsightAcceptedRequest, ListRecommendationsRequest, ListRecommendationsResponse, - GetRecommendationRequest, + MarkInsightAcceptedRequest, MarkRecommendationClaimedRequest, - MarkRecommendationSucceededRequest, MarkRecommendationFailedRequest, + MarkRecommendationSucceededRequest, ) __all__ = ( "Insight", "InsightStateInfo", - "Recommendation", - "RecommendationContent", - "OperationGroup", - "Operation", - "ValueMatcher", "CostProjection", "Impact", + "Operation", + "OperationGroup", + "Recommendation", + "RecommendationContent", "RecommendationStateInfo", + "ValueMatcher", + "GetInsightRequest", + "GetRecommendationRequest", "ListInsightsRequest", "ListInsightsResponse", - "GetInsightRequest", - "MarkInsightAcceptedRequest", "ListRecommendationsRequest", "ListRecommendationsResponse", - "GetRecommendationRequest", + "MarkInsightAcceptedRequest", "MarkRecommendationClaimedRequest", - "MarkRecommendationSucceededRequest", "MarkRecommendationFailedRequest", + "MarkRecommendationSucceededRequest", ) diff --git a/google/cloud/recommender_v1/types/insight.py b/google/cloud/recommender_v1/types/insight.py index fb351cd..65ebc99 100644 --- a/google/cloud/recommender_v1/types/insight.py +++ b/google/cloud/recommender_v1/types/insight.py @@ -44,25 +44,25 @@ class Insight(proto.Message): insight_subtype (str): Insight subtype. Insight content schema will be stable for a given subtype. - content (~.struct.Struct): + content (google.protobuf.struct_pb2.Struct): A struct of custom fields to explain the insight. Example: "grantedPermissionsCount": "1000". - last_refresh_time (~.timestamp.Timestamp): + last_refresh_time (google.protobuf.timestamp_pb2.Timestamp): Timestamp of the latest data used to generate the insight. - observation_period (~.duration.Duration): + observation_period (google.protobuf.duration_pb2.Duration): Observation period that led to the insight. The source data used to generate the insight ends at last_refresh_time and begins at (last_refresh_time - observation_period). - state_info (~.insight.InsightStateInfo): + state_info (google.cloud.recommender_v1.types.InsightStateInfo): Information state and metadata. - category (~.insight.Insight.Category): + category (google.cloud.recommender_v1.types.Insight.Category): Category being targeted by the insight. etag (str): Fingerprint of the Insight. Provides optimistic locking when updating states. - associated_recommendations (Sequence[~.insight.Insight.RecommendationReference]): + associated_recommendations (Sequence[google.cloud.recommender_v1.types.Insight.RecommendationReference]): Recommendations derived from this insight. """ @@ -118,9 +118,9 @@ class InsightStateInfo(proto.Message): r"""Information related to insight state. Attributes: - state (~.insight.InsightStateInfo.State): + state (google.cloud.recommender_v1.types.InsightStateInfo.State): Insight state. - state_metadata (Sequence[~.insight.InsightStateInfo.StateMetadataEntry]): + state_metadata (Sequence[google.cloud.recommender_v1.types.InsightStateInfo.StateMetadataEntry]): A map of metadata for the state, provided by user or automations systems. """ diff --git a/google/cloud/recommender_v1/types/recommendation.py b/google/cloud/recommender_v1/types/recommendation.py index e1c4de9..ee4ad3f 100644 --- a/google/cloud/recommender_v1/types/recommendation.py +++ b/google/cloud/recommender_v1/types/recommendation.py @@ -62,29 +62,29 @@ class Recommendation(proto.Message): Examples: For recommender = "google.iam.policy.Recommender", recommender_subtype can be one of "REMOVE_ROLE"/"REPLACE_ROLE". - last_refresh_time (~.timestamp.Timestamp): + last_refresh_time (google.protobuf.timestamp_pb2.Timestamp): Last time this recommendation was refreshed by the system that created it in the first place. - primary_impact (~.recommendation.Impact): + primary_impact (google.cloud.recommender_v1.types.Impact): The primary impact that this recommendation can have while trying to optimize for one category. - additional_impact (Sequence[~.recommendation.Impact]): + additional_impact (Sequence[google.cloud.recommender_v1.types.Impact]): Optional set of additional impact that this recommendation may have when trying to optimize for the primary category. These may be positive or negative. - content (~.recommendation.RecommendationContent): + content (google.cloud.recommender_v1.types.RecommendationContent): Content of the recommendation describing recommended changes to resources. - state_info (~.recommendation.RecommendationStateInfo): + state_info (google.cloud.recommender_v1.types.RecommendationStateInfo): Information for state. Contains state and metadata. etag (str): Fingerprint of the Recommendation. Provides optimistic locking when updating states. - associated_insights (Sequence[~.recommendation.Recommendation.InsightReference]): + associated_insights (Sequence[google.cloud.recommender_v1.types.Recommendation.InsightReference]): Insights that led to this recommendation. """ @@ -131,7 +131,7 @@ class RecommendationContent(proto.Message): changing. Attributes: - operation_groups (Sequence[~.recommendation.OperationGroup]): + operation_groups (Sequence[google.cloud.recommender_v1.types.OperationGroup]): Operations to one or more Google Cloud resources grouped in such a way that, all operations within one group are expected to be @@ -147,7 +147,7 @@ class OperationGroup(proto.Message): r"""Group of operations that need to be performed atomically. Attributes: - operations (Sequence[~.recommendation.Operation]): + operations (Sequence[google.cloud.recommender_v1.types.Operation]): List of operations across one or more resources that belong to this group. Loosely based on RFC6902 and should be performed in the @@ -199,16 +199,16 @@ class Operation(proto.Message): Can be set with action 'copy' or 'move' to indicate the source field within resource or source_resource, ignored if provided for other operation types. - value (~.struct.Value): + value (google.protobuf.struct_pb2.Value): Value for the ``path`` field. Will be set for actions:'add'/'replace'. Maybe set for action: 'test'. Either this or ``value_matcher`` will be set for 'test' operation. An exact match must be performed. - value_matcher (~.recommendation.ValueMatcher): + value_matcher (google.cloud.recommender_v1.types.ValueMatcher): Can be set for action 'test' for advanced matching for the value of 'path' field. Either this or ``value`` will be set for 'test' operation. - path_filters (Sequence[~.recommendation.Operation.PathFiltersEntry]): + path_filters (Sequence[google.cloud.recommender_v1.types.Operation.PathFiltersEntry]): Set of filters to apply if ``path`` refers to array elements or nested array elements in order to narrow down to a single unique element that is being tested/modified. This is @@ -224,7 +224,7 @@ class Operation(proto.Message): "y@example.com"] }`` When both path_filters and path_value_matchers are set, an implicit AND must be performed. - path_value_matchers (Sequence[~.recommendation.Operation.PathValueMatchersEntry]): + path_value_matchers (Sequence[google.cloud.recommender_v1.types.Operation.PathValueMatchersEntry]): Similar to path_filters, this contains set of filters to apply if ``path`` field referes to array elements. This is meant to support value matching beyond exact match. To @@ -283,13 +283,13 @@ class CostProjection(proto.Message): save or incur. Attributes: - cost (~.money.Money): + cost (google.type.money_pb2.Money): An approximate projection on amount saved or amount incurred. Negative cost units indicate cost savings and positive cost units indicate increase. See google.type.Money documentation for positive/negative units. - duration (~.gp_duration.Duration): + duration (google.protobuf.duration_pb2.Duration): Duration for which this cost applies. """ @@ -303,9 +303,9 @@ class Impact(proto.Message): category. Attributes: - category (~.recommendation.Impact.Category): + category (google.cloud.recommender_v1.types.Impact.Category): Category that is being targeted. - cost_projection (~.recommendation.CostProjection): + cost_projection (google.cloud.recommender_v1.types.CostProjection): Use with CategoryType.COST """ @@ -328,10 +328,10 @@ class RecommendationStateInfo(proto.Message): r"""Information for state. Contains state and metadata. Attributes: - state (~.recommendation.RecommendationStateInfo.State): + state (google.cloud.recommender_v1.types.RecommendationStateInfo.State): The state of the recommendation, Eg ACTIVE, SUCCEEDED, FAILED. - state_metadata (Sequence[~.recommendation.RecommendationStateInfo.StateMetadataEntry]): + state_metadata (Sequence[google.cloud.recommender_v1.types.RecommendationStateInfo.StateMetadataEntry]): A map of metadata for the state, provided by user or automations systems. """ diff --git a/google/cloud/recommender_v1/types/recommender_service.py b/google/cloud/recommender_v1/types/recommender_service.py index c366b02..44c8d21 100644 --- a/google/cloud/recommender_v1/types/recommender_service.py +++ b/google/cloud/recommender_v1/types/recommender_service.py @@ -85,7 +85,7 @@ class ListInsightsResponse(proto.Message): r"""Response to the ``ListInsights`` method. Attributes: - insights (Sequence[~.insight.Insight]): + insights (Sequence[google.cloud.recommender_v1.types.Insight]): The set of insights for the ``parent`` resource. next_page_token (str): A token that can be used to request the next @@ -119,7 +119,7 @@ class MarkInsightAcceptedRequest(proto.Message): Attributes: name (str): Required. Name of the insight. - state_metadata (Sequence[~.recommender_service.MarkInsightAcceptedRequest.StateMetadataEntry]): + state_metadata (Sequence[google.cloud.recommender_v1.types.MarkInsightAcceptedRequest.StateMetadataEntry]): Optional. State properties user wish to include with this state. Full replace of the current state_metadata. etag (str): @@ -180,7 +180,7 @@ class ListRecommendationsResponse(proto.Message): r"""Response to the ``ListRecommendations`` method. Attributes: - recommendations (Sequence[~.recommendation.Recommendation]): + recommendations (Sequence[google.cloud.recommender_v1.types.Recommendation]): The set of recommendations for the ``parent`` resource. next_page_token (str): A token that can be used to request the next @@ -216,7 +216,7 @@ class MarkRecommendationClaimedRequest(proto.Message): Attributes: name (str): Required. Name of the recommendation. - state_metadata (Sequence[~.recommender_service.MarkRecommendationClaimedRequest.StateMetadataEntry]): + state_metadata (Sequence[google.cloud.recommender_v1.types.MarkRecommendationClaimedRequest.StateMetadataEntry]): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex @@ -239,7 +239,7 @@ class MarkRecommendationSucceededRequest(proto.Message): Attributes: name (str): Required. Name of the recommendation. - state_metadata (Sequence[~.recommender_service.MarkRecommendationSucceededRequest.StateMetadataEntry]): + state_metadata (Sequence[google.cloud.recommender_v1.types.MarkRecommendationSucceededRequest.StateMetadataEntry]): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex @@ -262,7 +262,7 @@ class MarkRecommendationFailedRequest(proto.Message): Attributes: name (str): Required. Name of the recommendation. - state_metadata (Sequence[~.recommender_service.MarkRecommendationFailedRequest.StateMetadataEntry]): + state_metadata (Sequence[google.cloud.recommender_v1.types.MarkRecommendationFailedRequest.StateMetadataEntry]): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex diff --git a/google/cloud/recommender_v1beta1/services/recommender/async_client.py b/google/cloud/recommender_v1beta1/services/recommender/async_client.py index 6a93552..edcfe33 100644 --- a/google/cloud/recommender_v1beta1/services/recommender/async_client.py +++ b/google/cloud/recommender_v1beta1/services/recommender/async_client.py @@ -90,7 +90,36 @@ class RecommenderAsyncClient: RecommenderClient.parse_common_location_path ) - from_service_account_file = RecommenderClient.from_service_account_file + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + RecommenderAsyncClient: The constructed client. + """ + return RecommenderClient.from_service_account_info.__func__(RecommenderAsyncClient, info, *args, **kwargs) # type: ignore + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + RecommenderAsyncClient: The constructed client. + """ + return RecommenderClient.from_service_account_file.__func__(RecommenderAsyncClient, filename, *args, **kwargs) # type: ignore + from_service_account_json = from_service_account_file @property @@ -168,7 +197,7 @@ async def list_insights( type. Args: - request (:class:`~.recommender_service.ListInsightsRequest`): + request (:class:`google.cloud.recommender_v1beta1.types.ListInsightsRequest`): The request object. Request for the `ListInsights` method. parent (:class:`str`): @@ -183,6 +212,7 @@ async def list_insights( https://cloud.google.com/about/locations/ INSIGHT_TYPE_ID refers to supported insight types: https://cloud.google.com/recommender/docs/insights/insight-types. + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -194,8 +224,8 @@ async def list_insights( sent along with the request as metadata. Returns: - ~.pagers.ListInsightsAsyncPager: - Response to the ``ListInsights`` method. + google.cloud.recommender_v1beta1.services.recommender.pagers.ListInsightsAsyncPager: + Response to the ListInsights method. Iterating over this object will yield results and resolve additional pages automatically. @@ -230,6 +260,7 @@ async def list_insights( predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=60.0, ), default_timeout=60.0, client_info=DEFAULT_CLIENT_INFO, @@ -266,7 +297,7 @@ async def get_insight( permission for the specified insight type. Args: - request (:class:`~.recommender_service.GetInsightRequest`): + request (:class:`google.cloud.recommender_v1beta1.types.GetInsightRequest`): The request object. Request to the `GetInsight` method. name (:class:`str`): Required. Name of the insight. @@ -281,7 +312,7 @@ async def get_insight( sent along with the request as metadata. Returns: - ~.insight.Insight: + google.cloud.recommender_v1beta1.types.Insight: An insight along with the information used to derive the insight. The insight may have associated recomendations as @@ -317,6 +348,7 @@ async def get_insight( predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=60.0, ), default_timeout=60.0, client_info=DEFAULT_CLIENT_INFO, @@ -357,7 +389,7 @@ async def mark_insight_accepted( specified insight. Args: - request (:class:`~.recommender_service.MarkInsightAcceptedRequest`): + request (:class:`google.cloud.recommender_v1beta1.types.MarkInsightAcceptedRequest`): The request object. Request for the `MarkInsightAccepted` method. name (:class:`str`): @@ -365,15 +397,17 @@ async def mark_insight_accepted( This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - state_metadata (:class:`Sequence[~.recommender_service.MarkInsightAcceptedRequest.StateMetadataEntry]`): + state_metadata (:class:`Sequence[google.cloud.recommender_v1beta1.types.MarkInsightAcceptedRequest.StateMetadataEntry]`): Optional. State properties user wish to include with this state. Full replace of the current state_metadata. + This corresponds to the ``state_metadata`` field on the ``request`` instance; if ``request`` is provided, this should not be set. etag (:class:`str`): Required. Fingerprint of the Insight. Provides optimistic locking. + This corresponds to the ``etag`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -385,7 +419,7 @@ async def mark_insight_accepted( sent along with the request as metadata. Returns: - ~.insight.Insight: + google.cloud.recommender_v1beta1.types.Insight: An insight along with the information used to derive the insight. The insight may have associated recomendations as @@ -449,7 +483,7 @@ async def list_recommendations( recommender.*.list IAM permission for the specified recommender. Args: - request (:class:`~.recommender_service.ListRecommendationsRequest`): + request (:class:`google.cloud.recommender_v1beta1.types.ListRecommendationsRequest`): The request object. Request for the `ListRecommendations` method. parent (:class:`str`): @@ -464,6 +498,7 @@ async def list_recommendations( https://cloud.google.com/about/locations/ RECOMMENDER_ID refers to supported recommenders: https://cloud.google.com/recommender/docs/recommenders. + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -471,7 +506,8 @@ async def list_recommendations( Filter expression to restrict the recommendations returned. Supported filter fields: state_info.state Eg: \`state_info.state:"DISMISSED" or - state_info.state:"FAILED". + state_info.state:"FAILED" + This corresponds to the ``filter`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -483,8 +519,8 @@ async def list_recommendations( sent along with the request as metadata. Returns: - ~.pagers.ListRecommendationsAsyncPager: - Response to the ``ListRecommendations`` method. + google.cloud.recommender_v1beta1.services.recommender.pagers.ListRecommendationsAsyncPager: + Response to the ListRecommendations method. Iterating over this object will yield results and resolve additional pages automatically. @@ -521,6 +557,7 @@ async def list_recommendations( predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=60.0, ), default_timeout=60.0, client_info=DEFAULT_CLIENT_INFO, @@ -557,7 +594,7 @@ async def get_recommendation( recommender.*.get IAM permission for the specified recommender. Args: - request (:class:`~.recommender_service.GetRecommendationRequest`): + request (:class:`google.cloud.recommender_v1beta1.types.GetRecommendationRequest`): The request object. Request to the `GetRecommendation` method. name (:class:`str`): @@ -573,7 +610,7 @@ async def get_recommendation( sent along with the request as metadata. Returns: - ~.recommendation.Recommendation: + google.cloud.recommender_v1beta1.types.Recommendation: A recommendation along with a suggested action. E.g., a rightsizing recommendation for an underutilized VM, @@ -609,6 +646,7 @@ async def get_recommendation( predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=60.0, ), default_timeout=60.0, client_info=DEFAULT_CLIENT_INFO, @@ -652,7 +690,7 @@ async def mark_recommendation_claimed( specified recommender. Args: - request (:class:`~.recommender_service.MarkRecommendationClaimedRequest`): + request (:class:`google.cloud.recommender_v1beta1.types.MarkRecommendationClaimedRequest`): The request object. Request for the `MarkRecommendationClaimed` Method. name (:class:`str`): @@ -660,11 +698,12 @@ async def mark_recommendation_claimed( This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - state_metadata (:class:`Sequence[~.recommender_service.MarkRecommendationClaimedRequest.StateMetadataEntry]`): + state_metadata (:class:`Sequence[google.cloud.recommender_v1beta1.types.MarkRecommendationClaimedRequest.StateMetadataEntry]`): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex ``/^[a-zA-Z0-9_./-]{0,255}$/``. + This corresponds to the ``state_metadata`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -672,6 +711,7 @@ async def mark_recommendation_claimed( Required. Fingerprint of the Recommendation. Provides optimistic locking. + This corresponds to the ``etag`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -683,7 +723,7 @@ async def mark_recommendation_claimed( sent along with the request as metadata. Returns: - ~.recommendation.Recommendation: + google.cloud.recommender_v1beta1.types.Recommendation: A recommendation along with a suggested action. E.g., a rightsizing recommendation for an underutilized VM, @@ -759,7 +799,7 @@ async def mark_recommendation_succeeded( specified recommender. Args: - request (:class:`~.recommender_service.MarkRecommendationSucceededRequest`): + request (:class:`google.cloud.recommender_v1beta1.types.MarkRecommendationSucceededRequest`): The request object. Request for the `MarkRecommendationSucceeded` Method. name (:class:`str`): @@ -767,11 +807,12 @@ async def mark_recommendation_succeeded( This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - state_metadata (:class:`Sequence[~.recommender_service.MarkRecommendationSucceededRequest.StateMetadataEntry]`): + state_metadata (:class:`Sequence[google.cloud.recommender_v1beta1.types.MarkRecommendationSucceededRequest.StateMetadataEntry]`): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex ``/^[a-zA-Z0-9_./-]{0,255}$/``. + This corresponds to the ``state_metadata`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -779,6 +820,7 @@ async def mark_recommendation_succeeded( Required. Fingerprint of the Recommendation. Provides optimistic locking. + This corresponds to the ``etag`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -790,7 +832,7 @@ async def mark_recommendation_succeeded( sent along with the request as metadata. Returns: - ~.recommendation.Recommendation: + google.cloud.recommender_v1beta1.types.Recommendation: A recommendation along with a suggested action. E.g., a rightsizing recommendation for an underutilized VM, @@ -866,7 +908,7 @@ async def mark_recommendation_failed( specified recommender. Args: - request (:class:`~.recommender_service.MarkRecommendationFailedRequest`): + request (:class:`google.cloud.recommender_v1beta1.types.MarkRecommendationFailedRequest`): The request object. Request for the `MarkRecommendationFailed` Method. name (:class:`str`): @@ -874,11 +916,12 @@ async def mark_recommendation_failed( This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - state_metadata (:class:`Sequence[~.recommender_service.MarkRecommendationFailedRequest.StateMetadataEntry]`): + state_metadata (:class:`Sequence[google.cloud.recommender_v1beta1.types.MarkRecommendationFailedRequest.StateMetadataEntry]`): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex ``/^[a-zA-Z0-9_./-]{0,255}$/``. + This corresponds to the ``state_metadata`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -886,6 +929,7 @@ async def mark_recommendation_failed( Required. Fingerprint of the Recommendation. Provides optimistic locking. + This corresponds to the ``etag`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -897,7 +941,7 @@ async def mark_recommendation_failed( sent along with the request as metadata. Returns: - ~.recommendation.Recommendation: + google.cloud.recommender_v1beta1.types.Recommendation: A recommendation along with a suggested action. E.g., a rightsizing recommendation for an underutilized VM, diff --git a/google/cloud/recommender_v1beta1/services/recommender/client.py b/google/cloud/recommender_v1beta1/services/recommender/client.py index e3ea7d3..901efec 100644 --- a/google/cloud/recommender_v1beta1/services/recommender/client.py +++ b/google/cloud/recommender_v1beta1/services/recommender/client.py @@ -118,6 +118,22 @@ def _get_default_mtls_endpoint(api_endpoint): DEFAULT_ENDPOINT ) + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + RecommenderClient: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_info(info) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + @classmethod def from_service_account_file(cls, filename: str, *args, **kwargs): """Creates an instance of this client using the provided credentials @@ -130,7 +146,7 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): kwargs: Additional arguments to pass to the constructor. Returns: - {@api.name}: The constructed client. + RecommenderClient: The constructed client. """ credentials = service_account.Credentials.from_service_account_file(filename) kwargs["credentials"] = credentials @@ -296,10 +312,10 @@ def __init__( credentials identify the application to the service; if none are specified, the client will attempt to ascertain the credentials from the environment. - transport (Union[str, ~.RecommenderTransport]): The + transport (Union[str, RecommenderTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (client_options_lib.ClientOptions): Custom options for the + client_options (google.api_core.client_options.ClientOptions): Custom options for the client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT @@ -335,21 +351,17 @@ def __init__( util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) ) - ssl_credentials = None + client_cert_source_func = None is_mtls = False if use_client_cert: if client_options.client_cert_source: - import grpc # type: ignore - - cert, key = client_options.client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) is_mtls = True + client_cert_source_func = client_options.client_cert_source else: - creds = SslCredentials() - is_mtls = creds.is_mtls - ssl_credentials = creds.ssl_credentials if is_mtls else None + is_mtls = mtls.has_default_client_cert_source() + client_cert_source_func = ( + mtls.default_client_cert_source() if is_mtls else None + ) # Figure out which api endpoint to use. if client_options.api_endpoint is not None: @@ -392,7 +404,7 @@ def __init__( credentials_file=client_options.credentials_file, host=api_endpoint, scopes=client_options.scopes, - ssl_channel_credentials=ssl_credentials, + client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, ) @@ -411,10 +423,10 @@ def list_insights( type. Args: - request (:class:`~.recommender_service.ListInsightsRequest`): + request (google.cloud.recommender_v1beta1.types.ListInsightsRequest): The request object. Request for the `ListInsights` method. - parent (:class:`str`): + parent (str): Required. The container resource on which to execute the request. Acceptable formats: @@ -426,6 +438,7 @@ def list_insights( https://cloud.google.com/about/locations/ INSIGHT_TYPE_ID refers to supported insight types: https://cloud.google.com/recommender/docs/insights/insight-types. + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -437,8 +450,8 @@ def list_insights( sent along with the request as metadata. Returns: - ~.pagers.ListInsightsPager: - Response to the ``ListInsights`` method. + google.cloud.recommender_v1beta1.services.recommender.pagers.ListInsightsPager: + Response to the ListInsights method. Iterating over this object will yield results and resolve additional pages automatically. @@ -502,9 +515,9 @@ def get_insight( permission for the specified insight type. Args: - request (:class:`~.recommender_service.GetInsightRequest`): + request (google.cloud.recommender_v1beta1.types.GetInsightRequest): The request object. Request to the `GetInsight` method. - name (:class:`str`): + name (str): Required. Name of the insight. This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this @@ -517,7 +530,7 @@ def get_insight( sent along with the request as metadata. Returns: - ~.insight.Insight: + google.cloud.recommender_v1beta1.types.Insight: An insight along with the information used to derive the insight. The insight may have associated recomendations as @@ -586,23 +599,25 @@ def mark_insight_accepted( specified insight. Args: - request (:class:`~.recommender_service.MarkInsightAcceptedRequest`): + request (google.cloud.recommender_v1beta1.types.MarkInsightAcceptedRequest): The request object. Request for the `MarkInsightAccepted` method. - name (:class:`str`): + name (str): Required. Name of the insight. This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - state_metadata (:class:`Sequence[~.recommender_service.MarkInsightAcceptedRequest.StateMetadataEntry]`): + state_metadata (Sequence[google.cloud.recommender_v1beta1.types.MarkInsightAcceptedRequest.StateMetadataEntry]): Optional. State properties user wish to include with this state. Full replace of the current state_metadata. + This corresponds to the ``state_metadata`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - etag (:class:`str`): + etag (str): Required. Fingerprint of the Insight. Provides optimistic locking. + This corresponds to the ``etag`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -614,7 +629,7 @@ def mark_insight_accepted( sent along with the request as metadata. Returns: - ~.insight.Insight: + google.cloud.recommender_v1beta1.types.Insight: An insight along with the information used to derive the insight. The insight may have associated recomendations as @@ -643,12 +658,11 @@ def mark_insight_accepted( if name is not None: request.name = name + if state_metadata is not None: + request.state_metadata = state_metadata if etag is not None: request.etag = etag - if state_metadata: - request.state_metadata.update(state_metadata) - # Wrap the RPC method; this adds retry and timeout information, # and friendly error handling. rpc = self._transport._wrapped_methods[self._transport.mark_insight_accepted] @@ -679,10 +693,10 @@ def list_recommendations( recommender.*.list IAM permission for the specified recommender. Args: - request (:class:`~.recommender_service.ListRecommendationsRequest`): + request (google.cloud.recommender_v1beta1.types.ListRecommendationsRequest): The request object. Request for the `ListRecommendations` method. - parent (:class:`str`): + parent (str): Required. The container resource on which to execute the request. Acceptable formats: @@ -694,14 +708,16 @@ def list_recommendations( https://cloud.google.com/about/locations/ RECOMMENDER_ID refers to supported recommenders: https://cloud.google.com/recommender/docs/recommenders. + This corresponds to the ``parent`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - filter (:class:`str`): + filter (str): Filter expression to restrict the recommendations returned. Supported filter fields: state_info.state Eg: \`state_info.state:"DISMISSED" or - state_info.state:"FAILED". + state_info.state:"FAILED" + This corresponds to the ``filter`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -713,8 +729,8 @@ def list_recommendations( sent along with the request as metadata. Returns: - ~.pagers.ListRecommendationsPager: - Response to the ``ListRecommendations`` method. + google.cloud.recommender_v1beta1.services.recommender.pagers.ListRecommendationsPager: + Response to the ListRecommendations method. Iterating over this object will yield results and resolve additional pages automatically. @@ -780,10 +796,10 @@ def get_recommendation( recommender.*.get IAM permission for the specified recommender. Args: - request (:class:`~.recommender_service.GetRecommendationRequest`): + request (google.cloud.recommender_v1beta1.types.GetRecommendationRequest): The request object. Request to the `GetRecommendation` method. - name (:class:`str`): + name (str): Required. Name of the recommendation. This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this @@ -796,7 +812,7 @@ def get_recommendation( sent along with the request as metadata. Returns: - ~.recommendation.Recommendation: + google.cloud.recommender_v1beta1.types.Recommendation: A recommendation along with a suggested action. E.g., a rightsizing recommendation for an underutilized VM, @@ -868,26 +884,28 @@ def mark_recommendation_claimed( specified recommender. Args: - request (:class:`~.recommender_service.MarkRecommendationClaimedRequest`): + request (google.cloud.recommender_v1beta1.types.MarkRecommendationClaimedRequest): The request object. Request for the `MarkRecommendationClaimed` Method. - name (:class:`str`): + name (str): Required. Name of the recommendation. This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - state_metadata (:class:`Sequence[~.recommender_service.MarkRecommendationClaimedRequest.StateMetadataEntry]`): + state_metadata (Sequence[google.cloud.recommender_v1beta1.types.MarkRecommendationClaimedRequest.StateMetadataEntry]): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex ``/^[a-zA-Z0-9_./-]{0,255}$/``. + This corresponds to the ``state_metadata`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - etag (:class:`str`): + etag (str): Required. Fingerprint of the Recommendation. Provides optimistic locking. + This corresponds to the ``etag`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -899,7 +917,7 @@ def mark_recommendation_claimed( sent along with the request as metadata. Returns: - ~.recommendation.Recommendation: + google.cloud.recommender_v1beta1.types.Recommendation: A recommendation along with a suggested action. E.g., a rightsizing recommendation for an underutilized VM, @@ -930,12 +948,11 @@ def mark_recommendation_claimed( if name is not None: request.name = name + if state_metadata is not None: + request.state_metadata = state_metadata if etag is not None: request.etag = etag - if state_metadata: - request.state_metadata.update(state_metadata) - # Wrap the RPC method; this adds retry and timeout information, # and friendly error handling. rpc = self._transport._wrapped_methods[ @@ -980,26 +997,28 @@ def mark_recommendation_succeeded( specified recommender. Args: - request (:class:`~.recommender_service.MarkRecommendationSucceededRequest`): + request (google.cloud.recommender_v1beta1.types.MarkRecommendationSucceededRequest): The request object. Request for the `MarkRecommendationSucceeded` Method. - name (:class:`str`): + name (str): Required. Name of the recommendation. This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - state_metadata (:class:`Sequence[~.recommender_service.MarkRecommendationSucceededRequest.StateMetadataEntry]`): + state_metadata (Sequence[google.cloud.recommender_v1beta1.types.MarkRecommendationSucceededRequest.StateMetadataEntry]): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex ``/^[a-zA-Z0-9_./-]{0,255}$/``. + This corresponds to the ``state_metadata`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - etag (:class:`str`): + etag (str): Required. Fingerprint of the Recommendation. Provides optimistic locking. + This corresponds to the ``etag`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -1011,7 +1030,7 @@ def mark_recommendation_succeeded( sent along with the request as metadata. Returns: - ~.recommendation.Recommendation: + google.cloud.recommender_v1beta1.types.Recommendation: A recommendation along with a suggested action. E.g., a rightsizing recommendation for an underutilized VM, @@ -1042,12 +1061,11 @@ def mark_recommendation_succeeded( if name is not None: request.name = name + if state_metadata is not None: + request.state_metadata = state_metadata if etag is not None: request.etag = etag - if state_metadata: - request.state_metadata.update(state_metadata) - # Wrap the RPC method; this adds retry and timeout information, # and friendly error handling. rpc = self._transport._wrapped_methods[ @@ -1092,26 +1110,28 @@ def mark_recommendation_failed( specified recommender. Args: - request (:class:`~.recommender_service.MarkRecommendationFailedRequest`): + request (google.cloud.recommender_v1beta1.types.MarkRecommendationFailedRequest): The request object. Request for the `MarkRecommendationFailed` Method. - name (:class:`str`): + name (str): Required. Name of the recommendation. This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - state_metadata (:class:`Sequence[~.recommender_service.MarkRecommendationFailedRequest.StateMetadataEntry]`): + state_metadata (Sequence[google.cloud.recommender_v1beta1.types.MarkRecommendationFailedRequest.StateMetadataEntry]): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex ``/^[a-zA-Z0-9_./-]{0,255}$/``. + This corresponds to the ``state_metadata`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - etag (:class:`str`): + etag (str): Required. Fingerprint of the Recommendation. Provides optimistic locking. + This corresponds to the ``etag`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -1123,7 +1143,7 @@ def mark_recommendation_failed( sent along with the request as metadata. Returns: - ~.recommendation.Recommendation: + google.cloud.recommender_v1beta1.types.Recommendation: A recommendation along with a suggested action. E.g., a rightsizing recommendation for an underutilized VM, @@ -1152,12 +1172,11 @@ def mark_recommendation_failed( if name is not None: request.name = name + if state_metadata is not None: + request.state_metadata = state_metadata if etag is not None: request.etag = etag - if state_metadata: - request.state_metadata.update(state_metadata) - # Wrap the RPC method; this adds retry and timeout information, # and friendly error handling. rpc = self._transport._wrapped_methods[ diff --git a/google/cloud/recommender_v1beta1/services/recommender/pagers.py b/google/cloud/recommender_v1beta1/services/recommender/pagers.py index cbcf8d9..57ced80 100644 --- a/google/cloud/recommender_v1beta1/services/recommender/pagers.py +++ b/google/cloud/recommender_v1beta1/services/recommender/pagers.py @@ -15,7 +15,16 @@ # limitations under the License. # -from typing import Any, AsyncIterable, Awaitable, Callable, Iterable, Sequence, Tuple +from typing import ( + Any, + AsyncIterable, + Awaitable, + Callable, + Iterable, + Sequence, + Tuple, + Optional, +) from google.cloud.recommender_v1beta1.types import insight from google.cloud.recommender_v1beta1.types import recommendation @@ -26,7 +35,7 @@ class ListInsightsPager: """A pager for iterating through ``list_insights`` requests. This class thinly wraps an initial - :class:`~.recommender_service.ListInsightsResponse` object, and + :class:`google.cloud.recommender_v1beta1.types.ListInsightsResponse` object, and provides an ``__iter__`` method to iterate through its ``insights`` field. @@ -35,7 +44,7 @@ class ListInsightsPager: through the ``insights`` field on the corresponding responses. - All the usual :class:`~.recommender_service.ListInsightsResponse` + All the usual :class:`google.cloud.recommender_v1beta1.types.ListInsightsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -53,9 +62,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.recommender_service.ListInsightsRequest`): + request (google.cloud.recommender_v1beta1.types.ListInsightsRequest): The initial request object. - response (:class:`~.recommender_service.ListInsightsResponse`): + response (google.cloud.recommender_v1beta1.types.ListInsightsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. @@ -88,7 +97,7 @@ class ListInsightsAsyncPager: """A pager for iterating through ``list_insights`` requests. This class thinly wraps an initial - :class:`~.recommender_service.ListInsightsResponse` object, and + :class:`google.cloud.recommender_v1beta1.types.ListInsightsResponse` object, and provides an ``__aiter__`` method to iterate through its ``insights`` field. @@ -97,7 +106,7 @@ class ListInsightsAsyncPager: through the ``insights`` field on the corresponding responses. - All the usual :class:`~.recommender_service.ListInsightsResponse` + All the usual :class:`google.cloud.recommender_v1beta1.types.ListInsightsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -115,9 +124,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.recommender_service.ListInsightsRequest`): + request (google.cloud.recommender_v1beta1.types.ListInsightsRequest): The initial request object. - response (:class:`~.recommender_service.ListInsightsResponse`): + response (google.cloud.recommender_v1beta1.types.ListInsightsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. @@ -154,7 +163,7 @@ class ListRecommendationsPager: """A pager for iterating through ``list_recommendations`` requests. This class thinly wraps an initial - :class:`~.recommender_service.ListRecommendationsResponse` object, and + :class:`google.cloud.recommender_v1beta1.types.ListRecommendationsResponse` object, and provides an ``__iter__`` method to iterate through its ``recommendations`` field. @@ -163,7 +172,7 @@ class ListRecommendationsPager: through the ``recommendations`` field on the corresponding responses. - All the usual :class:`~.recommender_service.ListRecommendationsResponse` + All the usual :class:`google.cloud.recommender_v1beta1.types.ListRecommendationsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -181,9 +190,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.recommender_service.ListRecommendationsRequest`): + request (google.cloud.recommender_v1beta1.types.ListRecommendationsRequest): The initial request object. - response (:class:`~.recommender_service.ListRecommendationsResponse`): + response (google.cloud.recommender_v1beta1.types.ListRecommendationsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. @@ -216,7 +225,7 @@ class ListRecommendationsAsyncPager: """A pager for iterating through ``list_recommendations`` requests. This class thinly wraps an initial - :class:`~.recommender_service.ListRecommendationsResponse` object, and + :class:`google.cloud.recommender_v1beta1.types.ListRecommendationsResponse` object, and provides an ``__aiter__`` method to iterate through its ``recommendations`` field. @@ -225,7 +234,7 @@ class ListRecommendationsAsyncPager: through the ``recommendations`` field on the corresponding responses. - All the usual :class:`~.recommender_service.ListRecommendationsResponse` + All the usual :class:`google.cloud.recommender_v1beta1.types.ListRecommendationsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -245,9 +254,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.recommender_service.ListRecommendationsRequest`): + request (google.cloud.recommender_v1beta1.types.ListRecommendationsRequest): The initial request object. - response (:class:`~.recommender_service.ListRecommendationsResponse`): + response (google.cloud.recommender_v1beta1.types.ListRecommendationsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. diff --git a/google/cloud/recommender_v1beta1/services/recommender/transports/base.py b/google/cloud/recommender_v1beta1/services/recommender/transports/base.py index 00e2b76..28e9cc2 100644 --- a/google/cloud/recommender_v1beta1/services/recommender/transports/base.py +++ b/google/cloud/recommender_v1beta1/services/recommender/transports/base.py @@ -71,10 +71,10 @@ def __init__( scope (Optional[Sequence[str]]): A list of scopes. quota_project_id (Optional[str]): An optional project to use for billing and quota. - client_info (google.api_core.gapic_v1.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 + client_info (google.api_core.gapic_v1.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 client library. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. @@ -82,6 +82,9 @@ def __init__( host += ":443" self._host = host + # Save the scopes. + self._scopes = scopes or self.AUTH_SCOPES + # If no credentials are provided, then determine the appropriate # defaults. if credentials and credentials_file: @@ -91,20 +94,17 @@ def __init__( if credentials_file is not None: credentials, _ = auth.load_credentials_from_file( - credentials_file, scopes=scopes, quota_project_id=quota_project_id + credentials_file, scopes=self._scopes, quota_project_id=quota_project_id ) elif credentials is None: credentials, _ = auth.default( - scopes=scopes, quota_project_id=quota_project_id + scopes=self._scopes, quota_project_id=quota_project_id ) # Save the credentials. self._credentials = credentials - # Lifted into its own function so it can be stubbed out during tests. - self._prep_wrapped_messages(client_info) - def _prep_wrapped_messages(self, client_info): # Precompute the wrapped methods. self._wrapped_methods = { @@ -117,6 +117,7 @@ def _prep_wrapped_messages(self, client_info): predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=60.0, ), default_timeout=60.0, client_info=client_info, @@ -130,6 +131,7 @@ def _prep_wrapped_messages(self, client_info): predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=60.0, ), default_timeout=60.0, client_info=client_info, @@ -148,6 +150,7 @@ def _prep_wrapped_messages(self, client_info): predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=60.0, ), default_timeout=60.0, client_info=client_info, @@ -161,6 +164,7 @@ def _prep_wrapped_messages(self, client_info): predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=60.0, ), default_timeout=60.0, client_info=client_info, diff --git a/google/cloud/recommender_v1beta1/services/recommender/transports/grpc.py b/google/cloud/recommender_v1beta1/services/recommender/transports/grpc.py index 10c3583..e8d4502 100644 --- a/google/cloud/recommender_v1beta1/services/recommender/transports/grpc.py +++ b/google/cloud/recommender_v1beta1/services/recommender/transports/grpc.py @@ -63,6 +63,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -93,6 +94,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -107,72 +112,60 @@ def __init__( google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` and ``credentials_file`` are passed. """ + self._grpc_channel = None self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) if channel: - # Sanity check: Ensure that channel and credentials are not both - # provided. + # Ignore credentials if a channel was passed. credentials = False - # If a channel was explicitly provided, set it. self._grpc_channel = channel self._ssl_channel_credentials = None - elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( - api_mtls_endpoint - if ":" in api_mtls_endpoint - else api_mtls_endpoint + ":443" - ) + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) - - # Create SSL credentials with client_cert_source or application - # default SSL credentials. - if client_cert_source: - cert, key = client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) else: - ssl_credentials = SslCredentials().ssl_credentials - - # create a new channel. The provided one is ignored. - self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, - credentials_file=credentials_file, - ssl_credentials=ssl_credentials, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - options=[ - ("grpc.max_send_message_length", -1), - ("grpc.max_receive_message_length", -1), - ], - ) - self._ssl_channel_credentials = ssl_credentials - else: - host = host if ":" in host else host + ":443" + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) + # The base transport sets the host, credentials and scopes + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + client_info=client_info, + ) - # create a new channel. The provided one is ignored. + if not self._grpc_channel: self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, + self._host, + credentials=self._credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, - scopes=scopes or self.AUTH_SCOPES, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, options=[ ("grpc.max_send_message_length", -1), @@ -180,17 +173,8 @@ def __init__( ], ) - self._stubs = {} # type: Dict[str, Callable] - - # Run the base constructor. - super().__init__( - host=host, - credentials=credentials, - credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - client_info=client_info, - ) + # Wrap messages. This must be done after self._grpc_channel exists + self._prep_wrapped_messages(client_info) @classmethod def create_channel( @@ -204,7 +188,7 @@ def create_channel( ) -> grpc.Channel: """Create and return a gRPC channel object. Args: - address (Optional[str]): The host for the channel to use. + host (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If diff --git a/google/cloud/recommender_v1beta1/services/recommender/transports/grpc_asyncio.py b/google/cloud/recommender_v1beta1/services/recommender/transports/grpc_asyncio.py index 3f4fb2b..a99ea36 100644 --- a/google/cloud/recommender_v1beta1/services/recommender/transports/grpc_asyncio.py +++ b/google/cloud/recommender_v1beta1/services/recommender/transports/grpc_asyncio.py @@ -67,7 +67,7 @@ def create_channel( ) -> aio.Channel: """Create and return a gRPC AsyncIO channel object. Args: - address (Optional[str]): The host for the channel to use. + host (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If @@ -107,6 +107,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -138,12 +139,16 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. - client_info (google.api_core.gapic_v1.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 + client_info (google.api_core.gapic_v1.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 client library. Raises: @@ -152,72 +157,60 @@ def __init__( google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` and ``credentials_file`` are passed. """ + self._grpc_channel = None self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) if channel: - # Sanity check: Ensure that channel and credentials are not both - # provided. + # Ignore credentials if a channel was passed. credentials = False - # If a channel was explicitly provided, set it. self._grpc_channel = channel self._ssl_channel_credentials = None - elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( - api_mtls_endpoint - if ":" in api_mtls_endpoint - else api_mtls_endpoint + ":443" - ) + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) - - # Create SSL credentials with client_cert_source or application - # default SSL credentials. - if client_cert_source: - cert, key = client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) else: - ssl_credentials = SslCredentials().ssl_credentials - - # create a new channel. The provided one is ignored. - self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, - credentials_file=credentials_file, - ssl_credentials=ssl_credentials, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - options=[ - ("grpc.max_send_message_length", -1), - ("grpc.max_receive_message_length", -1), - ], - ) - self._ssl_channel_credentials = ssl_credentials - else: - host = host if ":" in host else host + ":443" + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) + # The base transport sets the host, credentials and scopes + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + client_info=client_info, + ) - # create a new channel. The provided one is ignored. + if not self._grpc_channel: self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, + self._host, + credentials=self._credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, - scopes=scopes or self.AUTH_SCOPES, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, options=[ ("grpc.max_send_message_length", -1), @@ -225,17 +218,8 @@ def __init__( ], ) - # Run the base constructor. - super().__init__( - host=host, - credentials=credentials, - credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - client_info=client_info, - ) - - self._stubs = {} + # Wrap messages. This must be done after self._grpc_channel exists + self._prep_wrapped_messages(client_info) @property def grpc_channel(self) -> aio.Channel: diff --git a/google/cloud/recommender_v1beta1/types/__init__.py b/google/cloud/recommender_v1beta1/types/__init__.py index f75c4f2..395fe1a 100644 --- a/google/cloud/recommender_v1beta1/types/__init__.py +++ b/google/cloud/recommender_v1beta1/types/__init__.py @@ -20,47 +20,47 @@ InsightStateInfo, ) from .recommendation import ( - Recommendation, - RecommendationContent, - OperationGroup, - Operation, - ValueMatcher, CostProjection, Impact, + Operation, + OperationGroup, + Recommendation, + RecommendationContent, RecommendationStateInfo, + ValueMatcher, ) from .recommender_service import ( + GetInsightRequest, + GetRecommendationRequest, ListInsightsRequest, ListInsightsResponse, - GetInsightRequest, - MarkInsightAcceptedRequest, ListRecommendationsRequest, ListRecommendationsResponse, - GetRecommendationRequest, + MarkInsightAcceptedRequest, MarkRecommendationClaimedRequest, - MarkRecommendationSucceededRequest, MarkRecommendationFailedRequest, + MarkRecommendationSucceededRequest, ) __all__ = ( "Insight", "InsightStateInfo", - "Recommendation", - "RecommendationContent", - "OperationGroup", - "Operation", - "ValueMatcher", "CostProjection", "Impact", + "Operation", + "OperationGroup", + "Recommendation", + "RecommendationContent", "RecommendationStateInfo", + "ValueMatcher", + "GetInsightRequest", + "GetRecommendationRequest", "ListInsightsRequest", "ListInsightsResponse", - "GetInsightRequest", - "MarkInsightAcceptedRequest", "ListRecommendationsRequest", "ListRecommendationsResponse", - "GetRecommendationRequest", + "MarkInsightAcceptedRequest", "MarkRecommendationClaimedRequest", - "MarkRecommendationSucceededRequest", "MarkRecommendationFailedRequest", + "MarkRecommendationSucceededRequest", ) diff --git a/google/cloud/recommender_v1beta1/types/insight.py b/google/cloud/recommender_v1beta1/types/insight.py index d842980..8f76281 100644 --- a/google/cloud/recommender_v1beta1/types/insight.py +++ b/google/cloud/recommender_v1beta1/types/insight.py @@ -45,25 +45,25 @@ class Insight(proto.Message): insight_subtype (str): Insight subtype. Insight content schema will be stable for a given subtype. - content (~.struct.Struct): + content (google.protobuf.struct_pb2.Struct): A struct of custom fields to explain the insight. Example: "grantedPermissionsCount": "1000". - last_refresh_time (~.timestamp.Timestamp): + last_refresh_time (google.protobuf.timestamp_pb2.Timestamp): Timestamp of the latest data used to generate the insight. - observation_period (~.duration.Duration): + observation_period (google.protobuf.duration_pb2.Duration): Observation period that led to the insight. The source data used to generate the insight ends at last_refresh_time and begins at (last_refresh_time - observation_period). - state_info (~.insight.InsightStateInfo): + state_info (google.cloud.recommender_v1beta1.types.InsightStateInfo): Information state and metadata. - category (~.insight.Insight.Category): + category (google.cloud.recommender_v1beta1.types.Insight.Category): Category being targeted by the insight. etag (str): Fingerprint of the Insight. Provides optimistic locking when updating states. - associated_recommendations (Sequence[~.insight.Insight.RecommendationReference]): + associated_recommendations (Sequence[google.cloud.recommender_v1beta1.types.Insight.RecommendationReference]): Recommendations derived from this insight. """ @@ -119,9 +119,9 @@ class InsightStateInfo(proto.Message): r"""Information related to insight state. Attributes: - state (~.insight.InsightStateInfo.State): + state (google.cloud.recommender_v1beta1.types.InsightStateInfo.State): Insight state. - state_metadata (Sequence[~.insight.InsightStateInfo.StateMetadataEntry]): + state_metadata (Sequence[google.cloud.recommender_v1beta1.types.InsightStateInfo.StateMetadataEntry]): A map of metadata for the state, provided by user or automations systems. """ diff --git a/google/cloud/recommender_v1beta1/types/recommendation.py b/google/cloud/recommender_v1beta1/types/recommendation.py index f05060f..d3a1dd7 100644 --- a/google/cloud/recommender_v1beta1/types/recommendation.py +++ b/google/cloud/recommender_v1beta1/types/recommendation.py @@ -62,29 +62,29 @@ class Recommendation(proto.Message): Examples: For recommender = "google.iam.policy.Recommender", recommender_subtype can be one of "REMOVE_ROLE"/"REPLACE_ROLE". - last_refresh_time (~.timestamp.Timestamp): + last_refresh_time (google.protobuf.timestamp_pb2.Timestamp): Last time this recommendation was refreshed by the system that created it in the first place. - primary_impact (~.recommendation.Impact): + primary_impact (google.cloud.recommender_v1beta1.types.Impact): The primary impact that this recommendation can have while trying to optimize for one category. - additional_impact (Sequence[~.recommendation.Impact]): + additional_impact (Sequence[google.cloud.recommender_v1beta1.types.Impact]): Optional set of additional impact that this recommendation may have when trying to optimize for the primary category. These may be positive or negative. - content (~.recommendation.RecommendationContent): + content (google.cloud.recommender_v1beta1.types.RecommendationContent): Content of the recommendation describing recommended changes to resources. - state_info (~.recommendation.RecommendationStateInfo): + state_info (google.cloud.recommender_v1beta1.types.RecommendationStateInfo): Information for state. Contains state and metadata. etag (str): Fingerprint of the Recommendation. Provides optimistic locking when updating states. - associated_insights (Sequence[~.recommendation.Recommendation.InsightReference]): + associated_insights (Sequence[google.cloud.recommender_v1beta1.types.Recommendation.InsightReference]): Insights that led to this recommendation. """ @@ -131,7 +131,7 @@ class RecommendationContent(proto.Message): changing. Attributes: - operation_groups (Sequence[~.recommendation.OperationGroup]): + operation_groups (Sequence[google.cloud.recommender_v1beta1.types.OperationGroup]): Operations to one or more Google Cloud resources grouped in such a way that, all operations within one group are expected to be @@ -147,7 +147,7 @@ class OperationGroup(proto.Message): r"""Group of operations that need to be performed atomically. Attributes: - operations (Sequence[~.recommendation.Operation]): + operations (Sequence[google.cloud.recommender_v1beta1.types.Operation]): List of operations across one or more resources that belong to this group. Loosely based on RFC6902 and should be performed in the @@ -199,16 +199,16 @@ class Operation(proto.Message): Can be set with action 'copy' or 'move' to indicate the source field within resource or source_resource, ignored if provided for other operation types. - value (~.struct.Value): + value (google.protobuf.struct_pb2.Value): Value for the ``path`` field. Will be set for actions:'add'/'replace'. Maybe set for action: 'test'. Either this or ``value_matcher`` will be set for 'test' operation. An exact match must be performed. - value_matcher (~.recommendation.ValueMatcher): + value_matcher (google.cloud.recommender_v1beta1.types.ValueMatcher): Can be set for action 'test' for advanced matching for the value of 'path' field. Either this or ``value`` will be set for 'test' operation. - path_filters (Sequence[~.recommendation.Operation.PathFiltersEntry]): + path_filters (Sequence[google.cloud.recommender_v1beta1.types.Operation.PathFiltersEntry]): Set of filters to apply if ``path`` refers to array elements or nested array elements in order to narrow down to a single unique element that is being tested/modified. This is @@ -224,7 +224,7 @@ class Operation(proto.Message): "y@example.com"] }`` When both path_filters and path_value_matchers are set, an implicit AND must be performed. - path_value_matchers (Sequence[~.recommendation.Operation.PathValueMatchersEntry]): + path_value_matchers (Sequence[google.cloud.recommender_v1beta1.types.Operation.PathValueMatchersEntry]): Similar to path_filters, this contains set of filters to apply if ``path`` field referes to array elements. This is meant to support value matching beyond exact match. To @@ -283,13 +283,13 @@ class CostProjection(proto.Message): save or incur. Attributes: - cost (~.money.Money): + cost (google.type.money_pb2.Money): An approximate projection on amount saved or amount incurred. Negative cost units indicate cost savings and positive cost units indicate increase. See google.type.Money documentation for positive/negative units. - duration (~.gp_duration.Duration): + duration (google.protobuf.duration_pb2.Duration): Duration for which this cost applies. """ @@ -303,9 +303,9 @@ class Impact(proto.Message): category. Attributes: - category (~.recommendation.Impact.Category): + category (google.cloud.recommender_v1beta1.types.Impact.Category): Category that is being targeted. - cost_projection (~.recommendation.CostProjection): + cost_projection (google.cloud.recommender_v1beta1.types.CostProjection): Use with CategoryType.COST """ @@ -328,10 +328,10 @@ class RecommendationStateInfo(proto.Message): r"""Information for state. Contains state and metadata. Attributes: - state (~.recommendation.RecommendationStateInfo.State): + state (google.cloud.recommender_v1beta1.types.RecommendationStateInfo.State): The state of the recommendation, Eg ACTIVE, SUCCEEDED, FAILED. - state_metadata (Sequence[~.recommendation.RecommendationStateInfo.StateMetadataEntry]): + state_metadata (Sequence[google.cloud.recommender_v1beta1.types.RecommendationStateInfo.StateMetadataEntry]): A map of metadata for the state, provided by user or automations systems. """ diff --git a/google/cloud/recommender_v1beta1/types/recommender_service.py b/google/cloud/recommender_v1beta1/types/recommender_service.py index 402b510..2c0fcd7 100644 --- a/google/cloud/recommender_v1beta1/types/recommender_service.py +++ b/google/cloud/recommender_v1beta1/types/recommender_service.py @@ -85,7 +85,7 @@ class ListInsightsResponse(proto.Message): r"""Response to the ``ListInsights`` method. Attributes: - insights (Sequence[~.insight.Insight]): + insights (Sequence[google.cloud.recommender_v1beta1.types.Insight]): The set of insights for the ``parent`` resource. next_page_token (str): A token that can be used to request the next @@ -119,7 +119,7 @@ class MarkInsightAcceptedRequest(proto.Message): Attributes: name (str): Required. Name of the insight. - state_metadata (Sequence[~.recommender_service.MarkInsightAcceptedRequest.StateMetadataEntry]): + state_metadata (Sequence[google.cloud.recommender_v1beta1.types.MarkInsightAcceptedRequest.StateMetadataEntry]): Optional. State properties user wish to include with this state. Full replace of the current state_metadata. etag (str): @@ -180,7 +180,7 @@ class ListRecommendationsResponse(proto.Message): r"""Response to the ``ListRecommendations`` method. Attributes: - recommendations (Sequence[~.recommendation.Recommendation]): + recommendations (Sequence[google.cloud.recommender_v1beta1.types.Recommendation]): The set of recommendations for the ``parent`` resource. next_page_token (str): A token that can be used to request the next @@ -216,7 +216,7 @@ class MarkRecommendationClaimedRequest(proto.Message): Attributes: name (str): Required. Name of the recommendation. - state_metadata (Sequence[~.recommender_service.MarkRecommendationClaimedRequest.StateMetadataEntry]): + state_metadata (Sequence[google.cloud.recommender_v1beta1.types.MarkRecommendationClaimedRequest.StateMetadataEntry]): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex @@ -239,7 +239,7 @@ class MarkRecommendationSucceededRequest(proto.Message): Attributes: name (str): Required. Name of the recommendation. - state_metadata (Sequence[~.recommender_service.MarkRecommendationSucceededRequest.StateMetadataEntry]): + state_metadata (Sequence[google.cloud.recommender_v1beta1.types.MarkRecommendationSucceededRequest.StateMetadataEntry]): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex @@ -262,7 +262,7 @@ class MarkRecommendationFailedRequest(proto.Message): Attributes: name (str): Required. Name of the recommendation. - state_metadata (Sequence[~.recommender_service.MarkRecommendationFailedRequest.StateMetadataEntry]): + state_metadata (Sequence[google.cloud.recommender_v1beta1.types.MarkRecommendationFailedRequest.StateMetadataEntry]): State properties to include with this state. Overwrites any existing ``state_metadata``. Keys must match the regex ``/^[a-z0-9][a-z0-9_.-]{0,62}$/``. Values must match the regex diff --git a/noxfile.py b/noxfile.py index 8004482..43dd302 100644 --- a/noxfile.py +++ b/noxfile.py @@ -18,6 +18,7 @@ from __future__ import absolute_import import os +import pathlib import shutil import nox @@ -30,6 +31,22 @@ SYSTEM_TEST_PYTHON_VERSIONS = ["3.8"] UNIT_TEST_PYTHON_VERSIONS = ["3.6", "3.7", "3.8", "3.9"] +CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() + +# 'docfx' is excluded since it only needs to run in 'docs-presubmit' +nox.options.sessions = [ + "unit", + "system", + "cover", + "lint", + "lint_setup_py", + "blacken", + "docs", +] + +# Error if a python version is missing +nox.options.error_on_missing_interpreters = True + @nox.session(python=DEFAULT_PYTHON_VERSION) def lint(session): @@ -70,17 +87,21 @@ def lint_setup_py(session): def default(session): # Install all test dependencies, then install this package in-place. - session.install("asyncmock", "pytest-asyncio") - session.install( - "mock", "pytest", "pytest-cov", + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) - session.install("-e", ".") + session.install("asyncmock", "pytest-asyncio", "-c", constraints_path) + + session.install("mock", "pytest", "pytest-cov", "-c", constraints_path) + + session.install("-e", ".", "-c", constraints_path) # Run py.test against the unit tests. session.run( "py.test", "--quiet", + f"--junitxml=unit_{session.python}_sponge_log.xml", "--cov=google/cloud", "--cov=tests/unit", "--cov-append", @@ -101,6 +122,9 @@ def unit(session): @nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS) def system(session): """Run the system test suite.""" + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + ) system_test_path = os.path.join("tests", "system.py") system_test_folder_path = os.path.join("tests", "system") @@ -110,6 +134,9 @@ def system(session): # Sanity check: Only run tests if the environment variable is set. if not os.environ.get("GOOGLE_APPLICATION_CREDENTIALS", ""): session.skip("Credentials must be set via environment variable") + # Install pyopenssl for mTLS testing. + if os.environ.get("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true": + session.install("pyopenssl") system_test_exists = os.path.exists(system_test_path) system_test_folder_exists = os.path.exists(system_test_folder_path) @@ -122,16 +149,26 @@ def system(session): # Install all test dependencies, then install this package into the # virtualenv's dist-packages. - session.install( - "mock", "pytest", "google-cloud-testutils", - ) - session.install("-e", ".") + session.install("mock", "pytest", "google-cloud-testutils", "-c", constraints_path) + session.install("-e", ".", "-c", constraints_path) # Run py.test against the system tests. if system_test_exists: - session.run("py.test", "--quiet", system_test_path, *session.posargs) + session.run( + "py.test", + "--quiet", + f"--junitxml=system_{session.python}_sponge_log.xml", + system_test_path, + *session.posargs, + ) if system_test_folder_exists: - session.run("py.test", "--quiet", system_test_folder_path, *session.posargs) + session.run( + "py.test", + "--quiet", + f"--junitxml=system_{session.python}_sponge_log.xml", + system_test_folder_path, + *session.posargs, + ) @nox.session(python=DEFAULT_PYTHON_VERSION) @@ -142,7 +179,7 @@ def cover(session): test runs (not system test runs), and then erases coverage data. """ session.install("coverage", "pytest-cov") - session.run("coverage", "report", "--show-missing", "--fail-under=100") + session.run("coverage", "report", "--show-missing", "--fail-under=98") session.run("coverage", "erase") diff --git a/renovate.json b/renovate.json index 4fa9493..f08bc22 100644 --- a/renovate.json +++ b/renovate.json @@ -1,5 +1,6 @@ { "extends": [ "config:base", ":preserveSemverRanges" - ] + ], + "ignorePaths": [".pre-commit-config.yaml"] } diff --git a/setup.py b/setup.py index e90242a..2482921 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ description = "Cloud Recommender API client library" version = "2.1.0" release_status = "Development Status :: 5 - Production/Stable" -dependencies = ["google-api-core[grpc] >= 1.22.0, < 2.0.0dev", "proto-plus >= 1.10.0"] +dependencies = ["google-api-core[grpc] >= 1.22.2, < 2.0.0dev", "proto-plus >= 1.10.0"] extras = {"libcst": "libcst >= 0.2.5"} scripts = [ "scripts/fixup_recommender_v1_keywords.py", diff --git a/synth.metadata b/synth.metadata index b870cc8..1c6bc7b 100644 --- a/synth.metadata +++ b/synth.metadata @@ -3,30 +3,30 @@ { "git": { "name": ".", - "remote": "https://github.com/googleapis/python-recommender.git", - "sha": "5bb9b2c6b627e58a831be24d038f7b3f6bf55e3b" + "remote": "git@github.com:googleapis/python-recommender.git", + "sha": "9cec8cec00b3bd5f59fdebe16c65d8721af8ba0f" } }, { "git": { "name": "googleapis", "remote": "https://github.com/googleapis/googleapis.git", - "sha": "5256ab60f3d396a3d1bd393043776936b9651c5b", - "internalRef": "347703845" + "sha": "95dd24960cf9f794ef583e59ad9f1fabe1c4a924", + "internalRef": "365882072" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "373861061648b5fe5e0ac4f8a38b32d639ee93e4" + "sha": "4dc31ac1ece23dc555f574aa701e5857b5bf2d3f" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "373861061648b5fe5e0ac4f8a38b32d639ee93e4" + "sha": "4dc31ac1ece23dc555f574aa701e5857b5bf2d3f" } } ], @@ -49,116 +49,5 @@ "generator": "bazel" } } - ], - "generatedFiles": [ - ".flake8", - ".github/CONTRIBUTING.md", - ".github/ISSUE_TEMPLATE/bug_report.md", - ".github/ISSUE_TEMPLATE/feature_request.md", - ".github/ISSUE_TEMPLATE/support_request.md", - ".github/PULL_REQUEST_TEMPLATE.md", - ".github/release-please.yml", - ".github/snippet-bot.yml", - ".gitignore", - ".kokoro/build.sh", - ".kokoro/continuous/common.cfg", - ".kokoro/continuous/continuous.cfg", - ".kokoro/docker/docs/Dockerfile", - ".kokoro/docker/docs/fetch_gpg_keys.sh", - ".kokoro/docs/common.cfg", - ".kokoro/docs/docs-presubmit.cfg", - ".kokoro/docs/docs.cfg", - ".kokoro/populate-secrets.sh", - ".kokoro/presubmit/common.cfg", - ".kokoro/presubmit/presubmit.cfg", - ".kokoro/publish-docs.sh", - ".kokoro/release.sh", - ".kokoro/release/common.cfg", - ".kokoro/release/release.cfg", - ".kokoro/samples/lint/common.cfg", - ".kokoro/samples/lint/continuous.cfg", - ".kokoro/samples/lint/periodic.cfg", - ".kokoro/samples/lint/presubmit.cfg", - ".kokoro/samples/python3.6/common.cfg", - ".kokoro/samples/python3.6/continuous.cfg", - ".kokoro/samples/python3.6/periodic.cfg", - ".kokoro/samples/python3.6/presubmit.cfg", - ".kokoro/samples/python3.7/common.cfg", - ".kokoro/samples/python3.7/continuous.cfg", - ".kokoro/samples/python3.7/periodic.cfg", - ".kokoro/samples/python3.7/presubmit.cfg", - ".kokoro/samples/python3.8/common.cfg", - ".kokoro/samples/python3.8/continuous.cfg", - ".kokoro/samples/python3.8/periodic.cfg", - ".kokoro/samples/python3.8/presubmit.cfg", - ".kokoro/test-samples.sh", - ".kokoro/trampoline.sh", - ".kokoro/trampoline_v2.sh", - ".pre-commit-config.yaml", - ".trampolinerc", - "CODE_OF_CONDUCT.md", - "CONTRIBUTING.rst", - "LICENSE", - "MANIFEST.in", - "docs/_static/custom.css", - "docs/_templates/layout.html", - "docs/conf.py", - "docs/multiprocessing.rst", - "docs/recommender_v1/services.rst", - "docs/recommender_v1/types.rst", - "docs/recommender_v1beta1/services.rst", - "docs/recommender_v1beta1/types.rst", - "google/cloud/recommender/__init__.py", - "google/cloud/recommender/py.typed", - "google/cloud/recommender_v1/__init__.py", - "google/cloud/recommender_v1/py.typed", - "google/cloud/recommender_v1/services/__init__.py", - "google/cloud/recommender_v1/services/recommender/__init__.py", - "google/cloud/recommender_v1/services/recommender/async_client.py", - "google/cloud/recommender_v1/services/recommender/client.py", - "google/cloud/recommender_v1/services/recommender/pagers.py", - "google/cloud/recommender_v1/services/recommender/transports/__init__.py", - "google/cloud/recommender_v1/services/recommender/transports/base.py", - "google/cloud/recommender_v1/services/recommender/transports/grpc.py", - "google/cloud/recommender_v1/services/recommender/transports/grpc_asyncio.py", - "google/cloud/recommender_v1/types/__init__.py", - "google/cloud/recommender_v1/types/insight.py", - "google/cloud/recommender_v1/types/recommendation.py", - "google/cloud/recommender_v1/types/recommender_service.py", - "google/cloud/recommender_v1beta1/__init__.py", - "google/cloud/recommender_v1beta1/py.typed", - "google/cloud/recommender_v1beta1/services/__init__.py", - "google/cloud/recommender_v1beta1/services/recommender/__init__.py", - "google/cloud/recommender_v1beta1/services/recommender/async_client.py", - "google/cloud/recommender_v1beta1/services/recommender/client.py", - "google/cloud/recommender_v1beta1/services/recommender/pagers.py", - "google/cloud/recommender_v1beta1/services/recommender/transports/__init__.py", - "google/cloud/recommender_v1beta1/services/recommender/transports/base.py", - "google/cloud/recommender_v1beta1/services/recommender/transports/grpc.py", - "google/cloud/recommender_v1beta1/services/recommender/transports/grpc_asyncio.py", - "google/cloud/recommender_v1beta1/types/__init__.py", - "google/cloud/recommender_v1beta1/types/insight.py", - "google/cloud/recommender_v1beta1/types/recommendation.py", - "google/cloud/recommender_v1beta1/types/recommender_service.py", - "mypy.ini", - "noxfile.py", - "renovate.json", - "samples/AUTHORING_GUIDE.md", - "samples/CONTRIBUTING.md", - "scripts/decrypt-secrets.sh", - "scripts/fixup_recommender_v1_keywords.py", - "scripts/fixup_recommender_v1beta1_keywords.py", - "scripts/readme-gen/readme_gen.py", - "scripts/readme-gen/templates/README.tmpl.rst", - "scripts/readme-gen/templates/auth.tmpl.rst", - "scripts/readme-gen/templates/auth_api_key.tmpl.rst", - "scripts/readme-gen/templates/install_deps.tmpl.rst", - "scripts/readme-gen/templates/install_portaudio.tmpl.rst", - "setup.cfg", - "testing/.gitignore", - "tests/unit/gapic/recommender_v1/__init__.py", - "tests/unit/gapic/recommender_v1/test_recommender.py", - "tests/unit/gapic/recommender_v1beta1/__init__.py", - "tests/unit/gapic/recommender_v1beta1/test_recommender.py" ] } \ No newline at end of file diff --git a/synth.py b/synth.py index 359fece..04523a5 100644 --- a/synth.py +++ b/synth.py @@ -61,6 +61,7 @@ templated_files = common.py_library( samples=False, # set to True only if there are samples microgenerator=True, + cov_level=98, ) s.move( templated_files, excludes=[".coveragerc"] diff --git a/testing/constraints-3.6.txt b/testing/constraints-3.6.txt new file mode 100644 index 0000000..a929701 --- /dev/null +++ b/testing/constraints-3.6.txt @@ -0,0 +1,9 @@ +# This constraints file is used to check that lower bounds +# are correct in setup.py +# List all library dependencies and extras in this file. +# Pin the version to the lower bound. + +# e.g., if setup.py has "google-cloud-foo >= 1.14.0, < 2.0.0dev", +# Then this file should have google-cloud-foo==1.14.0 +google-api-core==1.22.2 +proto-plus==1.15.0 diff --git a/testing/constraints-3.7.txt b/testing/constraints-3.7.txt new file mode 100644 index 0000000..da93009 --- /dev/null +++ b/testing/constraints-3.7.txt @@ -0,0 +1,2 @@ +# This constraints file is left inentionally empty +# so the latest version of dependencies is installed \ No newline at end of file diff --git a/testing/constraints-3.8.txt b/testing/constraints-3.8.txt new file mode 100644 index 0000000..da93009 --- /dev/null +++ b/testing/constraints-3.8.txt @@ -0,0 +1,2 @@ +# This constraints file is left inentionally empty +# so the latest version of dependencies is installed \ No newline at end of file diff --git a/testing/constraints-3.9.txt b/testing/constraints-3.9.txt new file mode 100644 index 0000000..da93009 --- /dev/null +++ b/testing/constraints-3.9.txt @@ -0,0 +1,2 @@ +# This constraints file is left inentionally empty +# so the latest version of dependencies is installed \ No newline at end of file diff --git a/tests/unit/gapic/recommender_v1/__init__.py b/tests/unit/gapic/recommender_v1/__init__.py index 8b13789..42ffdf2 100644 --- a/tests/unit/gapic/recommender_v1/__init__.py +++ b/tests/unit/gapic/recommender_v1/__init__.py @@ -1 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 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. +# diff --git a/tests/unit/gapic/recommender_v1/test_recommender.py b/tests/unit/gapic/recommender_v1/test_recommender.py index 1ffd245..bd6b694 100644 --- a/tests/unit/gapic/recommender_v1/test_recommender.py +++ b/tests/unit/gapic/recommender_v1/test_recommender.py @@ -86,7 +86,22 @@ def test__get_default_mtls_endpoint(): assert RecommenderClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi -@pytest.mark.parametrize("client_class", [RecommenderClient, RecommenderAsyncClient]) +@pytest.mark.parametrize("client_class", [RecommenderClient, RecommenderAsyncClient,]) +def test_recommender_client_from_service_account_info(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_info" + ) as factory: + factory.return_value = creds + info = {"valid": True} + client = client_class.from_service_account_info(info) + assert client.transport._credentials == creds + assert isinstance(client, client_class) + + assert client.transport._host == "recommender.googleapis.com:443" + + +@pytest.mark.parametrize("client_class", [RecommenderClient, RecommenderAsyncClient,]) def test_recommender_client_from_service_account_file(client_class): creds = credentials.AnonymousCredentials() with mock.patch.object( @@ -95,16 +110,21 @@ def test_recommender_client_from_service_account_file(client_class): factory.return_value = creds client = client_class.from_service_account_file("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) client = client_class.from_service_account_json("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "recommender.googleapis.com:443" def test_recommender_client_get_transport_class(): transport = RecommenderClient.get_transport_class() - assert transport == transports.RecommenderGrpcTransport + available_transports = [ + transports.RecommenderGrpcTransport, + ] + assert transport in available_transports transport = RecommenderClient.get_transport_class("grpc") assert transport == transports.RecommenderGrpcTransport @@ -153,7 +173,7 @@ def test_recommender_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -169,7 +189,7 @@ def test_recommender_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -185,7 +205,7 @@ def test_recommender_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -213,7 +233,7 @@ def test_recommender_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id="octopus", client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -262,29 +282,25 @@ def test_recommender_client_mtls_env_auto( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - ssl_channel_creds = mock.Mock() - with mock.patch( - "grpc.ssl_channel_credentials", return_value=ssl_channel_creds - ): - patched.return_value = None - client = client_class(client_options=options) + patched.return_value = None + client = client_class(client_options=options) - if use_client_cert_env == "false": - expected_ssl_channel_creds = None - expected_host = client.DEFAULT_ENDPOINT - else: - expected_ssl_channel_creds = ssl_channel_creds - expected_host = client.DEFAULT_MTLS_ENDPOINT + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) # Check the case ADC client cert is provided. Whether client cert is used depends on # GOOGLE_API_USE_CLIENT_CERTIFICATE value. @@ -293,66 +309,53 @@ def test_recommender_client_mtls_env_auto( ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, ): with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.ssl_credentials", - new_callable=mock.PropertyMock, - ) as ssl_credentials_mock: - if use_client_cert_env == "false": - is_mtls_mock.return_value = False - ssl_credentials_mock.return_value = None - expected_host = client.DEFAULT_ENDPOINT - expected_ssl_channel_creds = None - else: - is_mtls_mock.return_value = True - ssl_credentials_mock.return_value = mock.Mock() - expected_host = client.DEFAULT_MTLS_ENDPOINT - expected_ssl_channel_creds = ( - ssl_credentials_mock.return_value - ) - - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client.DEFAULT_ENDPOINT + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback - # Check the case client_cert_source and ADC client cert are not provided. - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} - ): - with mock.patch.object(transport_class, "__init__") as patched: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None - ): - with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - is_mtls_mock.return_value = False patched.return_value = None client = client_class() patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_ENDPOINT, + host=expected_host, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + @pytest.mark.parametrize( "client_class,transport_class,transport_name", @@ -378,7 +381,7 @@ def test_recommender_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -408,7 +411,7 @@ def test_recommender_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -425,7 +428,7 @@ def test_recommender_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -468,6 +471,22 @@ def test_list_insights_from_dict(): test_list_insights(request_type=dict) +def test_list_insights_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = RecommenderClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_insights), "__call__") as call: + client.list_insights() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == recommender_service.ListInsightsRequest() + + @pytest.mark.asyncio async def test_list_insights_async( transport: str = "grpc_asyncio", @@ -809,6 +828,22 @@ def test_get_insight_from_dict(): test_get_insight(request_type=dict) +def test_get_insight_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = RecommenderClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_insight), "__call__") as call: + client.get_insight() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == recommender_service.GetInsightRequest() + + @pytest.mark.asyncio async def test_get_insight_async( transport: str = "grpc_asyncio", request_type=recommender_service.GetInsightRequest @@ -1032,6 +1067,24 @@ def test_mark_insight_accepted_from_dict(): test_mark_insight_accepted(request_type=dict) +def test_mark_insight_accepted_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = RecommenderClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.mark_insight_accepted), "__call__" + ) as call: + client.mark_insight_accepted() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == recommender_service.MarkInsightAcceptedRequest() + + @pytest.mark.asyncio async def test_mark_insight_accepted_async( transport: str = "grpc_asyncio", @@ -1273,6 +1326,24 @@ def test_list_recommendations_from_dict(): test_list_recommendations(request_type=dict) +def test_list_recommendations_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = RecommenderClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_recommendations), "__call__" + ) as call: + client.list_recommendations() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == recommender_service.ListRecommendationsRequest() + + @pytest.mark.asyncio async def test_list_recommendations_async( transport: str = "grpc_asyncio", @@ -1672,6 +1743,24 @@ def test_get_recommendation_from_dict(): test_get_recommendation(request_type=dict) +def test_get_recommendation_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = RecommenderClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.get_recommendation), "__call__" + ) as call: + client.get_recommendation() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == recommender_service.GetRecommendationRequest() + + @pytest.mark.asyncio async def test_get_recommendation_async( transport: str = "grpc_asyncio", @@ -1899,6 +1988,24 @@ def test_mark_recommendation_claimed_from_dict(): test_mark_recommendation_claimed(request_type=dict) +def test_mark_recommendation_claimed_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = RecommenderClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.mark_recommendation_claimed), "__call__" + ) as call: + client.mark_recommendation_claimed() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == recommender_service.MarkRecommendationClaimedRequest() + + @pytest.mark.asyncio async def test_mark_recommendation_claimed_async( transport: str = "grpc_asyncio", @@ -2148,6 +2255,24 @@ def test_mark_recommendation_succeeded_from_dict(): test_mark_recommendation_succeeded(request_type=dict) +def test_mark_recommendation_succeeded_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = RecommenderClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.mark_recommendation_succeeded), "__call__" + ) as call: + client.mark_recommendation_succeeded() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == recommender_service.MarkRecommendationSucceededRequest() + + @pytest.mark.asyncio async def test_mark_recommendation_succeeded_async( transport: str = "grpc_asyncio", @@ -2397,6 +2522,24 @@ def test_mark_recommendation_failed_from_dict(): test_mark_recommendation_failed(request_type=dict) +def test_mark_recommendation_failed_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = RecommenderClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.mark_recommendation_failed), "__call__" + ) as call: + client.mark_recommendation_failed() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == recommender_service.MarkRecommendationFailedRequest() + + @pytest.mark.asyncio async def test_mark_recommendation_failed_async( transport: str = "grpc_asyncio", @@ -2653,7 +2796,7 @@ def test_transport_get_channel(): @pytest.mark.parametrize( "transport_class", - [transports.RecommenderGrpcTransport, transports.RecommenderGrpcAsyncIOTransport], + [transports.RecommenderGrpcTransport, transports.RecommenderGrpcAsyncIOTransport,], ) def test_transport_adc(transport_class): # Test default credentials are used if not provided. @@ -2760,6 +2903,48 @@ def test_recommender_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class", + [transports.RecommenderGrpcTransport, transports.RecommenderGrpcAsyncIOTransport], +) +def test_recommender_grpc_transport_client_cert_source_for_mtls(transport_class): + cred = credentials.AnonymousCredentials() + + # Check ssl_channel_credentials is used if provided. + with mock.patch.object(transport_class, "create_channel") as mock_create_channel: + mock_ssl_channel_creds = mock.Mock() + transport_class( + host="squid.clam.whelk", + credentials=cred, + ssl_channel_credentials=mock_ssl_channel_creds, + ) + mock_create_channel.assert_called_once_with( + "squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ssl_credentials=mock_ssl_channel_creds, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls + # is used. + with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()): + with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred: + transport_class( + credentials=cred, + client_cert_source_for_mtls=client_cert_source_callback, + ) + expected_cert, expected_key = client_cert_source_callback() + mock_ssl_cred.assert_called_once_with( + certificate_chain=expected_cert, private_key=expected_key + ) + + def test_recommender_host_no_port(): client = RecommenderClient( credentials=credentials.AnonymousCredentials(), @@ -2781,7 +2966,7 @@ def test_recommender_host_with_port(): def test_recommender_grpc_transport_channel(): - channel = grpc.insecure_channel("http://localhost/") + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.RecommenderGrpcTransport( @@ -2793,7 +2978,7 @@ def test_recommender_grpc_transport_channel(): def test_recommender_grpc_asyncio_transport_channel(): - channel = aio.insecure_channel("http://localhost/") + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.RecommenderGrpcAsyncIOTransport( @@ -2804,6 +2989,8 @@ def test_recommender_grpc_asyncio_transport_channel(): assert transport._ssl_channel_credentials == None +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [transports.RecommenderGrpcTransport, transports.RecommenderGrpcAsyncIOTransport], @@ -2813,7 +3000,7 @@ def test_recommender_transport_channel_mtls_with_client_cert_source(transport_cl "grpc.ssl_channel_credentials", autospec=True ) as grpc_ssl_channel_cred: with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_ssl_cred = mock.Mock() grpc_ssl_channel_cred.return_value = mock_ssl_cred @@ -2851,6 +3038,8 @@ def test_recommender_transport_channel_mtls_with_client_cert_source(transport_cl assert transport._ssl_channel_credentials == mock_ssl_cred +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [transports.RecommenderGrpcTransport, transports.RecommenderGrpcAsyncIOTransport], @@ -2863,7 +3052,7 @@ def test_recommender_transport_channel_mtls_with_adc(transport_class): ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_grpc_channel = mock.Mock() grpc_create_channel.return_value = mock_grpc_channel diff --git a/tests/unit/gapic/recommender_v1beta1/__init__.py b/tests/unit/gapic/recommender_v1beta1/__init__.py index 8b13789..42ffdf2 100644 --- a/tests/unit/gapic/recommender_v1beta1/__init__.py +++ b/tests/unit/gapic/recommender_v1beta1/__init__.py @@ -1 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 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. +# diff --git a/tests/unit/gapic/recommender_v1beta1/test_recommender.py b/tests/unit/gapic/recommender_v1beta1/test_recommender.py index 3ea1c2c..91de2d8 100644 --- a/tests/unit/gapic/recommender_v1beta1/test_recommender.py +++ b/tests/unit/gapic/recommender_v1beta1/test_recommender.py @@ -86,7 +86,22 @@ def test__get_default_mtls_endpoint(): assert RecommenderClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi -@pytest.mark.parametrize("client_class", [RecommenderClient, RecommenderAsyncClient]) +@pytest.mark.parametrize("client_class", [RecommenderClient, RecommenderAsyncClient,]) +def test_recommender_client_from_service_account_info(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_info" + ) as factory: + factory.return_value = creds + info = {"valid": True} + client = client_class.from_service_account_info(info) + assert client.transport._credentials == creds + assert isinstance(client, client_class) + + assert client.transport._host == "recommender.googleapis.com:443" + + +@pytest.mark.parametrize("client_class", [RecommenderClient, RecommenderAsyncClient,]) def test_recommender_client_from_service_account_file(client_class): creds = credentials.AnonymousCredentials() with mock.patch.object( @@ -95,16 +110,21 @@ def test_recommender_client_from_service_account_file(client_class): factory.return_value = creds client = client_class.from_service_account_file("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) client = client_class.from_service_account_json("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "recommender.googleapis.com:443" def test_recommender_client_get_transport_class(): transport = RecommenderClient.get_transport_class() - assert transport == transports.RecommenderGrpcTransport + available_transports = [ + transports.RecommenderGrpcTransport, + ] + assert transport in available_transports transport = RecommenderClient.get_transport_class("grpc") assert transport == transports.RecommenderGrpcTransport @@ -153,7 +173,7 @@ def test_recommender_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -169,7 +189,7 @@ def test_recommender_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -185,7 +205,7 @@ def test_recommender_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -213,7 +233,7 @@ def test_recommender_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id="octopus", client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -262,29 +282,25 @@ def test_recommender_client_mtls_env_auto( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - ssl_channel_creds = mock.Mock() - with mock.patch( - "grpc.ssl_channel_credentials", return_value=ssl_channel_creds - ): - patched.return_value = None - client = client_class(client_options=options) + patched.return_value = None + client = client_class(client_options=options) - if use_client_cert_env == "false": - expected_ssl_channel_creds = None - expected_host = client.DEFAULT_ENDPOINT - else: - expected_ssl_channel_creds = ssl_channel_creds - expected_host = client.DEFAULT_MTLS_ENDPOINT + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) # Check the case ADC client cert is provided. Whether client cert is used depends on # GOOGLE_API_USE_CLIENT_CERTIFICATE value. @@ -293,66 +309,53 @@ def test_recommender_client_mtls_env_auto( ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, ): with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.ssl_credentials", - new_callable=mock.PropertyMock, - ) as ssl_credentials_mock: - if use_client_cert_env == "false": - is_mtls_mock.return_value = False - ssl_credentials_mock.return_value = None - expected_host = client.DEFAULT_ENDPOINT - expected_ssl_channel_creds = None - else: - is_mtls_mock.return_value = True - ssl_credentials_mock.return_value = mock.Mock() - expected_host = client.DEFAULT_MTLS_ENDPOINT - expected_ssl_channel_creds = ( - ssl_credentials_mock.return_value - ) - - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client.DEFAULT_ENDPOINT + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback - # Check the case client_cert_source and ADC client cert are not provided. - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} - ): - with mock.patch.object(transport_class, "__init__") as patched: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None - ): - with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - is_mtls_mock.return_value = False patched.return_value = None client = client_class() patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_ENDPOINT, + host=expected_host, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + @pytest.mark.parametrize( "client_class,transport_class,transport_name", @@ -378,7 +381,7 @@ def test_recommender_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -408,7 +411,7 @@ def test_recommender_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -425,7 +428,7 @@ def test_recommender_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -468,6 +471,22 @@ def test_list_insights_from_dict(): test_list_insights(request_type=dict) +def test_list_insights_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = RecommenderClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_insights), "__call__") as call: + client.list_insights() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == recommender_service.ListInsightsRequest() + + @pytest.mark.asyncio async def test_list_insights_async( transport: str = "grpc_asyncio", @@ -809,6 +828,22 @@ def test_get_insight_from_dict(): test_get_insight(request_type=dict) +def test_get_insight_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = RecommenderClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_insight), "__call__") as call: + client.get_insight() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == recommender_service.GetInsightRequest() + + @pytest.mark.asyncio async def test_get_insight_async( transport: str = "grpc_asyncio", request_type=recommender_service.GetInsightRequest @@ -1032,6 +1067,24 @@ def test_mark_insight_accepted_from_dict(): test_mark_insight_accepted(request_type=dict) +def test_mark_insight_accepted_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = RecommenderClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.mark_insight_accepted), "__call__" + ) as call: + client.mark_insight_accepted() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == recommender_service.MarkInsightAcceptedRequest() + + @pytest.mark.asyncio async def test_mark_insight_accepted_async( transport: str = "grpc_asyncio", @@ -1273,6 +1326,24 @@ def test_list_recommendations_from_dict(): test_list_recommendations(request_type=dict) +def test_list_recommendations_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = RecommenderClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.list_recommendations), "__call__" + ) as call: + client.list_recommendations() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == recommender_service.ListRecommendationsRequest() + + @pytest.mark.asyncio async def test_list_recommendations_async( transport: str = "grpc_asyncio", @@ -1672,6 +1743,24 @@ def test_get_recommendation_from_dict(): test_get_recommendation(request_type=dict) +def test_get_recommendation_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = RecommenderClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.get_recommendation), "__call__" + ) as call: + client.get_recommendation() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == recommender_service.GetRecommendationRequest() + + @pytest.mark.asyncio async def test_get_recommendation_async( transport: str = "grpc_asyncio", @@ -1899,6 +1988,24 @@ def test_mark_recommendation_claimed_from_dict(): test_mark_recommendation_claimed(request_type=dict) +def test_mark_recommendation_claimed_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = RecommenderClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.mark_recommendation_claimed), "__call__" + ) as call: + client.mark_recommendation_claimed() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == recommender_service.MarkRecommendationClaimedRequest() + + @pytest.mark.asyncio async def test_mark_recommendation_claimed_async( transport: str = "grpc_asyncio", @@ -2148,6 +2255,24 @@ def test_mark_recommendation_succeeded_from_dict(): test_mark_recommendation_succeeded(request_type=dict) +def test_mark_recommendation_succeeded_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = RecommenderClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.mark_recommendation_succeeded), "__call__" + ) as call: + client.mark_recommendation_succeeded() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == recommender_service.MarkRecommendationSucceededRequest() + + @pytest.mark.asyncio async def test_mark_recommendation_succeeded_async( transport: str = "grpc_asyncio", @@ -2397,6 +2522,24 @@ def test_mark_recommendation_failed_from_dict(): test_mark_recommendation_failed(request_type=dict) +def test_mark_recommendation_failed_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = RecommenderClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.mark_recommendation_failed), "__call__" + ) as call: + client.mark_recommendation_failed() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == recommender_service.MarkRecommendationFailedRequest() + + @pytest.mark.asyncio async def test_mark_recommendation_failed_async( transport: str = "grpc_asyncio", @@ -2653,7 +2796,7 @@ def test_transport_get_channel(): @pytest.mark.parametrize( "transport_class", - [transports.RecommenderGrpcTransport, transports.RecommenderGrpcAsyncIOTransport], + [transports.RecommenderGrpcTransport, transports.RecommenderGrpcAsyncIOTransport,], ) def test_transport_adc(transport_class): # Test default credentials are used if not provided. @@ -2760,6 +2903,48 @@ def test_recommender_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class", + [transports.RecommenderGrpcTransport, transports.RecommenderGrpcAsyncIOTransport], +) +def test_recommender_grpc_transport_client_cert_source_for_mtls(transport_class): + cred = credentials.AnonymousCredentials() + + # Check ssl_channel_credentials is used if provided. + with mock.patch.object(transport_class, "create_channel") as mock_create_channel: + mock_ssl_channel_creds = mock.Mock() + transport_class( + host="squid.clam.whelk", + credentials=cred, + ssl_channel_credentials=mock_ssl_channel_creds, + ) + mock_create_channel.assert_called_once_with( + "squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ssl_credentials=mock_ssl_channel_creds, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls + # is used. + with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()): + with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred: + transport_class( + credentials=cred, + client_cert_source_for_mtls=client_cert_source_callback, + ) + expected_cert, expected_key = client_cert_source_callback() + mock_ssl_cred.assert_called_once_with( + certificate_chain=expected_cert, private_key=expected_key + ) + + def test_recommender_host_no_port(): client = RecommenderClient( credentials=credentials.AnonymousCredentials(), @@ -2781,7 +2966,7 @@ def test_recommender_host_with_port(): def test_recommender_grpc_transport_channel(): - channel = grpc.insecure_channel("http://localhost/") + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.RecommenderGrpcTransport( @@ -2793,7 +2978,7 @@ def test_recommender_grpc_transport_channel(): def test_recommender_grpc_asyncio_transport_channel(): - channel = aio.insecure_channel("http://localhost/") + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.RecommenderGrpcAsyncIOTransport( @@ -2804,6 +2989,8 @@ def test_recommender_grpc_asyncio_transport_channel(): assert transport._ssl_channel_credentials == None +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [transports.RecommenderGrpcTransport, transports.RecommenderGrpcAsyncIOTransport], @@ -2813,7 +3000,7 @@ def test_recommender_transport_channel_mtls_with_client_cert_source(transport_cl "grpc.ssl_channel_credentials", autospec=True ) as grpc_ssl_channel_cred: with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_ssl_cred = mock.Mock() grpc_ssl_channel_cred.return_value = mock_ssl_cred @@ -2851,6 +3038,8 @@ def test_recommender_transport_channel_mtls_with_client_cert_source(transport_cl assert transport._ssl_channel_credentials == mock_ssl_cred +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [transports.RecommenderGrpcTransport, transports.RecommenderGrpcAsyncIOTransport], @@ -2863,7 +3052,7 @@ def test_recommender_transport_channel_mtls_with_adc(transport_class): ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_grpc_channel = mock.Mock() grpc_create_channel.return_value = mock_grpc_channel