From b98373544181ecc55104230fc1e53b206c2e23ac Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Thu, 11 Jun 2020 12:58:37 -0700 Subject: [PATCH] feat: add mTLS support, fix missing routing header issue (#15) --- .flake8 | 2 + .gitignore | 4 +- .kokoro/publish-docs.sh | 2 - .kokoro/release.sh | 2 - .kokoro/samples/lint/common.cfg | 34 ++ .kokoro/samples/lint/continuous.cfg | 6 + .kokoro/samples/lint/periodic.cfg | 6 + .kokoro/samples/lint/presubmit.cfg | 6 + .kokoro/samples/python3.6/common.cfg | 34 ++ .kokoro/samples/python3.6/continuous.cfg | 7 + .kokoro/samples/python3.6/periodic.cfg | 6 + .kokoro/samples/python3.6/presubmit.cfg | 6 + .kokoro/samples/python3.7/common.cfg | 34 ++ .kokoro/samples/python3.7/continuous.cfg | 6 + .kokoro/samples/python3.7/periodic.cfg | 6 + .kokoro/samples/python3.7/presubmit.cfg | 6 + .kokoro/samples/python3.8/common.cfg | 34 ++ .kokoro/samples/python3.8/continuous.cfg | 6 + .kokoro/samples/python3.8/periodic.cfg | 6 + .kokoro/samples/python3.8/presubmit.cfg | 6 + .kokoro/test-samples.sh | 104 ++++ MANIFEST.in | 3 + docs/conf.py | 5 +- docs/index.rst | 2 + docs/multiprocessing.rst | 7 + docs/servicedirectory_v1beta1/services.rst | 9 +- docs/servicedirectory_v1beta1/types.rst | 4 +- google/cloud/servicedirectory/__init__.py | 1 - .../servicedirectory_v1beta1/__init__.py | 1 - .../services/lookup_service/client.py | 81 +-- .../lookup_service/transports/grpc.py | 6 +- .../services/registration_service/client.py | 185 +++++-- .../registration_service/transports/grpc.py | 6 +- noxfile.py | 22 +- scripts/decrypt-secrets.sh | 33 ++ scripts/readme-gen/readme_gen.py | 66 +++ scripts/readme-gen/templates/README.tmpl.rst | 87 ++++ scripts/readme-gen/templates/auth.tmpl.rst | 9 + .../templates/auth_api_key.tmpl.rst | 14 + .../templates/install_deps.tmpl.rst | 29 ++ .../templates/install_portaudio.tmpl.rst | 35 ++ setup.py | 4 +- synth.metadata | 17 +- synth.py | 25 +- testing/.gitignore | 3 + .../test_lookup_service.py | 148 +++++- .../test_registration_service.py | 480 ++++++++++++++++-- 47 files changed, 1402 insertions(+), 203 deletions(-) create mode 100644 .kokoro/samples/lint/common.cfg create mode 100644 .kokoro/samples/lint/continuous.cfg create mode 100644 .kokoro/samples/lint/periodic.cfg create mode 100644 .kokoro/samples/lint/presubmit.cfg create mode 100644 .kokoro/samples/python3.6/common.cfg create mode 100644 .kokoro/samples/python3.6/continuous.cfg create mode 100644 .kokoro/samples/python3.6/periodic.cfg create mode 100644 .kokoro/samples/python3.6/presubmit.cfg create mode 100644 .kokoro/samples/python3.7/common.cfg create mode 100644 .kokoro/samples/python3.7/continuous.cfg create mode 100644 .kokoro/samples/python3.7/periodic.cfg create mode 100644 .kokoro/samples/python3.7/presubmit.cfg create mode 100644 .kokoro/samples/python3.8/common.cfg create mode 100644 .kokoro/samples/python3.8/continuous.cfg create mode 100644 .kokoro/samples/python3.8/periodic.cfg create mode 100644 .kokoro/samples/python3.8/presubmit.cfg create mode 100755 .kokoro/test-samples.sh create mode 100644 docs/multiprocessing.rst create mode 100755 scripts/decrypt-secrets.sh create mode 100644 scripts/readme-gen/readme_gen.py create mode 100644 scripts/readme-gen/templates/README.tmpl.rst create mode 100644 scripts/readme-gen/templates/auth.tmpl.rst create mode 100644 scripts/readme-gen/templates/auth_api_key.tmpl.rst create mode 100644 scripts/readme-gen/templates/install_deps.tmpl.rst create mode 100644 scripts/readme-gen/templates/install_portaudio.tmpl.rst create mode 100644 testing/.gitignore diff --git a/.flake8 b/.flake8 index 49e82698..d1134d72 100644 --- a/.flake8 +++ b/.flake8 @@ -21,6 +21,8 @@ exclude = # Exclude generated code. **/proto/** **/gapic/** + **/services/** + **/types/** *_pb2.py # Standard linting exemptions. diff --git a/.gitignore b/.gitignore index df79b144..b87e1ed5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ dist build eggs +.eggs parts bin var @@ -44,11 +45,12 @@ pip-log.txt # Built documentation docs/_build -htmlcov +bigquery/docs/generated # Virtual environment env/ coverage.xml +sponge_log.xml # System test environment variables. system_tests/local_test_setup diff --git a/.kokoro/publish-docs.sh b/.kokoro/publish-docs.sh index b42ebaaa..b20a5ef8 100755 --- a/.kokoro/publish-docs.sh +++ b/.kokoro/publish-docs.sh @@ -13,8 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/bin/bash - set -eo pipefail # Disable buffering, so that the logs stream through. diff --git a/.kokoro/release.sh b/.kokoro/release.sh index e717c3df..e2916d80 100755 --- a/.kokoro/release.sh +++ b/.kokoro/release.sh @@ -13,8 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/bin/bash - set -eo pipefail # Start the releasetool reporter diff --git a/.kokoro/samples/lint/common.cfg b/.kokoro/samples/lint/common.cfg new file mode 100644 index 00000000..36acc942 --- /dev/null +++ b/.kokoro/samples/lint/common.cfg @@ -0,0 +1,34 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "lint" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-service-directory/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "python-service-directory/.kokoro/trampoline.sh" \ No newline at end of file diff --git a/.kokoro/samples/lint/continuous.cfg b/.kokoro/samples/lint/continuous.cfg new file mode 100644 index 00000000..a1c8d975 --- /dev/null +++ b/.kokoro/samples/lint/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/lint/periodic.cfg b/.kokoro/samples/lint/periodic.cfg new file mode 100644 index 00000000..50fec964 --- /dev/null +++ b/.kokoro/samples/lint/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} \ No newline at end of file diff --git a/.kokoro/samples/lint/presubmit.cfg b/.kokoro/samples/lint/presubmit.cfg new file mode 100644 index 00000000..a1c8d975 --- /dev/null +++ b/.kokoro/samples/lint/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.6/common.cfg b/.kokoro/samples/python3.6/common.cfg new file mode 100644 index 00000000..fb861668 --- /dev/null +++ b/.kokoro/samples/python3.6/common.cfg @@ -0,0 +1,34 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.6" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-service-directory/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "python-service-directory/.kokoro/trampoline.sh" \ No newline at end of file diff --git a/.kokoro/samples/python3.6/continuous.cfg b/.kokoro/samples/python3.6/continuous.cfg new file mode 100644 index 00000000..7218af14 --- /dev/null +++ b/.kokoro/samples/python3.6/continuous.cfg @@ -0,0 +1,7 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + diff --git a/.kokoro/samples/python3.6/periodic.cfg b/.kokoro/samples/python3.6/periodic.cfg new file mode 100644 index 00000000..50fec964 --- /dev/null +++ b/.kokoro/samples/python3.6/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.6/presubmit.cfg b/.kokoro/samples/python3.6/presubmit.cfg new file mode 100644 index 00000000..a1c8d975 --- /dev/null +++ b/.kokoro/samples/python3.6/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.7/common.cfg b/.kokoro/samples/python3.7/common.cfg new file mode 100644 index 00000000..4f6a8107 --- /dev/null +++ b/.kokoro/samples/python3.7/common.cfg @@ -0,0 +1,34 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.7" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-service-directory/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "python-service-directory/.kokoro/trampoline.sh" \ No newline at end of file diff --git a/.kokoro/samples/python3.7/continuous.cfg b/.kokoro/samples/python3.7/continuous.cfg new file mode 100644 index 00000000..a1c8d975 --- /dev/null +++ b/.kokoro/samples/python3.7/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.7/periodic.cfg b/.kokoro/samples/python3.7/periodic.cfg new file mode 100644 index 00000000..50fec964 --- /dev/null +++ b/.kokoro/samples/python3.7/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.7/presubmit.cfg b/.kokoro/samples/python3.7/presubmit.cfg new file mode 100644 index 00000000..a1c8d975 --- /dev/null +++ b/.kokoro/samples/python3.7/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.8/common.cfg b/.kokoro/samples/python3.8/common.cfg new file mode 100644 index 00000000..c6b67aa4 --- /dev/null +++ b/.kokoro/samples/python3.8/common.cfg @@ -0,0 +1,34 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Specify which tests to run +env_vars: { + key: "RUN_TESTS_SESSION" + value: "py-3.8" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-service-directory/.kokoro/test-samples.sh" +} + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/python-samples-testing-docker" +} + +# Download secrets for samples +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/python-docs-samples" + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "python-service-directory/.kokoro/trampoline.sh" \ No newline at end of file diff --git a/.kokoro/samples/python3.8/continuous.cfg b/.kokoro/samples/python3.8/continuous.cfg new file mode 100644 index 00000000..a1c8d975 --- /dev/null +++ b/.kokoro/samples/python3.8/continuous.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.8/periodic.cfg b/.kokoro/samples/python3.8/periodic.cfg new file mode 100644 index 00000000..50fec964 --- /dev/null +++ b/.kokoro/samples/python3.8/periodic.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "False" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.8/presubmit.cfg b/.kokoro/samples/python3.8/presubmit.cfg new file mode 100644 index 00000000..a1c8d975 --- /dev/null +++ b/.kokoro/samples/python3.8/presubmit.cfg @@ -0,0 +1,6 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} \ No newline at end of file diff --git a/.kokoro/test-samples.sh b/.kokoro/test-samples.sh new file mode 100755 index 00000000..bc963c8f --- /dev/null +++ b/.kokoro/test-samples.sh @@ -0,0 +1,104 @@ +#!/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. + + +# `-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-service-directory + +# Run periodic samples tests at latest release +if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then + LATEST_RELEASE=$(git describe --abbrev=0 --tags) + git checkout $LATEST_RELEASE +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 Build Cop Bot. + # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/buildcop. + if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then + chmod +x $KOKORO_GFILE_DIR/linux_amd64/buildcop + $KOKORO_GFILE_DIR/linux_amd64/buildcop + 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" \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index 68855abc..e9e29d12 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -20,3 +20,6 @@ recursive-include google *.json *.proto 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 diff --git a/docs/conf.py b/docs/conf.py index f4ea7f26..4a27377e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -38,6 +38,7 @@ "sphinx.ext.napoleon", "sphinx.ext.todo", "sphinx.ext.viewcode", + "recommonmark", ] # autodoc/autosummary flags @@ -49,10 +50,6 @@ # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] -# Allow markdown includes (so releases.md can include CHANGLEOG.md) -# http://www.sphinx-doc.org/en/master/markdown.html -source_parsers = {".md": "recommonmark.parser.CommonMarkParser"} - # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] diff --git a/docs/index.rst b/docs/index.rst index eb61c635..af1802bd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,7 @@ .. include:: README.rst +.. include:: multiprocessing.rst + API Reference ------------- .. toctree:: diff --git a/docs/multiprocessing.rst b/docs/multiprocessing.rst new file mode 100644 index 00000000..1cb29d4c --- /dev/null +++ b/docs/multiprocessing.rst @@ -0,0 +1,7 @@ +.. note:: + + Because this client uses :mod:`grpcio` library, it is safe to + share instances across threads. In multiprocessing scenarios, the best + practice is to create client instances *after* the invocation of + :func:`os.fork` by :class:`multiprocessing.Pool` or + :class:`multiprocessing.Process`. diff --git a/docs/servicedirectory_v1beta1/services.rst b/docs/servicedirectory_v1beta1/services.rst index c05b881c..be4bb089 100644 --- a/docs/servicedirectory_v1beta1/services.rst +++ b/docs/servicedirectory_v1beta1/services.rst @@ -1,6 +1,9 @@ -Client for Google Cloud Servicedirectory API -============================================ +Services for Google Cloud Servicedirectory v1beta1 API +====================================================== -.. automodule:: google.cloud.servicedirectory_v1beta1 +.. automodule:: google.cloud.servicedirectory_v1beta1.services.lookup_service + :members: + :inherited-members: +.. automodule:: google.cloud.servicedirectory_v1beta1.services.registration_service :members: :inherited-members: diff --git a/docs/servicedirectory_v1beta1/types.rst b/docs/servicedirectory_v1beta1/types.rst index 087ed274..9366889f 100644 --- a/docs/servicedirectory_v1beta1/types.rst +++ b/docs/servicedirectory_v1beta1/types.rst @@ -1,5 +1,5 @@ -Types for Google Cloud Servicedirectory API -=========================================== +Types for Google Cloud Servicedirectory v1beta1 API +=================================================== .. automodule:: google.cloud.servicedirectory_v1beta1.types :members: diff --git a/google/cloud/servicedirectory/__init__.py b/google/cloud/servicedirectory/__init__.py index 08bca5ae..15cc52a0 100644 --- a/google/cloud/servicedirectory/__init__.py +++ b/google/cloud/servicedirectory/__init__.py @@ -15,7 +15,6 @@ # limitations under the License. # - from google.cloud.servicedirectory_v1beta1.services.lookup_service.client import ( LookupServiceClient, ) diff --git a/google/cloud/servicedirectory_v1beta1/__init__.py b/google/cloud/servicedirectory_v1beta1/__init__.py index 86aff12a..e20c9ec4 100644 --- a/google/cloud/servicedirectory_v1beta1/__init__.py +++ b/google/cloud/servicedirectory_v1beta1/__init__.py @@ -15,7 +15,6 @@ # limitations under the License. # - from .services.lookup_service import LookupServiceClient from .services.registration_service import RegistrationServiceClient from .types.endpoint import Endpoint diff --git a/google/cloud/servicedirectory_v1beta1/services/lookup_service/client.py b/google/cloud/servicedirectory_v1beta1/services/lookup_service/client.py index 99540270..cf9ee64c 100644 --- a/google/cloud/servicedirectory_v1beta1/services/lookup_service/client.py +++ b/google/cloud/servicedirectory_v1beta1/services/lookup_service/client.py @@ -16,6 +16,7 @@ # from collections import OrderedDict +import os import re from typing import Callable, Dict, Sequence, Tuple, Type, Union import pkg_resources @@ -25,6 +26,8 @@ from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore from google.auth import credentials # type: ignore +from google.auth.transport import mtls # type: ignore +from google.auth.exceptions import MutualTLSChannelError # type: ignore from google.oauth2 import service_account # type: ignore from google.cloud.servicedirectory_v1beta1.types import lookup_service @@ -139,21 +142,49 @@ def __init__( transport (Union[str, ~.LookupServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (ClientOptions): Custom options for the client. + client_options (ClientOptions): Custom options for the client. It + won't take effect unless ``transport`` is None. (1) The ``api_endpoint`` property can be used to override the - default endpoint provided by the client. - (2) If ``transport`` argument is None, ``client_options`` can be - used to create a mutual TLS transport. If ``client_cert_source`` - is provided, mutual TLS transport will be created with the given - ``api_endpoint`` or the default mTLS endpoint, and the client - SSL credentials obtained from ``client_cert_source``. + default endpoint provided by the client. GOOGLE_API_USE_MTLS + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint, this is the default value for + the environment variable) and "auto" (auto switch to the default + mTLS endpoint if client SSL credentials is present). However, + the ``api_endpoint`` property takes precedence if provided. + (2) The ``client_cert_source`` property is used to provide client + SSL credentials for mutual TLS transport. If not provided, the + default SSL credentials will be used if present. Raises: - google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport creation failed for any reason. """ if isinstance(client_options, dict): client_options = ClientOptions.from_dict(client_options) + if client_options is None: + client_options = ClientOptions.ClientOptions() + + if transport is None and client_options.api_endpoint is None: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "never") + if use_mtls_env == "never": + client_options.api_endpoint = self.DEFAULT_ENDPOINT + elif use_mtls_env == "always": + client_options.api_endpoint = self.DEFAULT_MTLS_ENDPOINT + elif use_mtls_env == "auto": + has_client_cert_source = ( + client_options.client_cert_source is not None + or mtls.has_default_client_cert_source() + ) + client_options.api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT + if has_client_cert_source + else self.DEFAULT_ENDPOINT + ) + else: + raise MutualTLSChannelError( + "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: Never, Auto, Always" + ) # Save or instantiate the transport. # Ordinarily, we provide the transport, but allowing a custom transport @@ -166,38 +197,16 @@ def __init__( "provide its credentials directly." ) self._transport = transport - elif client_options is None or ( - client_options.api_endpoint is None - and client_options.client_cert_source is None - ): - # Don't trigger mTLS if we get an empty ClientOptions. + elif isinstance(transport, str): Transport = type(self).get_transport_class(transport) self._transport = Transport( credentials=credentials, host=self.DEFAULT_ENDPOINT ) else: - # We have a non-empty ClientOptions. If client_cert_source is - # provided, trigger mTLS with user provided endpoint or the default - # mTLS endpoint. - if client_options.client_cert_source: - api_mtls_endpoint = ( - client_options.api_endpoint - if client_options.api_endpoint - else self.DEFAULT_MTLS_ENDPOINT - ) - else: - api_mtls_endpoint = None - - api_endpoint = ( - client_options.api_endpoint - if client_options.api_endpoint - else self.DEFAULT_ENDPOINT - ) - self._transport = LookupServiceGrpcTransport( credentials=credentials, - host=api_endpoint, - api_mtls_endpoint=api_mtls_endpoint, + host=client_options.api_endpoint, + api_mtls_endpoint=client_options.api_endpoint, client_cert_source=client_options.client_cert_source, ) @@ -245,6 +254,12 @@ def resolve_service( client_info=_client_info, ) + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + # Send the request. response = rpc(request, retry=retry, timeout=timeout, metadata=metadata) diff --git a/google/cloud/servicedirectory_v1beta1/services/lookup_service/transports/grpc.py b/google/cloud/servicedirectory_v1beta1/services/lookup_service/transports/grpc.py index 8bbf277d..f50c0862 100644 --- a/google/cloud/servicedirectory_v1beta1/services/lookup_service/transports/grpc.py +++ b/google/cloud/servicedirectory_v1beta1/services/lookup_service/transports/grpc.py @@ -18,6 +18,7 @@ from typing import Callable, Dict, Tuple from google.api_core import grpc_helpers # type: ignore +from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -73,7 +74,7 @@ def __init__( is None. Raises: - google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport creation failed for any reason. """ if channel: @@ -90,6 +91,9 @@ def __init__( else api_mtls_endpoint + ":443" ) + if credentials is None: + credentials, _ = auth.default(scopes=self.AUTH_SCOPES) + # Create SSL credentials with client_cert_source or application # default SSL credentials. if client_cert_source: diff --git a/google/cloud/servicedirectory_v1beta1/services/registration_service/client.py b/google/cloud/servicedirectory_v1beta1/services/registration_service/client.py index 24c06ee7..14845a81 100644 --- a/google/cloud/servicedirectory_v1beta1/services/registration_service/client.py +++ b/google/cloud/servicedirectory_v1beta1/services/registration_service/client.py @@ -16,6 +16,7 @@ # from collections import OrderedDict +import os import re from typing import Callable, Dict, Sequence, Tuple, Type, Union import pkg_resources @@ -25,6 +26,8 @@ from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore from google.auth import credentials # type: ignore +from google.auth.transport import mtls # type: ignore +from google.auth.exceptions import MutualTLSChannelError # type: ignore from google.oauth2 import service_account # type: ignore from google.cloud.servicedirectory_v1beta1.services.registration_service import pagers @@ -150,22 +153,6 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): from_service_account_json = from_service_account_file - @staticmethod - def service_path(project: str, location: str, namespace: str, service: str) -> str: - """Return a fully-qualified service string.""" - return "projects/{project}/locations/{location}/namespaces/{namespace}/services/{service}".format( - project=project, location=location, namespace=namespace, service=service - ) - - @staticmethod - def parse_service_path(path: str) -> Dict[str, str]: - """Parse a service path into its component segments.""" - m = re.match( - r"^projects/(?P.+?)/locations/(?P.+?)/namespaces/(?P.+?)/services/(?P.+?)$", - path, - ) - return m.groupdict() if m else {} - @staticmethod def endpoint_path( project: str, location: str, namespace: str, service: str, endpoint: str @@ -204,6 +191,22 @@ def parse_namespace_path(path: str) -> Dict[str, str]: ) return m.groupdict() if m else {} + @staticmethod + def service_path(project: str, location: str, namespace: str, service: str) -> str: + """Return a fully-qualified service string.""" + return "projects/{project}/locations/{location}/namespaces/{namespace}/services/{service}".format( + project=project, location=location, namespace=namespace, service=service + ) + + @staticmethod + def parse_service_path(path: str) -> Dict[str, str]: + """Parse a service path into its component segments.""" + m = re.match( + r"^projects/(?P.+?)/locations/(?P.+?)/namespaces/(?P.+?)/services/(?P.+?)$", + path, + ) + return m.groupdict() if m else {} + def __init__( self, *, @@ -222,21 +225,49 @@ def __init__( transport (Union[str, ~.RegistrationServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (ClientOptions): Custom options for the client. + client_options (ClientOptions): Custom options for the client. It + won't take effect unless ``transport`` is None. (1) The ``api_endpoint`` property can be used to override the - default endpoint provided by the client. - (2) If ``transport`` argument is None, ``client_options`` can be - used to create a mutual TLS transport. If ``client_cert_source`` - is provided, mutual TLS transport will be created with the given - ``api_endpoint`` or the default mTLS endpoint, and the client - SSL credentials obtained from ``client_cert_source``. + default endpoint provided by the client. GOOGLE_API_USE_MTLS + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint, this is the default value for + the environment variable) and "auto" (auto switch to the default + mTLS endpoint if client SSL credentials is present). However, + the ``api_endpoint`` property takes precedence if provided. + (2) The ``client_cert_source`` property is used to provide client + SSL credentials for mutual TLS transport. If not provided, the + default SSL credentials will be used if present. Raises: - google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport creation failed for any reason. """ if isinstance(client_options, dict): client_options = ClientOptions.from_dict(client_options) + if client_options is None: + client_options = ClientOptions.ClientOptions() + + if transport is None and client_options.api_endpoint is None: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "never") + if use_mtls_env == "never": + client_options.api_endpoint = self.DEFAULT_ENDPOINT + elif use_mtls_env == "always": + client_options.api_endpoint = self.DEFAULT_MTLS_ENDPOINT + elif use_mtls_env == "auto": + has_client_cert_source = ( + client_options.client_cert_source is not None + or mtls.has_default_client_cert_source() + ) + client_options.api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT + if has_client_cert_source + else self.DEFAULT_ENDPOINT + ) + else: + raise MutualTLSChannelError( + "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: Never, Auto, Always" + ) # Save or instantiate the transport. # Ordinarily, we provide the transport, but allowing a custom transport @@ -249,38 +280,16 @@ def __init__( "provide its credentials directly." ) self._transport = transport - elif client_options is None or ( - client_options.api_endpoint is None - and client_options.client_cert_source is None - ): - # Don't trigger mTLS if we get an empty ClientOptions. + elif isinstance(transport, str): Transport = type(self).get_transport_class(transport) self._transport = Transport( credentials=credentials, host=self.DEFAULT_ENDPOINT ) else: - # We have a non-empty ClientOptions. If client_cert_source is - # provided, trigger mTLS with user provided endpoint or the default - # mTLS endpoint. - if client_options.client_cert_source: - api_mtls_endpoint = ( - client_options.api_endpoint - if client_options.api_endpoint - else self.DEFAULT_MTLS_ENDPOINT - ) - else: - api_mtls_endpoint = None - - api_endpoint = ( - client_options.api_endpoint - if client_options.api_endpoint - else self.DEFAULT_ENDPOINT - ) - self._transport = RegistrationServiceGrpcTransport( credentials=credentials, - host=api_endpoint, - api_mtls_endpoint=api_mtls_endpoint, + host=client_options.api_endpoint, + api_mtls_endpoint=client_options.api_endpoint, client_cert_source=client_options.client_cert_source, ) @@ -371,6 +380,12 @@ def create_namespace( client_info=_client_info, ) + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + # Send the request. response = rpc(request, retry=retry, timeout=timeout, metadata=metadata) @@ -602,6 +617,14 @@ def update_namespace( client_info=_client_info, ) + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata( + (("namespace.name", request.namespace.name),) + ), + ) + # Send the request. response = rpc(request, retry=retry, timeout=timeout, metadata=metadata) @@ -662,6 +685,12 @@ def delete_namespace( client_info=_client_info, ) + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + # Send the request. rpc(request, retry=retry, timeout=timeout, metadata=metadata) @@ -750,6 +779,12 @@ def create_service( client_info=_client_info, ) + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + # Send the request. response = rpc(request, retry=retry, timeout=timeout, metadata=metadata) @@ -980,6 +1015,14 @@ def update_service( client_info=_client_info, ) + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata( + (("service.name", request.service.name),) + ), + ) + # Send the request. response = rpc(request, retry=retry, timeout=timeout, metadata=metadata) @@ -1040,6 +1083,12 @@ def delete_service( client_info=_client_info, ) + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + # Send the request. rpc(request, retry=retry, timeout=timeout, metadata=metadata) @@ -1127,6 +1176,12 @@ def create_endpoint( client_info=_client_info, ) + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + # Send the request. response = rpc(request, retry=retry, timeout=timeout, metadata=metadata) @@ -1354,6 +1409,14 @@ def update_endpoint( client_info=_client_info, ) + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata( + (("endpoint.name", request.endpoint.name),) + ), + ) + # Send the request. response = rpc(request, retry=retry, timeout=timeout, metadata=metadata) @@ -1413,6 +1476,12 @@ def delete_endpoint( client_info=_client_info, ) + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + # Send the request. rpc(request, retry=retry, timeout=timeout, metadata=metadata) @@ -1522,6 +1591,12 @@ def get_iam_policy( client_info=_client_info, ) + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("resource", request.resource),)), + ) + # Send the request. response = rpc(request, retry=retry, timeout=timeout, metadata=metadata) @@ -1634,6 +1709,12 @@ def set_iam_policy( client_info=_client_info, ) + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("resource", request.resource),)), + ) + # Send the request. response = rpc(request, retry=retry, timeout=timeout, metadata=metadata) @@ -1681,6 +1762,12 @@ def test_iam_permissions( client_info=_client_info, ) + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("resource", request.resource),)), + ) + # Send the request. response = rpc(request, retry=retry, timeout=timeout, metadata=metadata) diff --git a/google/cloud/servicedirectory_v1beta1/services/registration_service/transports/grpc.py b/google/cloud/servicedirectory_v1beta1/services/registration_service/transports/grpc.py index 9bd5945c..6a7a6571 100644 --- a/google/cloud/servicedirectory_v1beta1/services/registration_service/transports/grpc.py +++ b/google/cloud/servicedirectory_v1beta1/services/registration_service/transports/grpc.py @@ -18,6 +18,7 @@ from typing import Callable, Dict, Tuple from google.api_core import grpc_helpers # type: ignore +from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -97,7 +98,7 @@ def __init__( is None. Raises: - google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport creation failed for any reason. """ if channel: @@ -114,6 +115,9 @@ def __init__( else api_mtls_endpoint + ":443" ) + if credentials is None: + credentials, _ = auth.default(scopes=self.AUTH_SCOPES) + # Create SSL credentials with client_cert_source or application # default SSL credentials. if client_cert_source: diff --git a/noxfile.py b/noxfile.py index 9c6bcdcc..78870285 100644 --- a/noxfile.py +++ b/noxfile.py @@ -26,11 +26,12 @@ BLACK_VERSION = "black==19.3b0" BLACK_PATHS = ["docs", "google", "tests", "noxfile.py", "setup.py"] -if os.path.exists("samples"): - BLACK_PATHS.append("samples") +DEFAULT_PYTHON_VERSION = "3.7" +SYSTEM_TEST_PYTHON_VERSIONS = ["3.7"] +UNIT_TEST_PYTHON_VERSIONS = ["3.6", "3.7", "3.8"] -@nox.session(python="3.7") +@nox.session(python=DEFAULT_PYTHON_VERSION) def lint(session): """Run linters. @@ -56,7 +57,7 @@ def blacken(session): session.run("black", *BLACK_PATHS) -@nox.session(python="3.7") +@nox.session(python=DEFAULT_PYTHON_VERSION) def lint_setup_py(session): """Verify that setup.py is valid (including RST check).""" session.install("docutils", "pygments") @@ -84,13 +85,13 @@ def default(session): ) -@nox.session(python=["3.6", "3.7", "3.8"]) +@nox.session(python=UNIT_TEST_PYTHON_VERSIONS) def unit(session): """Run the unit test suite.""" default(session) -@nox.session(python=["3.7"]) +@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS) def system(session): """Run the system test suite.""" system_test_path = os.path.join("tests", "system.py") @@ -110,8 +111,7 @@ def system(session): # Install all test dependencies, then install this package into the # virtualenv's dist-packages. - session.install("mock", "pytest") - session.install("git+https://github.com/googleapis/python-test-utils") + session.install("mock", "pytest", "google-cloud-testutils") session.install("-e", ".") # Run py.test against the system tests. @@ -121,7 +121,7 @@ def system(session): session.run("py.test", "--quiet", system_test_folder_path, *session.posargs) -@nox.session(python="3.7") +@nox.session(python=DEFAULT_PYTHON_VERSION) def cover(session): """Run the final coverage report. @@ -134,12 +134,12 @@ def cover(session): session.run("coverage", "erase") -@nox.session(python="3.7") +@nox.session(python=DEFAULT_PYTHON_VERSION) def docs(session): """Build the docs for this library.""" session.install("-e", ".") - session.install("sphinx<3.0.0", "alabaster", "recommonmark") + session.install("sphinx", "alabaster", "recommonmark") shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) session.run( diff --git a/scripts/decrypt-secrets.sh b/scripts/decrypt-secrets.sh new file mode 100755 index 00000000..ff599eb2 --- /dev/null +++ b/scripts/decrypt-secrets.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Copyright 2015 Google Inc. All rights reserved. +# +# 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. + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ROOT=$( dirname "$DIR" ) + +# Work from the project root. +cd $ROOT + +# Use SECRET_MANAGER_PROJECT if set, fallback to cloud-devrel-kokoro-resources. +PROJECT_ID="${SECRET_MANAGER_PROJECT:-cloud-devrel-kokoro-resources}" + +gcloud secrets versions access latest --secret="python-docs-samples-test-env" \ + > testing/test-env.sh +gcloud secrets versions access latest \ + --secret="python-docs-samples-service-account" \ + > testing/service-account.json +gcloud secrets versions access latest \ + --secret="python-docs-samples-client-secrets" \ + > testing/client-secrets.json \ No newline at end of file diff --git a/scripts/readme-gen/readme_gen.py b/scripts/readme-gen/readme_gen.py new file mode 100644 index 00000000..d309d6e9 --- /dev/null +++ b/scripts/readme-gen/readme_gen.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +# Copyright 2016 Google Inc +# +# 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. + +"""Generates READMEs using configuration defined in yaml.""" + +import argparse +import io +import os +import subprocess + +import jinja2 +import yaml + + +jinja_env = jinja2.Environment( + trim_blocks=True, + loader=jinja2.FileSystemLoader( + os.path.abspath(os.path.join(os.path.dirname(__file__), 'templates')))) + +README_TMPL = jinja_env.get_template('README.tmpl.rst') + + +def get_help(file): + return subprocess.check_output(['python', file, '--help']).decode() + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('source') + parser.add_argument('--destination', default='README.rst') + + args = parser.parse_args() + + source = os.path.abspath(args.source) + root = os.path.dirname(source) + destination = os.path.join(root, args.destination) + + jinja_env.globals['get_help'] = get_help + + with io.open(source, 'r') as f: + config = yaml.load(f) + + # This allows get_help to execute in the right directory. + os.chdir(root) + + output = README_TMPL.render(config) + + with io.open(destination, 'w') as f: + f.write(output) + + +if __name__ == '__main__': + main() diff --git a/scripts/readme-gen/templates/README.tmpl.rst b/scripts/readme-gen/templates/README.tmpl.rst new file mode 100644 index 00000000..4fd23976 --- /dev/null +++ b/scripts/readme-gen/templates/README.tmpl.rst @@ -0,0 +1,87 @@ +{# The following line is a lie. BUT! Once jinja2 is done with it, it will + become truth! #} +.. This file is automatically generated. Do not edit this file directly. + +{{product.name}} Python Samples +=============================================================================== + +.. image:: https://gstatic.com/cloudssh/images/open-btn.png + :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor={{folder}}/README.rst + + +This directory contains samples for {{product.name}}. {{product.description}} + +{{description}} + +.. _{{product.name}}: {{product.url}} + +{% if required_api_url %} +To run the sample, you need to enable the API at: {{required_api_url}} +{% endif %} + +{% if required_role %} +To run the sample, you need to have `{{required_role}}` role. +{% endif %} + +{{other_required_steps}} + +{% if setup %} +Setup +------------------------------------------------------------------------------- + +{% for section in setup %} + +{% include section + '.tmpl.rst' %} + +{% endfor %} +{% endif %} + +{% if samples %} +Samples +------------------------------------------------------------------------------- + +{% for sample in samples %} +{{sample.name}} ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +{% if not sample.hide_cloudshell_button %} +.. image:: https://gstatic.com/cloudssh/images/open-btn.png + :target: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor={{folder}}/{{sample.file}},{{folder}}/README.rst +{% endif %} + + +{{sample.description}} + +To run this sample: + +.. code-block:: bash + + $ python {{sample.file}} +{% if sample.show_help %} + + {{get_help(sample.file)|indent}} +{% endif %} + + +{% endfor %} +{% endif %} + +{% if cloud_client_library %} + +The client library +------------------------------------------------------------------------------- + +This sample uses the `Google Cloud Client Library for Python`_. +You can read the documentation for more details on API usage and use GitHub +to `browse the source`_ and `report issues`_. + +.. _Google Cloud Client Library for Python: + https://googlecloudplatform.github.io/google-cloud-python/ +.. _browse the source: + https://github.com/GoogleCloudPlatform/google-cloud-python +.. _report issues: + https://github.com/GoogleCloudPlatform/google-cloud-python/issues + +{% endif %} + +.. _Google Cloud SDK: https://cloud.google.com/sdk/ \ No newline at end of file diff --git a/scripts/readme-gen/templates/auth.tmpl.rst b/scripts/readme-gen/templates/auth.tmpl.rst new file mode 100644 index 00000000..1446b94a --- /dev/null +++ b/scripts/readme-gen/templates/auth.tmpl.rst @@ -0,0 +1,9 @@ +Authentication +++++++++++++++ + +This sample requires you to have authentication setup. Refer to the +`Authentication Getting Started Guide`_ for instructions on setting up +credentials for applications. + +.. _Authentication Getting Started Guide: + https://cloud.google.com/docs/authentication/getting-started diff --git a/scripts/readme-gen/templates/auth_api_key.tmpl.rst b/scripts/readme-gen/templates/auth_api_key.tmpl.rst new file mode 100644 index 00000000..11957ce2 --- /dev/null +++ b/scripts/readme-gen/templates/auth_api_key.tmpl.rst @@ -0,0 +1,14 @@ +Authentication +++++++++++++++ + +Authentication for this service is done via an `API Key`_. To obtain an API +Key: + +1. Open the `Cloud Platform Console`_ +2. Make sure that billing is enabled for your project. +3. From the **Credentials** page, create a new **API Key** or use an existing + one for your project. + +.. _API Key: + https://developers.google.com/api-client-library/python/guide/aaa_apikeys +.. _Cloud Console: https://console.cloud.google.com/project?_ diff --git a/scripts/readme-gen/templates/install_deps.tmpl.rst b/scripts/readme-gen/templates/install_deps.tmpl.rst new file mode 100644 index 00000000..a0406dba --- /dev/null +++ b/scripts/readme-gen/templates/install_deps.tmpl.rst @@ -0,0 +1,29 @@ +Install Dependencies +++++++++++++++++++++ + +#. Clone python-docs-samples and change directory to the sample directory you want to use. + + .. code-block:: bash + + $ git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git + +#. Install `pip`_ and `virtualenv`_ if you do not already have them. You may want to refer to the `Python Development Environment Setup Guide`_ for Google Cloud Platform for instructions. + + .. _Python Development Environment Setup Guide: + https://cloud.google.com/python/setup + +#. Create a virtualenv. Samples are compatible with Python 2.7 and 3.4+. + + .. code-block:: bash + + $ virtualenv env + $ source env/bin/activate + +#. Install the dependencies needed to run the samples. + + .. code-block:: bash + + $ pip install -r requirements.txt + +.. _pip: https://pip.pypa.io/ +.. _virtualenv: https://virtualenv.pypa.io/ diff --git a/scripts/readme-gen/templates/install_portaudio.tmpl.rst b/scripts/readme-gen/templates/install_portaudio.tmpl.rst new file mode 100644 index 00000000..5ea33d18 --- /dev/null +++ b/scripts/readme-gen/templates/install_portaudio.tmpl.rst @@ -0,0 +1,35 @@ +Install PortAudio ++++++++++++++++++ + +Install `PortAudio`_. This is required by the `PyAudio`_ library to stream +audio from your computer's microphone. PyAudio depends on PortAudio for cross-platform compatibility, and is installed differently depending on the +platform. + +* For Mac OS X, you can use `Homebrew`_:: + + brew install portaudio + + **Note**: if you encounter an error when running `pip install` that indicates + it can't find `portaudio.h`, try running `pip install` with the following + flags:: + + pip install --global-option='build_ext' \ + --global-option='-I/usr/local/include' \ + --global-option='-L/usr/local/lib' \ + pyaudio + +* For Debian / Ubuntu Linux:: + + apt-get install portaudio19-dev python-all-dev + +* Windows may work without having to install PortAudio explicitly (it will get + installed with PyAudio). + +For more details, see the `PyAudio installation`_ page. + + +.. _PyAudio: https://people.csail.mit.edu/hubert/pyaudio/ +.. _PortAudio: http://www.portaudio.com/ +.. _PyAudio installation: + https://people.csail.mit.edu/hubert/pyaudio/#downloads +.. _Homebrew: http://brew.sh diff --git a/setup.py b/setup.py index 5db288d7..478b2dc0 100644 --- a/setup.py +++ b/setup.py @@ -40,9 +40,7 @@ platforms="Posix; MacOS X; Windows", include_package_data=True, install_requires=( - "google-api-core >= 1.17.0, < 2.0.0dev", - "googleapis-common-protos >= 1.5.8", - "grpcio >= 1.10.0", + "google-api-core >= 1.18.0, < 2.0.0dev", "proto-plus >= 0.4.0", "grpc-google-iam-v1", ), diff --git a/synth.metadata b/synth.metadata index a957e5e1..5bc6ed86 100644 --- a/synth.metadata +++ b/synth.metadata @@ -3,23 +3,30 @@ { "git": { "name": ".", - "remote": "https://github.com/googleapis/python-service-directory.git", - "sha": "6f15919759b7aff0da589b8e4c62bba17a43003e" + "remote": "git@github.com:googleapis/python-service-directory", + "sha": "9d7c27ee83b06f1d1fb7f757677dd1ccfbf3a9ad" } }, { "git": { "name": "googleapis", "remote": "https://github.com/googleapis/googleapis.git", - "sha": "bcc476396e799806d3355e87246c6becf6250a70", - "internalRef": "306974763" + "sha": "b789f790565ad7cc473571b0cf35dfbe6707f6a8", + "internalRef": "315933871" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "6980131905b652563280e4d2482384d4acc9eafc" + "sha": "a0ee80e0492a03b9b2bfefb5cca7eaf17bf10438" + } + }, + { + "git": { + "name": "synthtool", + "remote": "https://github.com/googleapis/synthtool.git", + "sha": "a0ee80e0492a03b9b2bfefb5cca7eaf17bf10438" } } ], diff --git a/synth.py b/synth.py index 97ebf343..47a1a768 100644 --- a/synth.py +++ b/synth.py @@ -31,10 +31,6 @@ s.move(library, excludes=["setup.py", "README.rst", "docs/index.rst"]) -# correct license headers -python.fix_pb2_headers() -python.fix_pb2_grpc_headers() - # rename library to google-cloud-service-directory s.replace(["google/**/*.py", "tests/**/*.py"], "google-cloud-servicedirectory", "google-cloud-service-directory") @@ -44,18 +40,17 @@ # ---------------------------------------------------------------------------- # Add templated files # ---------------------------------------------------------------------------- -templated_files = common.py_library(cov_level=100) -s.move(templated_files, excludes=[".coveragerc"]) # the microgenerator has a good coveragerc file -s.replace(".gitignore", "bigquery/docs/generated", "htmlcov") # temporary hack to ignore htmlcov +templated_files = common.py_library( + cov_level=100, + samples=False, + unit_test_python_versions=["3.6", "3.7", "3.8"], + system_test_python_versions=["3.7"], +) -# Remove 2.7 and 3.5 tests from noxfile.py -s.replace("noxfile.py", '''\["2\.7", ''', '[') -s.replace("noxfile.py", '''"3.5", ''', '') +s.move(templated_files, excludes=[".coveragerc"]) # the microgenerator has a good coveragerc file -# Expand flake errors permitted to accomodate the Microgenerator -# TODO: remove extra error codes once issues below are resolved -# F401: https://github.com/googleapis/gapic-generator-python/issues/324 -# F841: local variable 'client'/'response' is assigned to but never use -s.replace(".flake8", "ignore = .*", "ignore = E203, E266, E501, W503, F401, F841") +# Extra lint ignores for microgenerator tests +# TODO: Remove when https://github.com/googleapis/gapic-generator-python/issues/425 is closed +s.replace(".flake8", "(ignore = .*)", "\g<1>, F401, F841") s.shell.run(["nox", "-s", "blacken"], hide_output=False) \ No newline at end of file diff --git a/testing/.gitignore b/testing/.gitignore new file mode 100644 index 00000000..b05fbd63 --- /dev/null +++ b/testing/.gitignore @@ -0,0 +1,3 @@ +test-env.sh +service-account.json +client-secrets.json \ No newline at end of file diff --git a/tests/unit/servicedirectory_v1beta1/test_lookup_service.py b/tests/unit/servicedirectory_v1beta1/test_lookup_service.py index c6fdd5b0..c6d81b7f 100644 --- a/tests/unit/servicedirectory_v1beta1/test_lookup_service.py +++ b/tests/unit/servicedirectory_v1beta1/test_lookup_service.py @@ -15,6 +15,7 @@ # limitations under the License. # +import os from unittest import mock import grpc @@ -25,6 +26,7 @@ from google.api_core import client_options from google.api_core import grpc_helpers from google.auth import credentials +from google.auth.exceptions import MutualTLSChannelError from google.cloud.servicedirectory_v1beta1.services.lookup_service import ( LookupServiceClient, ) @@ -82,6 +84,14 @@ def test_lookup_service_client_from_service_account_file(): assert client._transport._host == "servicedirectory.googleapis.com:443" +def test_lookup_service_client_get_transport_class(): + transport = LookupServiceClient.get_transport_class() + assert transport == transports.LookupServiceGrpcTransport + + transport = LookupServiceClient.get_transport_class("grpc") + assert transport == transports.LookupServiceGrpcTransport + + def test_lookup_service_client_client_options(): # Check that if channel is provided we won't create a new one. with mock.patch( @@ -93,19 +103,14 @@ def test_lookup_service_client_client_options(): client = LookupServiceClient(transport=transport) gtc.assert_not_called() - # Check mTLS is not triggered with empty client options. - options = client_options.ClientOptions() + # Check that if channel is provided via str we will create a new one. with mock.patch( "google.cloud.servicedirectory_v1beta1.services.lookup_service.LookupServiceClient.get_transport_class" ) as gtc: - transport = gtc.return_value = mock.MagicMock() - client = LookupServiceClient(client_options=options) - transport.assert_called_once_with( - credentials=None, host=client.DEFAULT_ENDPOINT - ) + client = LookupServiceClient(transport="grpc") + gtc.assert_called() - # Check mTLS is not triggered if api_endpoint is provided but - # client_cert_source is None. + # Check the case api_endpoint is provided. options = client_options.ClientOptions(api_endpoint="squid.clam.whelk") with mock.patch( "google.cloud.servicedirectory_v1beta1.services.lookup_service.transports.LookupServiceGrpcTransport.__init__" @@ -113,31 +118,47 @@ def test_lookup_service_client_client_options(): grpc_transport.return_value = None client = LookupServiceClient(client_options=options) grpc_transport.assert_called_once_with( - api_mtls_endpoint=None, + api_mtls_endpoint="squid.clam.whelk", client_cert_source=None, credentials=None, host="squid.clam.whelk", ) - # Check mTLS is triggered if client_cert_source is provided. - options = client_options.ClientOptions( - client_cert_source=client_cert_source_callback - ) + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # "never". + os.environ["GOOGLE_API_USE_MTLS"] = "never" with mock.patch( "google.cloud.servicedirectory_v1beta1.services.lookup_service.transports.LookupServiceGrpcTransport.__init__" ) as grpc_transport: grpc_transport.return_value = None - client = LookupServiceClient(client_options=options) + client = LookupServiceClient() grpc_transport.assert_called_once_with( - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=client_cert_source_callback, + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, credentials=None, host=client.DEFAULT_ENDPOINT, ) - # Check mTLS is triggered if api_endpoint and client_cert_source are provided. + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # "always". + os.environ["GOOGLE_API_USE_MTLS"] = "always" + with mock.patch( + "google.cloud.servicedirectory_v1beta1.services.lookup_service.transports.LookupServiceGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = LookupServiceClient() + grpc_transport.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=None, + credentials=None, + host=client.DEFAULT_MTLS_ENDPOINT, + ) + + # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is + # "auto", and client_cert_source is provided. + os.environ["GOOGLE_API_USE_MTLS"] = "auto" options = client_options.ClientOptions( - api_endpoint="squid.clam.whelk", client_cert_source=client_cert_source_callback + client_cert_source=client_cert_source_callback ) with mock.patch( "google.cloud.servicedirectory_v1beta1.services.lookup_service.transports.LookupServiceGrpcTransport.__init__" @@ -145,12 +166,58 @@ def test_lookup_service_client_client_options(): grpc_transport.return_value = None client = LookupServiceClient(client_options=options) grpc_transport.assert_called_once_with( - api_mtls_endpoint="squid.clam.whelk", + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, client_cert_source=client_cert_source_callback, credentials=None, - host="squid.clam.whelk", + host=client.DEFAULT_MTLS_ENDPOINT, ) + # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is + # "auto", and default_client_cert_source is provided. + os.environ["GOOGLE_API_USE_MTLS"] = "auto" + with mock.patch( + "google.cloud.servicedirectory_v1beta1.services.lookup_service.transports.LookupServiceGrpcTransport.__init__" + ) as grpc_transport: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, + ): + grpc_transport.return_value = None + client = LookupServiceClient() + grpc_transport.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=None, + credentials=None, + host=client.DEFAULT_MTLS_ENDPOINT, + ) + + # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is + # "auto", but client_cert_source and default_client_cert_source are None. + os.environ["GOOGLE_API_USE_MTLS"] = "auto" + with mock.patch( + "google.cloud.servicedirectory_v1beta1.services.lookup_service.transports.LookupServiceGrpcTransport.__init__" + ) as grpc_transport: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + grpc_transport.return_value = None + client = LookupServiceClient() + grpc_transport.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + credentials=None, + host=client.DEFAULT_ENDPOINT, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has + # unsupported value. + os.environ["GOOGLE_API_USE_MTLS"] = "Unsupported" + with pytest.raises(MutualTLSChannelError): + client = LookupServiceClient() + + del os.environ["GOOGLE_API_USE_MTLS"] + def test_lookup_service_client_client_options_from_dict(): with mock.patch( @@ -161,7 +228,7 @@ def test_lookup_service_client_client_options_from_dict(): client_options={"api_endpoint": "squid.clam.whelk"} ) grpc_transport.assert_called_once_with( - api_mtls_endpoint=None, + api_mtls_endpoint="squid.clam.whelk", client_cert_source=None, credentials=None, host="squid.clam.whelk", @@ -194,6 +261,30 @@ def test_resolve_service(transport: str = "grpc"): assert isinstance(response, lookup_service.ResolveServiceResponse) +def test_resolve_service_field_headers(): + client = LookupServiceClient(credentials=credentials.AnonymousCredentials()) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = lookup_service.ResolveServiceRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.resolve_service), "__call__") as call: + call.return_value = lookup_service.ResolveServiceResponse() + + client.resolve_service(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value") in kw["metadata"] + + def test_credentials_transport_error(): # It is an error to provide credentials and a transport instance. transport = transports.LookupServiceGrpcTransport( @@ -244,13 +335,23 @@ def test_lookup_service_auth_adc(): ) +def test_lookup_service_transport_auth_adc(): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transports.LookupServiceGrpcTransport(host="squid.clam.whelk") + adc.assert_called_once_with( + scopes=("https://www.googleapis.com/auth/cloud-platform",) + ) + + def test_lookup_service_host_no_port(): client = LookupServiceClient( credentials=credentials.AnonymousCredentials(), client_options=client_options.ClientOptions( api_endpoint="servicedirectory.googleapis.com" ), - transport="grpc", ) assert client._transport._host == "servicedirectory.googleapis.com:443" @@ -261,7 +362,6 @@ def test_lookup_service_host_with_port(): client_options=client_options.ClientOptions( api_endpoint="servicedirectory.googleapis.com:8000" ), - transport="grpc", ) assert client._transport._host == "servicedirectory.googleapis.com:8000" diff --git a/tests/unit/servicedirectory_v1beta1/test_registration_service.py b/tests/unit/servicedirectory_v1beta1/test_registration_service.py index 56a58ac2..695f0d8f 100644 --- a/tests/unit/servicedirectory_v1beta1/test_registration_service.py +++ b/tests/unit/servicedirectory_v1beta1/test_registration_service.py @@ -15,6 +15,7 @@ # limitations under the License. # +import os from unittest import mock import grpc @@ -25,6 +26,7 @@ from google.api_core import client_options from google.api_core import grpc_helpers from google.auth import credentials +from google.auth.exceptions import MutualTLSChannelError from google.cloud.servicedirectory_v1beta1.services.registration_service import ( RegistrationServiceClient, ) @@ -100,6 +102,14 @@ def test_registration_service_client_from_service_account_file(): assert client._transport._host == "servicedirectory.googleapis.com:443" +def test_registration_service_client_get_transport_class(): + transport = RegistrationServiceClient.get_transport_class() + assert transport == transports.RegistrationServiceGrpcTransport + + transport = RegistrationServiceClient.get_transport_class("grpc") + assert transport == transports.RegistrationServiceGrpcTransport + + def test_registration_service_client_client_options(): # Check that if channel is provided we won't create a new one. with mock.patch( @@ -111,19 +121,14 @@ def test_registration_service_client_client_options(): client = RegistrationServiceClient(transport=transport) gtc.assert_not_called() - # Check mTLS is not triggered with empty client options. - options = client_options.ClientOptions() + # Check that if channel is provided via str we will create a new one. with mock.patch( "google.cloud.servicedirectory_v1beta1.services.registration_service.RegistrationServiceClient.get_transport_class" ) as gtc: - transport = gtc.return_value = mock.MagicMock() - client = RegistrationServiceClient(client_options=options) - transport.assert_called_once_with( - credentials=None, host=client.DEFAULT_ENDPOINT - ) + client = RegistrationServiceClient(transport="grpc") + gtc.assert_called() - # Check mTLS is not triggered if api_endpoint is provided but - # client_cert_source is None. + # Check the case api_endpoint is provided. options = client_options.ClientOptions(api_endpoint="squid.clam.whelk") with mock.patch( "google.cloud.servicedirectory_v1beta1.services.registration_service.transports.RegistrationServiceGrpcTransport.__init__" @@ -131,31 +136,47 @@ def test_registration_service_client_client_options(): grpc_transport.return_value = None client = RegistrationServiceClient(client_options=options) grpc_transport.assert_called_once_with( - api_mtls_endpoint=None, + api_mtls_endpoint="squid.clam.whelk", client_cert_source=None, credentials=None, host="squid.clam.whelk", ) - # Check mTLS is triggered if client_cert_source is provided. - options = client_options.ClientOptions( - client_cert_source=client_cert_source_callback - ) + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # "never". + os.environ["GOOGLE_API_USE_MTLS"] = "never" with mock.patch( "google.cloud.servicedirectory_v1beta1.services.registration_service.transports.RegistrationServiceGrpcTransport.__init__" ) as grpc_transport: grpc_transport.return_value = None - client = RegistrationServiceClient(client_options=options) + client = RegistrationServiceClient() grpc_transport.assert_called_once_with( - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=client_cert_source_callback, + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, credentials=None, host=client.DEFAULT_ENDPOINT, ) - # Check mTLS is triggered if api_endpoint and client_cert_source are provided. + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # "always". + os.environ["GOOGLE_API_USE_MTLS"] = "always" + with mock.patch( + "google.cloud.servicedirectory_v1beta1.services.registration_service.transports.RegistrationServiceGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = RegistrationServiceClient() + grpc_transport.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=None, + credentials=None, + host=client.DEFAULT_MTLS_ENDPOINT, + ) + + # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is + # "auto", and client_cert_source is provided. + os.environ["GOOGLE_API_USE_MTLS"] = "auto" options = client_options.ClientOptions( - api_endpoint="squid.clam.whelk", client_cert_source=client_cert_source_callback + client_cert_source=client_cert_source_callback ) with mock.patch( "google.cloud.servicedirectory_v1beta1.services.registration_service.transports.RegistrationServiceGrpcTransport.__init__" @@ -163,12 +184,58 @@ def test_registration_service_client_client_options(): grpc_transport.return_value = None client = RegistrationServiceClient(client_options=options) grpc_transport.assert_called_once_with( - api_mtls_endpoint="squid.clam.whelk", + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, client_cert_source=client_cert_source_callback, credentials=None, - host="squid.clam.whelk", + host=client.DEFAULT_MTLS_ENDPOINT, ) + # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is + # "auto", and default_client_cert_source is provided. + os.environ["GOOGLE_API_USE_MTLS"] = "auto" + with mock.patch( + "google.cloud.servicedirectory_v1beta1.services.registration_service.transports.RegistrationServiceGrpcTransport.__init__" + ) as grpc_transport: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, + ): + grpc_transport.return_value = None + client = RegistrationServiceClient() + grpc_transport.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=None, + credentials=None, + host=client.DEFAULT_MTLS_ENDPOINT, + ) + + # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is + # "auto", but client_cert_source and default_client_cert_source are None. + os.environ["GOOGLE_API_USE_MTLS"] = "auto" + with mock.patch( + "google.cloud.servicedirectory_v1beta1.services.registration_service.transports.RegistrationServiceGrpcTransport.__init__" + ) as grpc_transport: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + grpc_transport.return_value = None + client = RegistrationServiceClient() + grpc_transport.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + credentials=None, + host=client.DEFAULT_ENDPOINT, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has + # unsupported value. + os.environ["GOOGLE_API_USE_MTLS"] = "Unsupported" + with pytest.raises(MutualTLSChannelError): + client = RegistrationServiceClient() + + del os.environ["GOOGLE_API_USE_MTLS"] + def test_registration_service_client_client_options_from_dict(): with mock.patch( @@ -179,7 +246,7 @@ def test_registration_service_client_client_options_from_dict(): client_options={"api_endpoint": "squid.clam.whelk"} ) grpc_transport.assert_called_once_with( - api_mtls_endpoint=None, + api_mtls_endpoint="squid.clam.whelk", client_cert_source=None, credentials=None, host="squid.clam.whelk", @@ -215,6 +282,32 @@ def test_create_namespace(transport: str = "grpc"): assert response.name == "name_value" +def test_create_namespace_field_headers(): + client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = registration_service.CreateNamespaceRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.create_namespace), "__call__" + ) as call: + call.return_value = gcs_namespace.Namespace() + + client.create_namespace(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value") in kw["metadata"] + + def test_create_namespace_flattened(): client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) @@ -227,7 +320,7 @@ def test_create_namespace_flattened(): # Call the method with a truthy value for each flattened field, # using the keyword arguments to the method. - response = client.create_namespace( + client.create_namespace( parent="parent_value", namespace=gcs_namespace.Namespace(name="name_value"), namespace_id="namespace_id_value", @@ -290,11 +383,13 @@ def test_list_namespaces_field_headers(): # Any value that is part of the HTTP/1.1 URI should be sent as # a field header. Set these to a non-empty value. - request = registration_service.ListNamespacesRequest(parent="parent/value") + request = registration_service.ListNamespacesRequest() + request.parent = "parent/value" # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object(type(client._transport.list_namespaces), "__call__") as call: call.return_value = registration_service.ListNamespacesResponse() + client.list_namespaces(request) # Establish that the underlying gRPC stub method was called. @@ -317,7 +412,7 @@ def test_list_namespaces_flattened(): # Call the method with a truthy value for each flattened field, # using the keyword arguments to the method. - response = client.list_namespaces(parent="parent_value") + client.list_namespaces(parent="parent_value") # Establish that the underlying call was made with the expected # request object values. @@ -431,11 +526,13 @@ def test_get_namespace_field_headers(): # Any value that is part of the HTTP/1.1 URI should be sent as # a field header. Set these to a non-empty value. - request = registration_service.GetNamespaceRequest(name="name/value") + request = registration_service.GetNamespaceRequest() + request.name = "name/value" # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object(type(client._transport.get_namespace), "__call__") as call: call.return_value = namespace.Namespace() + client.get_namespace(request) # Establish that the underlying gRPC stub method was called. @@ -458,7 +555,7 @@ def test_get_namespace_flattened(): # Call the method with a truthy value for each flattened field, # using the keyword arguments to the method. - response = client.get_namespace(name="name_value") + client.get_namespace(name="name_value") # Establish that the underlying call was made with the expected # request object values. @@ -507,6 +604,34 @@ def test_update_namespace(transport: str = "grpc"): assert response.name == "name_value" +def test_update_namespace_field_headers(): + client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = registration_service.UpdateNamespaceRequest() + request.namespace.name = "namespace.name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.update_namespace), "__call__" + ) as call: + call.return_value = gcs_namespace.Namespace() + + client.update_namespace(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "namespace.name=namespace.name/value") in kw[ + "metadata" + ] + + def test_update_namespace_flattened(): client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) @@ -519,7 +644,7 @@ def test_update_namespace_flattened(): # Call the method with a truthy value for each flattened field, # using the keyword arguments to the method. - response = client.update_namespace( + client.update_namespace( namespace=gcs_namespace.Namespace(name="name_value"), update_mask=field_mask.FieldMask(paths=["paths_value"]), ) @@ -573,6 +698,32 @@ def test_delete_namespace(transport: str = "grpc"): assert response is None +def test_delete_namespace_field_headers(): + client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = registration_service.DeleteNamespaceRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.delete_namespace), "__call__" + ) as call: + call.return_value = None + + client.delete_namespace(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value") in kw["metadata"] + + def test_delete_namespace_flattened(): client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) @@ -585,7 +736,7 @@ def test_delete_namespace_flattened(): # Call the method with a truthy value for each flattened field, # using the keyword arguments to the method. - response = client.delete_namespace(name="name_value") + client.delete_namespace(name="name_value") # Establish that the underlying call was made with the expected # request object values. @@ -632,6 +783,30 @@ def test_create_service(transport: str = "grpc"): assert response.name == "name_value" +def test_create_service_field_headers(): + client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = registration_service.CreateServiceRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.create_service), "__call__") as call: + call.return_value = gcs_service.Service() + + client.create_service(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value") in kw["metadata"] + + def test_create_service_flattened(): client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) @@ -642,7 +817,7 @@ def test_create_service_flattened(): # Call the method with a truthy value for each flattened field, # using the keyword arguments to the method. - response = client.create_service( + client.create_service( parent="parent_value", service=gcs_service.Service(name="name_value"), service_id="service_id_value", @@ -705,11 +880,13 @@ def test_list_services_field_headers(): # Any value that is part of the HTTP/1.1 URI should be sent as # a field header. Set these to a non-empty value. - request = registration_service.ListServicesRequest(parent="parent/value") + request = registration_service.ListServicesRequest() + request.parent = "parent/value" # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object(type(client._transport.list_services), "__call__") as call: call.return_value = registration_service.ListServicesResponse() + client.list_services(request) # Establish that the underlying gRPC stub method was called. @@ -732,7 +909,7 @@ def test_list_services_flattened(): # Call the method with a truthy value for each flattened field, # using the keyword arguments to the method. - response = client.list_services(parent="parent_value") + client.list_services(parent="parent_value") # Establish that the underlying call was made with the expected # request object values. @@ -838,11 +1015,13 @@ def test_get_service_field_headers(): # Any value that is part of the HTTP/1.1 URI should be sent as # a field header. Set these to a non-empty value. - request = registration_service.GetServiceRequest(name="name/value") + request = registration_service.GetServiceRequest() + request.name = "name/value" # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object(type(client._transport.get_service), "__call__") as call: call.return_value = service.Service() + client.get_service(request) # Establish that the underlying gRPC stub method was called. @@ -865,7 +1044,7 @@ def test_get_service_flattened(): # Call the method with a truthy value for each flattened field, # using the keyword arguments to the method. - response = client.get_service(name="name_value") + client.get_service(name="name_value") # Establish that the underlying call was made with the expected # request object values. @@ -910,6 +1089,32 @@ def test_update_service(transport: str = "grpc"): assert response.name == "name_value" +def test_update_service_field_headers(): + client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = registration_service.UpdateServiceRequest() + request.service.name = "service.name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.update_service), "__call__") as call: + call.return_value = gcs_service.Service() + + client.update_service(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "service.name=service.name/value") in kw[ + "metadata" + ] + + def test_update_service_flattened(): client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) @@ -920,7 +1125,7 @@ def test_update_service_flattened(): # Call the method with a truthy value for each flattened field, # using the keyword arguments to the method. - response = client.update_service( + client.update_service( service=gcs_service.Service(name="name_value"), update_mask=field_mask.FieldMask(paths=["paths_value"]), ) @@ -972,6 +1177,30 @@ def test_delete_service(transport: str = "grpc"): assert response is None +def test_delete_service_field_headers(): + client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = registration_service.DeleteServiceRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.delete_service), "__call__") as call: + call.return_value = None + + client.delete_service(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value") in kw["metadata"] + + def test_delete_service_flattened(): client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) @@ -982,7 +1211,7 @@ def test_delete_service_flattened(): # Call the method with a truthy value for each flattened field, # using the keyword arguments to the method. - response = client.delete_service(name="name_value") + client.delete_service(name="name_value") # Establish that the underlying call was made with the expected # request object values. @@ -1033,6 +1262,30 @@ def test_create_endpoint(transport: str = "grpc"): assert response.port == 453 +def test_create_endpoint_field_headers(): + client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = registration_service.CreateEndpointRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.create_endpoint), "__call__") as call: + call.return_value = gcs_endpoint.Endpoint() + + client.create_endpoint(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value") in kw["metadata"] + + def test_create_endpoint_flattened(): client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) @@ -1043,7 +1296,7 @@ def test_create_endpoint_flattened(): # Call the method with a truthy value for each flattened field, # using the keyword arguments to the method. - response = client.create_endpoint( + client.create_endpoint( parent="parent_value", endpoint=gcs_endpoint.Endpoint(name="name_value"), endpoint_id="endpoint_id_value", @@ -1106,11 +1359,13 @@ def test_list_endpoints_field_headers(): # Any value that is part of the HTTP/1.1 URI should be sent as # a field header. Set these to a non-empty value. - request = registration_service.ListEndpointsRequest(parent="parent/value") + request = registration_service.ListEndpointsRequest() + request.parent = "parent/value" # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object(type(client._transport.list_endpoints), "__call__") as call: call.return_value = registration_service.ListEndpointsResponse() + client.list_endpoints(request) # Establish that the underlying gRPC stub method was called. @@ -1133,7 +1388,7 @@ def test_list_endpoints_flattened(): # Call the method with a truthy value for each flattened field, # using the keyword arguments to the method. - response = client.list_endpoints(parent="parent_value") + client.list_endpoints(parent="parent_value") # Establish that the underlying call was made with the expected # request object values. @@ -1251,11 +1506,13 @@ def test_get_endpoint_field_headers(): # Any value that is part of the HTTP/1.1 URI should be sent as # a field header. Set these to a non-empty value. - request = registration_service.GetEndpointRequest(name="name/value") + request = registration_service.GetEndpointRequest() + request.name = "name/value" # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object(type(client._transport.get_endpoint), "__call__") as call: call.return_value = endpoint.Endpoint() + client.get_endpoint(request) # Establish that the underlying gRPC stub method was called. @@ -1278,7 +1535,7 @@ def test_get_endpoint_flattened(): # Call the method with a truthy value for each flattened field, # using the keyword arguments to the method. - response = client.get_endpoint(name="name_value") + client.get_endpoint(name="name_value") # Establish that the underlying call was made with the expected # request object values. @@ -1329,6 +1586,32 @@ def test_update_endpoint(transport: str = "grpc"): assert response.port == 453 +def test_update_endpoint_field_headers(): + client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = registration_service.UpdateEndpointRequest() + request.endpoint.name = "endpoint.name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.update_endpoint), "__call__") as call: + call.return_value = gcs_endpoint.Endpoint() + + client.update_endpoint(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "endpoint.name=endpoint.name/value") in kw[ + "metadata" + ] + + def test_update_endpoint_flattened(): client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) @@ -1339,7 +1622,7 @@ def test_update_endpoint_flattened(): # Call the method with a truthy value for each flattened field, # using the keyword arguments to the method. - response = client.update_endpoint( + client.update_endpoint( endpoint=gcs_endpoint.Endpoint(name="name_value"), update_mask=field_mask.FieldMask(paths=["paths_value"]), ) @@ -1391,6 +1674,30 @@ def test_delete_endpoint(transport: str = "grpc"): assert response is None +def test_delete_endpoint_field_headers(): + client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = registration_service.DeleteEndpointRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.delete_endpoint), "__call__") as call: + call.return_value = None + + client.delete_endpoint(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value") in kw["metadata"] + + def test_delete_endpoint_flattened(): client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) @@ -1401,7 +1708,7 @@ def test_delete_endpoint_flattened(): # Call the method with a truthy value for each flattened field, # using the keyword arguments to the method. - response = client.delete_endpoint(name="name_value") + client.delete_endpoint(name="name_value") # Establish that the underlying call was made with the expected # request object values. @@ -1449,6 +1756,30 @@ def test_get_iam_policy(transport: str = "grpc"): assert response.etag == b"etag_blob" +def test_get_iam_policy_field_headers(): + client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = iam_policy.GetIamPolicyRequest() + request.resource = "resource/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.get_iam_policy), "__call__") as call: + call.return_value = policy.Policy() + + client.get_iam_policy(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "resource=resource/value") in kw["metadata"] + + def test_get_iam_policy_from_dict(): client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) # Mock the actual call within the gRPC stub, and fake the request. @@ -1493,6 +1824,30 @@ def test_set_iam_policy(transport: str = "grpc"): assert response.etag == b"etag_blob" +def test_set_iam_policy_field_headers(): + client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = iam_policy.SetIamPolicyRequest() + request.resource = "resource/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.set_iam_policy), "__call__") as call: + call.return_value = policy.Policy() + + client.set_iam_policy(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "resource=resource/value") in kw["metadata"] + + def test_set_iam_policy_from_dict(): client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) # Mock the actual call within the gRPC stub, and fake the request. @@ -1537,6 +1892,32 @@ def test_test_iam_permissions(transport: str = "grpc"): assert response.permissions == ["permissions_value"] +def test_test_iam_permissions_field_headers(): + client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = iam_policy.TestIamPermissionsRequest() + request.resource = "resource/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.test_iam_permissions), "__call__" + ) as call: + call.return_value = iam_policy.TestIamPermissionsResponse() + + client.test_iam_permissions(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "resource=resource/value") in kw["metadata"] + + def test_test_iam_permissions_from_dict(): client = RegistrationServiceClient(credentials=credentials.AnonymousCredentials()) # Mock the actual call within the gRPC stub, and fake the request. @@ -1621,13 +2002,23 @@ def test_registration_service_auth_adc(): ) +def test_registration_service_transport_auth_adc(): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transports.RegistrationServiceGrpcTransport(host="squid.clam.whelk") + adc.assert_called_once_with( + scopes=("https://www.googleapis.com/auth/cloud-platform",) + ) + + def test_registration_service_host_no_port(): client = RegistrationServiceClient( credentials=credentials.AnonymousCredentials(), client_options=client_options.ClientOptions( api_endpoint="servicedirectory.googleapis.com" ), - transport="grpc", ) assert client._transport._host == "servicedirectory.googleapis.com:443" @@ -1638,7 +2029,6 @@ def test_registration_service_host_with_port(): client_options=client_options.ClientOptions( api_endpoint="servicedirectory.googleapis.com:8000" ), - transport="grpc", ) assert client._transport._host == "servicedirectory.googleapis.com:8000"