diff --git a/.coveragerc b/.coveragerc index b16f6fc2..054c95ee 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,26 +1,11 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - [run] branch = True [report] fail_under = 100 show_missing = True -omit = google/cloud/devtools/cloudtrace/__init__.py +omit = + google/cloud/trace/__init__.py exclude_lines = # Re-enable the standard pragma pragma: NO COVER @@ -30,4 +15,4 @@ exclude_lines = # This is added at the module level as a safeguard for if someone # generates the code and tries to run it without pip installing. This # makes it virtually impossible to test properly. - except pkg_resources.DistributionNotFound \ No newline at end of file + except pkg_resources.DistributionNotFound diff --git a/.flake8 b/.flake8 index ed931638..29227d4c 100644 --- a/.flake8 +++ b/.flake8 @@ -26,6 +26,7 @@ exclude = *_pb2.py # Standard linting exemptions. + **/.nox/** __pycache__, .git, *.pyc, diff --git a/.github/header-checker-lint.yml b/.github/header-checker-lint.yml new file mode 100644 index 00000000..fc281c05 --- /dev/null +++ b/.github/header-checker-lint.yml @@ -0,0 +1,15 @@ +{"allowedCopyrightHolders": ["Google LLC"], + "allowedLicenses": ["Apache-2.0", "MIT", "BSD-3"], + "ignoreFiles": ["**/requirements.txt", "**/requirements-test.txt"], + "sourceFileExtensions": [ + "ts", + "js", + "java", + "sh", + "Dockerfile", + "yaml", + "py", + "html", + "txt" + ] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index b9daa52f..b4243ced 100644 --- a/.gitignore +++ b/.gitignore @@ -50,8 +50,10 @@ docs.metadata # Virtual environment env/ + +# Test logs coverage.xml -sponge_log.xml +*sponge_log.xml # System test environment variables. system_tests/local_test_setup diff --git a/.kokoro/build.sh b/.kokoro/build.sh index 40ae9af9..f4484f2a 100755 --- a/.kokoro/build.sh +++ b/.kokoro/build.sh @@ -15,7 +15,11 @@ set -eo pipefail -cd github/python-trace +if [[ -z "${PROJECT_ROOT:-}" ]]; then + PROJECT_ROOT="github/python-trace" +fi + +cd "${PROJECT_ROOT}" # Disable buffering, so that the logs stream through. export PYTHONUNBUFFERED=1 @@ -30,16 +34,26 @@ export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/service-account.json export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.json") # Remove old nox -python3.6 -m pip uninstall --yes --quiet nox-automation +python3 -m pip uninstall --yes --quiet nox-automation # Install nox -python3.6 -m pip install --upgrade --quiet nox -python3.6 -m nox --version +python3 -m pip install --upgrade --quiet nox +python3 -m nox --version + +# If this is a continuous build, send the test log to the FlakyBot. +# See https://github.com/googleapis/repo-automation-bots/tree/master/packages/flakybot. +if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]]; then + cleanup() { + chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot + $KOKORO_GFILE_DIR/linux_amd64/flakybot + } + trap cleanup EXIT HUP +fi # If NOX_SESSION is set, it only runs the specified session, # otherwise run all the sessions. if [[ -n "${NOX_SESSION:-}" ]]; then - python3.6 -m nox -s "${NOX_SESSION:-}" + python3 -m nox -s ${NOX_SESSION:-} else - python3.6 -m nox + python3 -m nox fi diff --git a/.kokoro/docs/common.cfg b/.kokoro/docs/common.cfg index 924821ed..015f2f6b 100644 --- a/.kokoro/docs/common.cfg +++ b/.kokoro/docs/common.cfg @@ -30,7 +30,7 @@ env_vars: { env_vars: { key: "V2_STAGING_BUCKET" - value: "docs-staging-v2-staging" + value: "docs-staging-v2" } # It will upload the docker image after successful builds. diff --git a/.kokoro/docs/docs-presubmit.cfg b/.kokoro/docs/docs-presubmit.cfg index 11181078..77b74e00 100644 --- a/.kokoro/docs/docs-presubmit.cfg +++ b/.kokoro/docs/docs-presubmit.cfg @@ -15,3 +15,14 @@ env_vars: { key: "TRAMPOLINE_IMAGE_UPLOAD" value: "false" } + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-trace/.kokoro/build.sh" +} + +# Only run this nox session. +env_vars: { + key: "NOX_SESSION" + value: "docs docfx" +} diff --git a/.kokoro/populate-secrets.sh b/.kokoro/populate-secrets.sh new file mode 100755 index 00000000..f5251425 --- /dev/null +++ b/.kokoro/populate-secrets.sh @@ -0,0 +1,43 @@ +#!/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 +# +# 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. + +set -eo pipefail + +function now { date +"%Y-%m-%d %H:%M:%S" | tr -d '\n' ;} +function msg { println "$*" >&2 ;} +function println { printf '%s\n' "$(now) $*" ;} + + +# Populates requested secrets set in SECRET_MANAGER_KEYS from service account: +# kokoro-trampoline@cloud-devrel-kokoro-resources.iam.gserviceaccount.com +SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager" +msg "Creating folder on disk for secrets: ${SECRET_LOCATION}" +mkdir -p ${SECRET_LOCATION} +for key in $(echo ${SECRET_MANAGER_KEYS} | sed "s/,/ /g") +do + msg "Retrieving secret ${key}" + docker run --entrypoint=gcloud \ + --volume=${KOKORO_GFILE_DIR}:${KOKORO_GFILE_DIR} \ + gcr.io/google.com/cloudsdktool/cloud-sdk \ + secrets versions access latest \ + --project cloud-devrel-kokoro-resources \ + --secret ${key} > \ + "${SECRET_LOCATION}/${key}" + if [[ $? == 0 ]]; then + msg "Secret written to ${SECRET_LOCATION}/${key}" + else + msg "Error retrieving secret ${key}" + fi +done diff --git a/.kokoro/release/common.cfg b/.kokoro/release/common.cfg index b3df8aba..f1e287cb 100644 --- a/.kokoro/release/common.cfg +++ b/.kokoro/release/common.cfg @@ -23,42 +23,18 @@ env_vars: { value: "github/python-trace/.kokoro/release.sh" } -# Fetch the token needed for reporting release status to GitHub -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "yoshi-automation-github-key" - } - } -} - -# Fetch PyPI password -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "google_cloud_pypi_password" - } - } -} - -# Fetch magictoken to use with Magic Github Proxy -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "releasetool-magictoken" - } - } +# Fetch PyPI password +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "google_cloud_pypi_password" + } + } } -# Fetch api key to use with Magic Github Proxy -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "magic-github-proxy-api-key" - } - } -} +# Tokens needed to report release status back to GitHub +env_vars: { + key: "SECRET_MANAGER_KEYS" + value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.6/common.cfg b/.kokoro/samples/python3.6/common.cfg index 671d6605..74a50800 100644 --- a/.kokoro/samples/python3.6/common.cfg +++ b/.kokoro/samples/python3.6/common.cfg @@ -13,6 +13,12 @@ env_vars: { value: "py-3.6" } +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-py36" +} + env_vars: { key: "TRAMPOLINE_BUILD_FILE" value: "github/python-trace/.kokoro/test-samples.sh" diff --git a/.kokoro/samples/python3.6/periodic-head.cfg b/.kokoro/samples/python3.6/periodic-head.cfg new file mode 100644 index 00000000..f9cfcd33 --- /dev/null +++ b/.kokoro/samples/python3.6/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-pubsub/.kokoro/test-samples-against-head.sh" +} diff --git a/.kokoro/samples/python3.7/common.cfg b/.kokoro/samples/python3.7/common.cfg index cf504173..8549f421 100644 --- a/.kokoro/samples/python3.7/common.cfg +++ b/.kokoro/samples/python3.7/common.cfg @@ -13,6 +13,12 @@ env_vars: { value: "py-3.7" } +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-py37" +} + env_vars: { key: "TRAMPOLINE_BUILD_FILE" value: "github/python-trace/.kokoro/test-samples.sh" diff --git a/.kokoro/samples/python3.7/periodic-head.cfg b/.kokoro/samples/python3.7/periodic-head.cfg new file mode 100644 index 00000000..f9cfcd33 --- /dev/null +++ b/.kokoro/samples/python3.7/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-pubsub/.kokoro/test-samples-against-head.sh" +} diff --git a/.kokoro/samples/python3.8/common.cfg b/.kokoro/samples/python3.8/common.cfg index a0a3be93..74fb9be8 100644 --- a/.kokoro/samples/python3.8/common.cfg +++ b/.kokoro/samples/python3.8/common.cfg @@ -13,6 +13,12 @@ env_vars: { value: "py-3.8" } +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-py38" +} + env_vars: { key: "TRAMPOLINE_BUILD_FILE" value: "github/python-trace/.kokoro/test-samples.sh" diff --git a/.kokoro/samples/python3.8/periodic-head.cfg b/.kokoro/samples/python3.8/periodic-head.cfg new file mode 100644 index 00000000..f9cfcd33 --- /dev/null +++ b/.kokoro/samples/python3.8/periodic-head.cfg @@ -0,0 +1,11 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +env_vars: { + key: "INSTALL_LIBRARY_FROM_SOURCE" + value: "True" +} + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/python-pubsub/.kokoro/test-samples-against-head.sh" +} diff --git a/tests/system/gapic/v1/test_system_trace_service_v1.py b/.kokoro/test-samples-against-head.sh old mode 100644 new mode 100755 similarity index 54% rename from tests/system/gapic/v1/test_system_trace_service_v1.py rename to .kokoro/test-samples-against-head.sh index d6bc7a78..821d7944 --- a/tests/system/gapic/v1/test_system_trace_service_v1.py +++ b/.kokoro/test-samples-against-head.sh @@ -1,4 +1,5 @@ -# Copyright 2018 Google LLC +#!/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. @@ -12,16 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os -import time - -from google.cloud import trace_v1 +# A customized test runner for samples. +# +# For periodic builds, you can specify this file for testing against head. +# `-e` enables the script to automatically fail when a command fails +# `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero +set -eo pipefail +# Enables `**` to include files nested inside sub-folders +shopt -s globstar -class TestSystemTraceService(object): - def test_list_traces(self): - project_id = os.environ["PROJECT_ID"] +cd github/python-trace - client = trace_v1.TraceServiceClient() - project_id_2 = project_id - response = client.list_traces(project_id=project_id_2) +exec .kokoro/test-samples-impl.sh diff --git a/.kokoro/test-samples-impl.sh b/.kokoro/test-samples-impl.sh new file mode 100755 index 00000000..cf5de74c --- /dev/null +++ b/.kokoro/test-samples-impl.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# `-e` enables the script to automatically fail when a command fails +# `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero +set -eo pipefail +# Enables `**` to include files nested inside sub-folders +shopt -s globstar + +# Exit early if samples directory doesn't exist +if [ ! -d "./samples" ]; then + echo "No tests run. `./samples` not found" + exit 0 +fi + +# Disable buffering, so that the logs stream through. +export PYTHONUNBUFFERED=1 + +# Debug: show build environment +env | grep KOKORO + +# Install nox +python3.6 -m pip install --upgrade --quiet nox + +# Use secrets acessor service account to get secrets +if [[ -f "${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" ]]; then + gcloud auth activate-service-account \ + --key-file="${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" \ + --project="cloud-devrel-kokoro-resources" +fi + +# This script will create 3 files: +# - testing/test-env.sh +# - testing/service-account.json +# - testing/client-secrets.json +./scripts/decrypt-secrets.sh + +source ./testing/test-env.sh +export GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/testing/service-account.json + +# For cloud-run session, we activate the service account for gcloud sdk. +gcloud auth activate-service-account \ + --key-file "${GOOGLE_APPLICATION_CREDENTIALS}" + +export GOOGLE_CLIENT_SECRETS=$(pwd)/testing/client-secrets.json + +echo -e "\n******************** TESTING PROJECTS ********************" + +# Switch to 'fail at end' to allow all tests to complete before exiting. +set +e +# Use RTN to return a non-zero value if the test fails. +RTN=0 +ROOT=$(pwd) +# Find all requirements.txt in the samples directory (may break on whitespace). +for file in samples/**/requirements.txt; do + cd "$ROOT" + # Navigate to the project folder. + file=$(dirname "$file") + cd "$file" + + echo "------------------------------------------------------------" + echo "- testing $file" + echo "------------------------------------------------------------" + + # Use nox to execute the tests for the project. + python3.6 -m nox -s "$RUN_TESTS_SESSION" + EXIT=$? + + # If this is a periodic build, send the test log to the FlakyBot. + # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/flakybot. + if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then + chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot + $KOKORO_GFILE_DIR/linux_amd64/flakybot + fi + + if [[ $EXIT -ne 0 ]]; then + RTN=1 + echo -e "\n Testing failed: Nox returned a non-zero exit code. \n" + else + echo -e "\n Testing completed.\n" + fi + +done +cd "$ROOT" + +# Workaround for Kokoro permissions issue: delete secrets +rm testing/{test-env.sh,client-secrets.json,service-account.json} + +exit "$RTN" diff --git a/.kokoro/test-samples.sh b/.kokoro/test-samples.sh index 2dbdf8a7..4fb9bf1f 100755 --- a/.kokoro/test-samples.sh +++ b/.kokoro/test-samples.sh @@ -13,6 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +# The default test runner for samples. +# +# For periodic builds, we rewinds the repo to the latest release, and +# run test-samples-impl.sh. # `-e` enables the script to automatically fail when a command fails # `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero @@ -24,81 +28,19 @@ cd github/python-trace # Run periodic samples tests at latest release if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then + # preserving the test runner implementation. + cp .kokoro/test-samples-impl.sh "${TMPDIR}/test-samples-impl.sh" + echo "--- IMPORTANT IMPORTANT IMPORTANT ---" + echo "Now we rewind the repo back to the latest release..." LATEST_RELEASE=$(git describe --abbrev=0 --tags) git checkout $LATEST_RELEASE -fi - -# Disable buffering, so that the logs stream through. -export PYTHONUNBUFFERED=1 - -# Debug: show build environment -env | grep KOKORO - -# Install nox -python3.6 -m pip install --upgrade --quiet nox - -# Use secrets acessor service account to get secrets -if [[ -f "${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" ]]; then - gcloud auth activate-service-account \ - --key-file="${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" \ - --project="cloud-devrel-kokoro-resources" -fi - -# This script will create 3 files: -# - testing/test-env.sh -# - testing/service-account.json -# - testing/client-secrets.json -./scripts/decrypt-secrets.sh - -source ./testing/test-env.sh -export GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/testing/service-account.json - -# For cloud-run session, we activate the service account for gcloud sdk. -gcloud auth activate-service-account \ - --key-file "${GOOGLE_APPLICATION_CREDENTIALS}" - -export GOOGLE_CLIENT_SECRETS=$(pwd)/testing/client-secrets.json - -echo -e "\n******************** TESTING PROJECTS ********************" - -# Switch to 'fail at end' to allow all tests to complete before exiting. -set +e -# Use RTN to return a non-zero value if the test fails. -RTN=0 -ROOT=$(pwd) -# Find all requirements.txt in the samples directory (may break on whitespace). -for file in samples/**/requirements.txt; do - cd "$ROOT" - # Navigate to the project folder. - file=$(dirname "$file") - cd "$file" - - echo "------------------------------------------------------------" - echo "- testing $file" - echo "------------------------------------------------------------" - - # Use nox to execute the tests for the project. - python3.6 -m nox -s "$RUN_TESTS_SESSION" - EXIT=$? - - # If this is a periodic build, send the test log to the FlakyBot. - # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/flakybot. - if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then - chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot - $KOKORO_GFILE_DIR/linux_amd64/flakybot + echo "The current head is: " + echo $(git rev-parse --verify HEAD) + echo "--- IMPORTANT IMPORTANT IMPORTANT ---" + # move back the test runner implementation if there's no file. + if [ ! -f .kokoro/test-samples-impl.sh ]; then + cp "${TMPDIR}/test-samples-impl.sh" .kokoro/test-samples-impl.sh fi +fi - if [[ $EXIT -ne 0 ]]; then - RTN=1 - echo -e "\n Testing failed: Nox returned a non-zero exit code. \n" - else - echo -e "\n Testing completed.\n" - fi - -done -cd "$ROOT" - -# Workaround for Kokoro permissions issue: delete secrets -rm testing/{test-env.sh,client-secrets.json,service-account.json} - -exit "$RTN" \ No newline at end of file +exec .kokoro/test-samples-impl.sh diff --git a/.kokoro/trampoline.sh b/.kokoro/trampoline.sh index e8c4251f..f39236e9 100755 --- a/.kokoro/trampoline.sh +++ b/.kokoro/trampoline.sh @@ -15,9 +15,14 @@ set -eo pipefail -python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" || ret_code=$? +# Always run the cleanup script, regardless of the success of bouncing into +# the container. +function cleanup() { + chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh + ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh + echo "cleanup"; +} +trap cleanup EXIT -chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh -${KOKORO_GFILE_DIR}/trampoline_cleanup.sh || true - -exit ${ret_code} +$(dirname $0)/populate-secrets.sh # Secret Manager secrets. +python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..32302e48 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml +- repo: https://github.com/psf/black + rev: 19.10b0 + hooks: + - id: black +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.9.0 + hooks: + - id: flake8 diff --git a/.trampolinerc b/.trampolinerc index 995ee291..383b6ec8 100644 --- a/.trampolinerc +++ b/.trampolinerc @@ -24,6 +24,7 @@ required_envvars+=( pass_down_envvars+=( "STAGING_BUCKET" "V2_STAGING_BUCKET" + "NOX_SESSION" ) # Prevent unintentional override on the default image. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b3d1f602..039f4368 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,44 +1,95 @@ -# Contributor Code of Conduct +# Code of Conduct -As contributors and maintainers of this project, -and in the interest of fostering an open and welcoming community, -we pledge to respect all people who contribute through reporting issues, -posting feature requests, updating documentation, -submitting pull requests or patches, and other activities. +## Our Pledge -We are committed to making participation in this project -a harassment-free experience for everyone, -regardless of level of experience, gender, gender identity and expression, -sexual orientation, disability, personal appearance, -body size, race, ethnicity, age, religion, or nationality. +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of +experience, education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery -* Personal attacks -* Trolling or insulting/derogatory comments -* Public or private harassment -* Publishing other's private information, -such as physical or electronic -addresses, without explicit permission -* Other unethical or unprofessional conduct. +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct. -By adopting this Code of Conduct, -project maintainers commit themselves to fairly and consistently -applying these principles to every aspect of managing this project. -Project maintainers who do not follow or enforce the Code of Conduct -may be permanently removed from the project team. - -This code of conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. - -Instances of abusive, harassing, or otherwise unacceptable behavior -may be reported by opening an issue -or contacting one or more of the project maintainers. - -This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, -available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +This Code of Conduct also applies outside the project spaces when the Project +Steward has a reasonable belief that an individual's behavior may have a +negative impact on the project or its community. + +## Conflict Resolution + +We do not believe that all conflict is bad; healthy debate and disagreement +often yield positive results. However, it is never okay to be disrespectful or +to engage in behavior that violates the project’s code of conduct. + +If you see someone violating the code of conduct, you are encouraged to address +the behavior directly with those involved. Many issues can be resolved quickly +and easily, and this gives people more control over the outcome of their +dispute. If you are unable to resolve the matter for any reason, or if the +behavior is threatening or harassing, report it. We are dedicated to providing +an environment where participants feel welcome and safe. + + +Reports should be directed to *googleapis-stewards@google.com*, the +Project Steward(s) for *Google Cloud Client Libraries*. It is the Project Steward’s duty to +receive and address reported violations of the code of conduct. They will then +work with a committee consisting of representatives from the Open Source +Programs Office and the Google Open Source Strategy team. If for any reason you +are uncomfortable reaching out to the Project Steward, please email +opensource@google.com. + +We will investigate every complaint, but you may not receive a direct response. +We will use our discretion in determining when and how to follow up on reported +incidents, which may range from not taking action to permanent expulsion from +the project and project-sponsored spaces. We will notify the accused of the +report and provide them an opportunity to discuss it before any action is taken. +The identity of the reporter will be omitted from the details of the report +supplied to the accused. In potentially harmful situations, such as ongoing +harassment or threats to anyone's safety, we may take action without notice. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 1.4, +available at +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html \ No newline at end of file diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 0e17c793..dd8c5248 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -21,8 +21,8 @@ In order to add a feature: - The feature must be documented in both the API and narrative documentation. -- The feature must work fully on the following CPython versions: 2.7, - 3.5, 3.6, 3.7 and 3.8 on both UNIX and Windows. +- The feature must work fully on the following CPython versions: + 3.6, 3.7, 3.8 and 3.9 on both UNIX and Windows. - The feature must not add unnecessary dependencies (where "unnecessary" is of course subjective, but new dependencies should @@ -70,9 +70,14 @@ We use `nox `__ to instrument our tests. - To test your changes, run unit tests with ``nox``:: $ nox -s unit-2.7 - $ nox -s unit-3.7 + $ nox -s unit-3.8 $ ... +- Args to pytest can be passed through the nox command separated by a `--`. For + example, to run a single test:: + + $ nox -s unit-3.8 -- -k + .. note:: The unit tests and system tests are described in the @@ -80,25 +85,6 @@ We use `nox `__ to instrument our tests. .. nox: https://pypi.org/project/nox/ -Note on Editable Installs / Develop Mode -======================================== - -- As mentioned previously, using ``setuptools`` in `develop mode`_ - or a ``pip`` `editable install`_ is not possible with this - library. This is because this library uses `namespace packages`_. - For context see `Issue #2316`_ and the relevant `PyPA issue`_. - - Since ``editable`` / ``develop`` mode can't be used, packages - need to be installed directly. Hence your changes to the source - tree don't get incorporated into the **already installed** - package. - -.. _namespace packages: https://www.python.org/dev/peps/pep-0420/ -.. _Issue #2316: https://github.com/GoogleCloudPlatform/google-cloud-python/issues/2316 -.. _PyPA issue: https://github.com/pypa/packaging-problems/issues/12 -.. _develop mode: https://setuptools.readthedocs.io/en/latest/setuptools.html#development-mode -.. _editable install: https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs - ***************************************** I'm getting weird errors... Can you help? ***************************************** @@ -112,8 +98,12 @@ On Debian/Ubuntu:: ************ Coding Style ************ +- We use the automatic code formatter ``black``. You can run it using + the nox session ``blacken``. This will eliminate many lint errors. Run via:: -- PEP8 compliance, with exceptions defined in the linter configuration. + $ nox -s blacken + +- PEP8 compliance is required, with exceptions defined in the linter configuration. If you have ``nox`` installed, you can test that you have not introduced any non-compliant code via:: @@ -130,6 +120,16 @@ Coding Style should point to the official ``googleapis`` checkout and the the branch should be the main branch on that remote (``master``). +- This repository contains configuration for the + `pre-commit `__ tool, which automates checking + our linters during a commit. If you have it installed on your ``$PATH``, + you can enable enforcing those checks via: + +.. code-block:: bash + + $ pre-commit install + pre-commit installed at .git/hooks/pre-commit + Exceptions to PEP8: - Many unit tests use a helper method, ``_call_fut`` ("FUT" is short for @@ -142,13 +142,18 @@ Running System Tests - To run system tests, you can execute:: - $ nox -s system-3.7 + # Run all system tests + $ nox -s system-3.8 $ nox -s system-2.7 + # Run a single system test + $ nox -s system-3.8 -- -k + + .. note:: System tests are only configured to run under Python 2.7 and - Python 3.7. For expediency, we do not run them in older versions + Python 3.8. For expediency, we do not run them in older versions of Python 3. This alone will not run the tests. You'll need to change some local @@ -211,25 +216,24 @@ Supported Python Versions We support: -- `Python 3.5`_ - `Python 3.6`_ - `Python 3.7`_ - `Python 3.8`_ +- `Python 3.9`_ -.. _Python 3.5: https://docs.python.org/3.5/ .. _Python 3.6: https://docs.python.org/3.6/ .. _Python 3.7: https://docs.python.org/3.7/ .. _Python 3.8: https://docs.python.org/3.8/ +.. _Python 3.9: https://docs.python.org/3.9/ Supported versions can be found in our ``noxfile.py`` `config`_. .. _config: https://github.com/googleapis/python-trace/blob/master/noxfile.py -Python 2.7 support is deprecated. All code changes should maintain Python 2.7 compatibility until January 1, 2020. We also explicitly decided to support Python 3 beginning with version -3.5. Reasons for this include: +3.6. Reasons for this include: - Encouraging use of newest versions of Python 3 - Taking the lead of `prominent`_ open-source `projects`_ diff --git a/LICENSE b/LICENSE index a8ee855d..d6456956 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ - Apache License + + Apache License Version 2.0, January 2004 - https://www.apache.org/licenses/ + http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION @@ -192,7 +193,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - https://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/MANIFEST.in b/MANIFEST.in index e9e29d12..e783f4c6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -16,10 +16,10 @@ # Generated by synthtool. DO NOT EDIT! include README.rst LICENSE -recursive-include google *.json *.proto +recursive-include google *.json *.proto py.typed recursive-include tests * global-exclude *.py[co] global-exclude __pycache__ # Exclude scripts for samples readmegen -prune scripts/readme-gen \ No newline at end of file +prune scripts/readme-gen diff --git a/README.rst b/README.rst index 597d5ca4..eee79bd6 100644 --- a/README.rst +++ b/README.rst @@ -1,25 +1,5 @@ -Python Client for Cloud Trace API -======================================= - -|ga| |pypi| |versions| - -The `Cloud Trace API`_ sends application trace data to Cloud Trace -for viewing. Trace data is collected for all App Engine applications by -default. Trace data from other applications can be provided using this API. - -- `Client Library Documentation`_ -- `Product Documentation`_ - -.. |ga| image:: https://img.shields.io/badge/support-ga-gold.svg - :target: https://github.com/googleapis/google-cloud-python/blob/master/README.rst#general-availability -.. |pypi| image:: https://img.shields.io/pypi/v/google-cloud-trace.svg - :target: https://pypi.org/project/google-cloud-trace/ -.. |versions| image:: https://img.shields.io/pypi/pyversions/google-cloud-trace.svg - :target: https://pypi.org/project/google-cloud-trace/ -.. _Cloud Trace API: https://cloud.google.com/trace -.. _Client Library Documentation: https://googleapis.dev/python/cloudtrace/latest -.. _Product Documentation: https://cloud.google.com/trace - +Python Client for Google Cloud Trace API +================================================= Quick Start ----------- @@ -27,14 +7,14 @@ Quick Start In order to use this library, you first need to go through the following steps: 1. `Select or create a Cloud Platform project.`_ -2. `Enable the trace API.`_ -3. `Setup Authentication.`_ +2. `Enable billing for your project.`_ +3. Enable the Google Cloud Trace API. +4. `Setup Authentication.`_ .. _Select or create a Cloud Platform project.: https://console.cloud.google.com/project -.. _Enable the trace API.: https://cloud.google.com/trace +.. _Enable billing for your project.: https://cloud.google.com/billing/docs/how-to/modify-project#enable_billing_for_a_project .. _Setup Authentication.: https://googleapis.dev/python/google-api-core/latest/auth.html - Installation ~~~~~~~~~~~~ @@ -42,23 +22,11 @@ Install this library in a `virtualenv`_ using pip. `virtualenv`_ is a tool to create isolated Python environments. The basic problem it addresses is one of dependencies and versions, and indirectly permissions. -With `virtualenv`_, it's possible to install this library without needing -system install permissions, and without clashing with the installed system +With `virtualenv`_, it's possible to install this library without needing system +install permissions, and without clashing with the installed system dependencies. -.. _virtualenv: https://virtualenv.pypa.io/en/latest/ - - -Supported Python Versions -^^^^^^^^^^^^^^^^^^^^^^^^^ -Python >= 3.6 - - -Unsupported Python Versions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Python == 2.7. - -The last version of this library compatible with Python 2.7 is google-cloud-trace==0.24.0 +.. _`virtualenv`: https://virtualenv.pypa.io/en/latest/ Mac/Linux @@ -66,10 +34,9 @@ Mac/Linux .. code-block:: console - pip install virtualenv - virtualenv + python3 -m venv source /bin/activate - /bin/pip install google-cloud-trace + /bin/pip install /path/to/library Windows @@ -77,22 +44,6 @@ Windows .. code-block:: console - pip install virtualenv - virtualenv + python3 -m venv \Scripts\activate - \Scripts\pip.exe install google-cloud-trace - -For more information on setting up your Python development environment, -such as installing ``pip`` and ``virtualenv`` on your system, please refer -to `Python Development Environment Setup Guide`_ for Google Cloud Platform. - -.. _Python Development Environment Setup Guide: https://cloud.google.com/python/setup - - -Next Steps -~~~~~~~~~~ - -- Read the `Client Library Documentation`_ for Cloud Trace API - to see other available methods on the client. -- Read the `Product documentation`_ to learn more about the product and see - How-to Guides. + \Scripts\pip.exe install \path\to\library diff --git a/docs/_static/custom.css b/docs/_static/custom.css index 0abaf229..bcd37bbd 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -1,4 +1,9 @@ div#python2-eol { border-color: red; border-width: medium; -} \ No newline at end of file +} + +/* Ensure minimum width for 'Parameters' / 'Returns' column */ +dl.field-list > dt { + min-width: 100px +} diff --git a/docs/conf.py b/docs/conf.py index 8f206ded..9f3c1dd4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -349,6 +349,7 @@ "google-auth": ("https://googleapis.dev/python/google-auth/latest/", None), "google.api_core": ("https://googleapis.dev/python/google-api-core/latest/", None,), "grpc": ("https://grpc.github.io/grpc/python/", None), + "proto-plus": ("https://proto-plus-python.readthedocs.io/en/latest/", None), } diff --git a/docs/trace_v1/services.rst b/docs/trace_v1/services.rst index e900d74b..ed26586e 100644 --- a/docs/trace_v1/services.rst +++ b/docs/trace_v1/services.rst @@ -1,6 +1,6 @@ Services for Google Cloud Trace v1 API ====================================== +.. toctree:: + :maxdepth: 2 -.. automodule:: google.cloud.trace_v1.services.trace_service - :members: - :inherited-members: + trace_service diff --git a/docs/trace_v1/trace_service.rst b/docs/trace_v1/trace_service.rst new file mode 100644 index 00000000..14bcf291 --- /dev/null +++ b/docs/trace_v1/trace_service.rst @@ -0,0 +1,11 @@ +TraceService +------------------------------ + +.. automodule:: google.cloud.trace_v1.services.trace_service + :members: + :inherited-members: + + +.. automodule:: google.cloud.trace_v1.services.trace_service.pagers + :members: + :inherited-members: diff --git a/docs/trace_v1/types.rst b/docs/trace_v1/types.rst index 3055682c..761d1e1f 100644 --- a/docs/trace_v1/types.rst +++ b/docs/trace_v1/types.rst @@ -3,3 +3,5 @@ Types for Google Cloud Trace v1 API .. automodule:: google.cloud.trace_v1.types :members: + :undoc-members: + :show-inheritance: diff --git a/docs/trace_v2/services.rst b/docs/trace_v2/services.rst index ce3705dd..d3d580ef 100644 --- a/docs/trace_v2/services.rst +++ b/docs/trace_v2/services.rst @@ -1,6 +1,6 @@ Services for Google Cloud Trace v2 API ====================================== +.. toctree:: + :maxdepth: 2 -.. automodule:: google.cloud.trace_v2.services.trace_service - :members: - :inherited-members: + trace_service diff --git a/docs/trace_v2/trace_service.rst b/docs/trace_v2/trace_service.rst new file mode 100644 index 00000000..3fb8cc16 --- /dev/null +++ b/docs/trace_v2/trace_service.rst @@ -0,0 +1,6 @@ +TraceService +------------------------------ + +.. automodule:: google.cloud.trace_v2.services.trace_service + :members: + :inherited-members: diff --git a/docs/trace_v2/types.rst b/docs/trace_v2/types.rst index b5e205ce..3bd88c61 100644 --- a/docs/trace_v2/types.rst +++ b/docs/trace_v2/types.rst @@ -3,3 +3,5 @@ Types for Google Cloud Trace v2 API .. automodule:: google.cloud.trace_v2.types :members: + :undoc-members: + :show-inheritance: diff --git a/google/cloud/trace_v1/proto/trace.proto b/google/cloud/trace_v1/proto/trace.proto new file mode 100644 index 00000000..d3948fa1 --- /dev/null +++ b/google/cloud/trace_v1/proto/trace.proto @@ -0,0 +1,305 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.devtools.cloudtrace.v1; + +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; +import "google/api/resource.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "Google.Cloud.Trace.V1"; +option go_package = "google.golang.org/genproto/googleapis/devtools/cloudtrace/v1;cloudtrace"; +option java_multiple_files = true; +option java_outer_classname = "TraceProto"; +option java_package = "com.google.devtools.cloudtrace.v1"; +option php_namespace = "Google\\Cloud\\Trace\\V1"; +option ruby_package = "Google::Cloud::Trace::V1"; + +// This file describes an API for collecting and viewing traces and spans +// within a trace. A Trace is a collection of spans corresponding to a single +// operation or set of operations for an application. A span is an individual +// timed event which forms a node of the trace tree. Spans for a single trace +// may span multiple services. +service TraceService { + option (google.api.default_host) = "cloudtrace.googleapis.com"; + option (google.api.oauth_scopes) = + "https://www.googleapis.com/auth/cloud-platform," + "https://www.googleapis.com/auth/trace.append," + "https://www.googleapis.com/auth/trace.readonly"; + + // Returns of a list of traces that match the specified filter conditions. + rpc ListTraces(ListTracesRequest) returns (ListTracesResponse) { + option (google.api.http) = { + get: "/v1/projects/{project_id}/traces" + }; + option (google.api.method_signature) = "project_id"; + } + + // Gets a single trace by its ID. + rpc GetTrace(GetTraceRequest) returns (Trace) { + option (google.api.http) = { + get: "/v1/projects/{project_id}/traces/{trace_id}" + }; + option (google.api.method_signature) = "project_id,trace_id"; + } + + // Sends new traces to Stackdriver Trace or updates existing traces. If the ID + // of a trace that you send matches that of an existing trace, any fields + // in the existing trace and its spans are overwritten by the provided values, + // and any new fields provided are merged with the existing trace data. If the + // ID does not match, a new trace is created. + rpc PatchTraces(PatchTracesRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + patch: "/v1/projects/{project_id}/traces" + body: "traces" + }; + option (google.api.method_signature) = "project_id,traces"; + } +} + +// A trace describes how long it takes for an application to perform an +// operation. It consists of a set of spans, each of which represent a single +// timed event within the operation. +message Trace { + // Project ID of the Cloud project where the trace data is stored. + string project_id = 1; + + // Globally unique identifier for the trace. This identifier is a 128-bit + // numeric value formatted as a 32-byte hex string. For example, + // `382d4f4c6b7bb2f4a972559d9085001d`. + string trace_id = 2; + + // Collection of spans in the trace. + repeated TraceSpan spans = 3; +} + +// List of new or updated traces. +message Traces { + // List of traces. + repeated Trace traces = 1; +} + +// A span represents a single timed event within a trace. Spans can be nested +// and form a trace tree. Often, a trace contains a root span that describes the +// end-to-end latency of an operation and, optionally, one or more subspans for +// its suboperations. Spans do not need to be contiguous. There may be gaps +// between spans in a trace. +message TraceSpan { + // Type of span. Can be used to specify additional relationships between spans + // in addition to a parent/child relationship. + enum SpanKind { + // Unspecified. + SPAN_KIND_UNSPECIFIED = 0; + + // Indicates that the span covers server-side handling of an RPC or other + // remote network request. + RPC_SERVER = 1; + + // Indicates that the span covers the client-side wrapper around an RPC or + // other remote request. + RPC_CLIENT = 2; + } + + // Identifier for the span. Must be a 64-bit integer other than 0 and + // unique within a trace. For example, `2205310701640571284`. + fixed64 span_id = 1; + + // Distinguishes between spans generated in a particular context. For example, + // two spans with the same name may be distinguished using `RPC_CLIENT` + // and `RPC_SERVER` to identify queueing latency associated with the span. + SpanKind kind = 2; + + // Name of the span. Must be less than 128 bytes. The span name is sanitized + // and displayed in the Stackdriver Trace tool in the + // Google Cloud Platform Console. + // The name may be a method name or some other per-call site name. + // For the same executable and the same call point, a best practice is + // to use a consistent name, which makes it easier to correlate + // cross-trace spans. + string name = 3; + + // Start time of the span in nanoseconds from the UNIX epoch. + google.protobuf.Timestamp start_time = 4; + + // End time of the span in nanoseconds from the UNIX epoch. + google.protobuf.Timestamp end_time = 5; + + // Optional. ID of the parent span, if any. + fixed64 parent_span_id = 6 [(google.api.field_behavior) = OPTIONAL]; + + // Collection of labels associated with the span. Label keys must be less than + // 128 bytes. Label values must be less than 16 kilobytes (10MB for + // `/stacktrace` values). + // + // Some predefined label keys exist, or you may create your own. When creating + // your own, we recommend the following formats: + // + // * `/category/product/key` for agents of well-known products (e.g. + // `/db/mongodb/read_size`). + // * `short_host/path/key` for domain-specific keys (e.g. + // `foo.com/myproduct/bar`) + // + // Predefined labels include: + // + // * `/agent` + // * `/component` + // * `/error/message` + // * `/error/name` + // * `/http/client_city` + // * `/http/client_country` + // * `/http/client_protocol` + // * `/http/client_region` + // * `/http/host` + // * `/http/method` + // * `/http/path` + // * `/http/redirected_url` + // * `/http/request/size` + // * `/http/response/size` + // * `/http/route` + // * `/http/status_code` + // * `/http/url` + // * `/http/user_agent` + // * `/pid` + // * `/stacktrace` + // * `/tid` + map labels = 7; +} + +// The request message for the `ListTraces` method. All fields are required +// unless specified. +message ListTracesRequest { + // Type of data returned for traces in the list. + enum ViewType { + // Default is `MINIMAL` if unspecified. + VIEW_TYPE_UNSPECIFIED = 0; + + // Minimal view of the trace record that contains only the project + // and trace IDs. + MINIMAL = 1; + + // Root span view of the trace record that returns the root spans along + // with the minimal trace data. + ROOTSPAN = 2; + + // Complete view of the trace record that contains the actual trace data. + // This is equivalent to calling the REST `get` or RPC `GetTrace` method + // using the ID of each listed trace. + COMPLETE = 3; + } + + // Required. ID of the Cloud project where the trace data is stored. + string project_id = 1 [(google.api.field_behavior) = REQUIRED]; + + // Optional. Type of data returned for traces in the list. Default is + // `MINIMAL`. + ViewType view = 2 [(google.api.field_behavior) = OPTIONAL]; + + // Optional. Maximum number of traces to return. If not specified or <= 0, the + // implementation selects a reasonable value. The implementation may + // return fewer traces than the requested page size. + int32 page_size = 3 [(google.api.field_behavior) = OPTIONAL]; + + // Token identifying the page of results to return. If provided, use the + // value of the `next_page_token` field from a previous request. + string page_token = 4; + + // Start of the time interval (inclusive) during which the trace data was + // collected from the application. + google.protobuf.Timestamp start_time = 5; + + // End of the time interval (inclusive) during which the trace data was + // collected from the application. + google.protobuf.Timestamp end_time = 6; + + // Optional. A filter against labels for the request. + // + // By default, searches use prefix matching. To specify exact match, prepend + // a plus symbol (`+`) to the search term. + // Multiple terms are ANDed. Syntax: + // + // * `root:NAME_PREFIX` or `NAME_PREFIX`: Return traces where any root + // span starts with `NAME_PREFIX`. + // * `+root:NAME` or `+NAME`: Return traces where any root span's name is + // exactly `NAME`. + // * `span:NAME_PREFIX`: Return traces where any span starts with + // `NAME_PREFIX`. + // * `+span:NAME`: Return traces where any span's name is exactly + // `NAME`. + // * `latency:DURATION`: Return traces whose overall latency is + // greater or equal to than `DURATION`. Accepted units are nanoseconds + // (`ns`), milliseconds (`ms`), and seconds (`s`). Default is `ms`. For + // example, `latency:24ms` returns traces whose overall latency + // is greater than or equal to 24 milliseconds. + // * `label:LABEL_KEY`: Return all traces containing the specified + // label key (exact match, case-sensitive) regardless of the key:value + // pair's value (including empty values). + // * `LABEL_KEY:VALUE_PREFIX`: Return all traces containing the specified + // label key (exact match, case-sensitive) whose value starts with + // `VALUE_PREFIX`. Both a key and a value must be specified. + // * `+LABEL_KEY:VALUE`: Return all traces containing a key:value pair + // exactly matching the specified text. Both a key and a value must be + // specified. + // * `method:VALUE`: Equivalent to `/http/method:VALUE`. + // * `url:VALUE`: Equivalent to `/http/url:VALUE`. + string filter = 7 [(google.api.field_behavior) = OPTIONAL]; + + // Optional. Field used to sort the returned traces. + // Can be one of the following: + // + // * `trace_id` + // * `name` (`name` field of root span in the trace) + // * `duration` (difference between `end_time` and `start_time` fields of + // the root span) + // * `start` (`start_time` field of the root span) + // + // Descending order can be specified by appending `desc` to the sort field + // (for example, `name desc`). + // + // Only one sort field is permitted. + string order_by = 8 [(google.api.field_behavior) = OPTIONAL]; +} + +// The response message for the `ListTraces` method. +message ListTracesResponse { + // List of trace records as specified by the view parameter. + repeated Trace traces = 1; + + // If defined, indicates that there are more traces that match the request + // and that this value should be passed to the next request to continue + // retrieving additional traces. + string next_page_token = 2; +} + +// The request message for the `GetTrace` method. +message GetTraceRequest { + // Required. ID of the Cloud project where the trace data is stored. + string project_id = 1 [(google.api.field_behavior) = REQUIRED]; + + // Required. ID of the trace to return. + string trace_id = 2 [(google.api.field_behavior) = REQUIRED]; +} + +// The request message for the `PatchTraces` method. +message PatchTracesRequest { + // Required. ID of the Cloud project where the trace data is stored. + string project_id = 1 [(google.api.field_behavior) = REQUIRED]; + + // Required. The body of the message. + Traces traces = 2 [(google.api.field_behavior) = REQUIRED]; +} diff --git a/google/cloud/trace_v1/services/trace_service/async_client.py b/google/cloud/trace_v1/services/trace_service/async_client.py index fb9606b2..64869c98 100644 --- a/google/cloud/trace_v1/services/trace_service/async_client.py +++ b/google/cloud/trace_v1/services/trace_service/async_client.py @@ -50,9 +50,72 @@ class TraceServiceAsyncClient: DEFAULT_ENDPOINT = TraceServiceClient.DEFAULT_ENDPOINT DEFAULT_MTLS_ENDPOINT = TraceServiceClient.DEFAULT_MTLS_ENDPOINT - from_service_account_file = TraceServiceClient.from_service_account_file + common_billing_account_path = staticmethod( + TraceServiceClient.common_billing_account_path + ) + parse_common_billing_account_path = staticmethod( + TraceServiceClient.parse_common_billing_account_path + ) + + common_folder_path = staticmethod(TraceServiceClient.common_folder_path) + parse_common_folder_path = staticmethod(TraceServiceClient.parse_common_folder_path) + + common_organization_path = staticmethod(TraceServiceClient.common_organization_path) + parse_common_organization_path = staticmethod( + TraceServiceClient.parse_common_organization_path + ) + + common_project_path = staticmethod(TraceServiceClient.common_project_path) + parse_common_project_path = staticmethod( + TraceServiceClient.parse_common_project_path + ) + + common_location_path = staticmethod(TraceServiceClient.common_location_path) + parse_common_location_path = staticmethod( + TraceServiceClient.parse_common_location_path + ) + + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + TraceServiceAsyncClient: The constructed client. + """ + return TraceServiceClient.from_service_account_info.__func__(TraceServiceAsyncClient, info, *args, **kwargs) # type: ignore + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + TraceServiceAsyncClient: The constructed client. + """ + return TraceServiceClient.from_service_account_file.__func__(TraceServiceAsyncClient, filename, *args, **kwargs) # type: ignore + from_service_account_json = from_service_account_file + @property + def transport(self) -> TraceServiceTransport: + """Return the transport used by the client instance. + + Returns: + TraceServiceTransport: The transport used by the client instance. + """ + return self._client.transport + get_transport_class = functools.partial( type(TraceServiceClient).get_transport_class, type(TraceServiceClient) ) @@ -79,16 +142,19 @@ def __init__( client_options (ClientOptions): Custom options for the client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the - default endpoint provided by the client. GOOGLE_API_USE_MTLS + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT 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. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -115,13 +181,14 @@ async def list_traces( filter conditions. Args: - request (:class:`~.trace.ListTracesRequest`): + request (:class:`google.cloud.trace_v1.types.ListTracesRequest`): The request object. The request message for the `ListTraces` method. All fields are required unless specified. project_id (:class:`str`): Required. ID of the Cloud project where the trace data is stored. + This corresponds to the ``project_id`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -133,8 +200,8 @@ async def list_traces( sent along with the request as metadata. Returns: - ~.pagers.ListTracesAsyncPager: - The response message for the ``ListTraces`` method. + google.cloud.trace_v1.services.trace_service.pagers.ListTracesAsyncPager: + The response message for the ListTraces method. Iterating over this object will yield results and resolve additional pages automatically. @@ -143,7 +210,8 @@ async def list_traces( # Create or coerce a protobuf request object. # Sanity check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. - if request is not None and any([project_id]): + has_flattened_params = any([project_id]) + if request is not None and has_flattened_params: raise ValueError( "If the `request` argument is set, then none of " "the individual field arguments should be set." @@ -168,6 +236,7 @@ async def list_traces( predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=45.0, ), default_timeout=45.0, client_info=DEFAULT_CLIENT_INFO, @@ -198,12 +267,13 @@ async def get_trace( r"""Gets a single trace by its ID. Args: - request (:class:`~.trace.GetTraceRequest`): + request (:class:`google.cloud.trace_v1.types.GetTraceRequest`): The request object. The request message for the `GetTrace` method. project_id (:class:`str`): Required. ID of the Cloud project where the trace data is stored. + This corresponds to the ``project_id`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -220,7 +290,7 @@ async def get_trace( sent along with the request as metadata. Returns: - ~.trace.Trace: + google.cloud.trace_v1.types.Trace: A trace describes how long it takes for an application to perform an operation. It consists of a set of @@ -231,7 +301,8 @@ async def get_trace( # Create or coerce a protobuf request object. # Sanity check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. - if request is not None and any([project_id, trace_id]): + has_flattened_params = any([project_id, trace_id]) + if request is not None and has_flattened_params: raise ValueError( "If the `request` argument is set, then none of " "the individual field arguments should be set." @@ -258,6 +329,7 @@ async def get_trace( predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=45.0, ), default_timeout=45.0, client_info=DEFAULT_CLIENT_INFO, @@ -288,16 +360,17 @@ async def patch_traces( a new trace is created. Args: - request (:class:`~.trace.PatchTracesRequest`): + request (:class:`google.cloud.trace_v1.types.PatchTracesRequest`): The request object. The request message for the `PatchTraces` method. project_id (:class:`str`): Required. ID of the Cloud project where the trace data is stored. + This corresponds to the ``project_id`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - traces (:class:`~.trace.Traces`): + traces (:class:`google.cloud.trace_v1.types.Traces`): Required. The body of the message. This corresponds to the ``traces`` field on the ``request`` instance; if ``request`` is provided, this @@ -312,7 +385,8 @@ async def patch_traces( # Create or coerce a protobuf request object. # Sanity check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. - if request is not None and any([project_id, traces]): + has_flattened_params = any([project_id, traces]) + if request is not None and has_flattened_params: raise ValueError( "If the `request` argument is set, then none of " "the individual field arguments should be set." @@ -339,6 +413,7 @@ async def patch_traces( predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=45.0, ), default_timeout=45.0, client_info=DEFAULT_CLIENT_INFO, diff --git a/google/cloud/trace_v1/services/trace_service/client.py b/google/cloud/trace_v1/services/trace_service/client.py index 77e2877c..6e182a33 100644 --- a/google/cloud/trace_v1/services/trace_service/client.py +++ b/google/cloud/trace_v1/services/trace_service/client.py @@ -16,17 +16,19 @@ # from collections import OrderedDict +from distutils import util import os import re -from typing import Callable, Dict, Sequence, Tuple, Type, Union +from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union import pkg_resources -import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import client_options as client_options_lib # type: ignore from google.api_core import exceptions # type: ignore 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.transport.grpc import SslCredentials # type: ignore from google.auth.exceptions import MutualTLSChannelError # type: ignore from google.oauth2 import service_account # type: ignore @@ -112,6 +114,22 @@ def _get_default_mtls_endpoint(api_endpoint): DEFAULT_ENDPOINT ) + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + TraceServiceClient: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_info(info) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + @classmethod def from_service_account_file(cls, filename: str, *args, **kwargs): """Creates an instance of this client using the provided credentials @@ -124,7 +142,7 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): kwargs: Additional arguments to pass to the constructor. Returns: - {@api.name}: The constructed client. + TraceServiceClient: The constructed client. """ credentials = service_account.Credentials.from_service_account_file(filename) kwargs["credentials"] = credentials @@ -132,12 +150,80 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): from_service_account_json = from_service_account_file + @property + def transport(self) -> TraceServiceTransport: + """Return the transport used by the client instance. + + Returns: + TraceServiceTransport: The transport used by the client instance. + """ + return self._transport + + @staticmethod + def common_billing_account_path(billing_account: str,) -> str: + """Return a fully-qualified billing_account string.""" + return "billingAccounts/{billing_account}".format( + billing_account=billing_account, + ) + + @staticmethod + def parse_common_billing_account_path(path: str) -> Dict[str, str]: + """Parse a billing_account path into its component segments.""" + m = re.match(r"^billingAccounts/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_folder_path(folder: str,) -> str: + """Return a fully-qualified folder string.""" + return "folders/{folder}".format(folder=folder,) + + @staticmethod + def parse_common_folder_path(path: str) -> Dict[str, str]: + """Parse a folder path into its component segments.""" + m = re.match(r"^folders/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_organization_path(organization: str,) -> str: + """Return a fully-qualified organization string.""" + return "organizations/{organization}".format(organization=organization,) + + @staticmethod + def parse_common_organization_path(path: str) -> Dict[str, str]: + """Parse a organization path into its component segments.""" + m = re.match(r"^organizations/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_project_path(project: str,) -> str: + """Return a fully-qualified project string.""" + return "projects/{project}".format(project=project,) + + @staticmethod + def parse_common_project_path(path: str) -> Dict[str, str]: + """Parse a project path into its component segments.""" + m = re.match(r"^projects/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_location_path(project: str, location: str,) -> str: + """Return a fully-qualified location string.""" + return "projects/{project}/locations/{location}".format( + project=project, location=location, + ) + + @staticmethod + def parse_common_location_path(path: str) -> Dict[str, str]: + """Parse a location path into its component segments.""" + m = re.match(r"^projects/(?P.+?)/locations/(?P.+?)$", path) + return m.groupdict() if m else {} + def __init__( self, *, - credentials: credentials.Credentials = None, - transport: Union[str, TraceServiceTransport] = None, - client_options: ClientOptions = None, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, TraceServiceTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the trace service client. @@ -148,26 +234,29 @@ def __init__( credentials identify the application to the service; if none are specified, the client will attempt to ascertain the credentials from the environment. - transport (Union[str, ~.TraceServiceTransport]): The + transport (Union[str, TraceServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (ClientOptions): Custom options for the client. It - won't take effect if a ``transport`` instance is provided. + client_options (google.api_core.client_options.ClientOptions): Custom options for the + client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the - default endpoint provided by the client. GOOGLE_API_USE_MTLS + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT 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. - client_info (google.api_core.gapic_v1.client_info.ClientInfo): - The client info used to send a user-agent string along with - API requests. If ``None``, then default info will be used. - Generally, you only need to set this if you're developing + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing your own client library. Raises: @@ -175,29 +264,43 @@ def __init__( creation failed for any reason. """ if isinstance(client_options, dict): - client_options = ClientOptions.from_dict(client_options) + client_options = client_options_lib.from_dict(client_options) if client_options is None: - client_options = ClientOptions.ClientOptions() + client_options = client_options_lib.ClientOptions() + + # Create SSL credentials for mutual TLS if needed. + use_client_cert = bool( + util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + ) + + client_cert_source_func = None + is_mtls = False + if use_client_cert: + if client_options.client_cert_source: + is_mtls = True + client_cert_source_func = client_options.client_cert_source + else: + is_mtls = mtls.has_default_client_cert_source() + client_cert_source_func = ( + mtls.default_client_cert_source() if is_mtls else None + ) - if client_options.api_endpoint is None: - use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "never") + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + else: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") if use_mtls_env == "never": - client_options.api_endpoint = self.DEFAULT_ENDPOINT + api_endpoint = self.DEFAULT_ENDPOINT elif use_mtls_env == "always": - client_options.api_endpoint = self.DEFAULT_MTLS_ENDPOINT + 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 + api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT ) else: raise MutualTLSChannelError( - "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: never, auto, always" + "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always" ) # Save or instantiate the transport. @@ -221,10 +324,9 @@ def __init__( self._transport = Transport( credentials=credentials, credentials_file=client_options.credentials_file, - host=client_options.api_endpoint, + host=api_endpoint, scopes=client_options.scopes, - api_mtls_endpoint=client_options.api_endpoint, - client_cert_source=client_options.client_cert_source, + client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, ) @@ -242,13 +344,14 @@ def list_traces( filter conditions. Args: - request (:class:`~.trace.ListTracesRequest`): + request (google.cloud.trace_v1.types.ListTracesRequest): The request object. The request message for the `ListTraces` method. All fields are required unless specified. - project_id (:class:`str`): + project_id (str): Required. ID of the Cloud project where the trace data is stored. + This corresponds to the ``project_id`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -260,8 +363,8 @@ def list_traces( sent along with the request as metadata. Returns: - ~.pagers.ListTracesPager: - The response message for the ``ListTraces`` method. + google.cloud.trace_v1.services.trace_service.pagers.ListTracesPager: + The response message for the ListTraces method. Iterating over this object will yield results and resolve additional pages automatically. @@ -319,16 +422,17 @@ def get_trace( r"""Gets a single trace by its ID. Args: - request (:class:`~.trace.GetTraceRequest`): + request (google.cloud.trace_v1.types.GetTraceRequest): The request object. The request message for the `GetTrace` method. - project_id (:class:`str`): + project_id (str): Required. ID of the Cloud project where the trace data is stored. + This corresponds to the ``project_id`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - trace_id (:class:`str`): + trace_id (str): Required. ID of the trace to return. This corresponds to the ``trace_id`` field on the ``request`` instance; if ``request`` is provided, this @@ -341,7 +445,7 @@ def get_trace( sent along with the request as metadata. Returns: - ~.trace.Trace: + google.cloud.trace_v1.types.Trace: A trace describes how long it takes for an application to perform an operation. It consists of a set of @@ -403,16 +507,17 @@ def patch_traces( a new trace is created. Args: - request (:class:`~.trace.PatchTracesRequest`): + request (google.cloud.trace_v1.types.PatchTracesRequest): The request object. The request message for the `PatchTraces` method. - project_id (:class:`str`): + project_id (str): Required. ID of the Cloud project where the trace data is stored. + This corresponds to the ``project_id`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - traces (:class:`~.trace.Traces`): + traces (google.cloud.trace_v1.types.Traces): Required. The body of the message. This corresponds to the ``traces`` field on the ``request`` instance; if ``request`` is provided, this diff --git a/google/cloud/trace_v1/services/trace_service/pagers.py b/google/cloud/trace_v1/services/trace_service/pagers.py index f5b2439b..ad600632 100644 --- a/google/cloud/trace_v1/services/trace_service/pagers.py +++ b/google/cloud/trace_v1/services/trace_service/pagers.py @@ -15,7 +15,16 @@ # limitations under the License. # -from typing import Any, AsyncIterable, Awaitable, Callable, Iterable, Sequence, Tuple +from typing import ( + Any, + AsyncIterable, + Awaitable, + Callable, + Iterable, + Sequence, + Tuple, + Optional, +) from google.cloud.trace_v1.types import trace @@ -24,7 +33,7 @@ class ListTracesPager: """A pager for iterating through ``list_traces`` requests. This class thinly wraps an initial - :class:`~.trace.ListTracesResponse` object, and + :class:`google.cloud.trace_v1.types.ListTracesResponse` object, and provides an ``__iter__`` method to iterate through its ``traces`` field. @@ -33,7 +42,7 @@ class ListTracesPager: through the ``traces`` field on the corresponding responses. - All the usual :class:`~.trace.ListTracesResponse` + All the usual :class:`google.cloud.trace_v1.types.ListTracesResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -51,9 +60,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.trace.ListTracesRequest`): + request (google.cloud.trace_v1.types.ListTracesRequest): The initial request object. - response (:class:`~.trace.ListTracesResponse`): + response (google.cloud.trace_v1.types.ListTracesResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. @@ -86,7 +95,7 @@ class ListTracesAsyncPager: """A pager for iterating through ``list_traces`` requests. This class thinly wraps an initial - :class:`~.trace.ListTracesResponse` object, and + :class:`google.cloud.trace_v1.types.ListTracesResponse` object, and provides an ``__aiter__`` method to iterate through its ``traces`` field. @@ -95,7 +104,7 @@ class ListTracesAsyncPager: through the ``traces`` field on the corresponding responses. - All the usual :class:`~.trace.ListTracesResponse` + All the usual :class:`google.cloud.trace_v1.types.ListTracesResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -113,9 +122,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.trace.ListTracesRequest`): + request (google.cloud.trace_v1.types.ListTracesRequest): The initial request object. - response (:class:`~.trace.ListTracesResponse`): + response (google.cloud.trace_v1.types.ListTracesResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. diff --git a/google/cloud/trace_v1/services/trace_service/transports/__init__.py b/google/cloud/trace_v1/services/trace_service/transports/__init__.py index 134fa0ad..b860866b 100644 --- a/google/cloud/trace_v1/services/trace_service/transports/__init__.py +++ b/google/cloud/trace_v1/services/trace_service/transports/__init__.py @@ -28,7 +28,6 @@ _transport_registry["grpc"] = TraceServiceGrpcTransport _transport_registry["grpc_asyncio"] = TraceServiceGrpcAsyncIOTransport - __all__ = ( "TraceServiceTransport", "TraceServiceGrpcTransport", diff --git a/google/cloud/trace_v1/services/trace_service/transports/base.py b/google/cloud/trace_v1/services/trace_service/transports/base.py index b8b99849..5557e905 100644 --- a/google/cloud/trace_v1/services/trace_service/transports/base.py +++ b/google/cloud/trace_v1/services/trace_service/transports/base.py @@ -19,7 +19,7 @@ import typing import pkg_resources -from google import auth +from google import auth # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore @@ -72,10 +72,10 @@ def __init__( scope (Optional[Sequence[str]]): A list of scopes. quota_project_id (Optional[str]): An optional project to use for billing and quota. - client_info (google.api_core.gapic_v1.client_info.ClientInfo): - The client info used to send a user-agent string along with - API requests. If ``None``, then default info will be used. - Generally, you only need to set this if you're developing + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing your own client library. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. @@ -83,6 +83,9 @@ def __init__( host += ":443" self._host = host + # Save the scopes. + self._scopes = scopes or self.AUTH_SCOPES + # If no credentials are provided, then determine the appropriate # defaults. if credentials and credentials_file: @@ -92,20 +95,17 @@ def __init__( if credentials_file is not None: credentials, _ = auth.load_credentials_from_file( - credentials_file, scopes=scopes, quota_project_id=quota_project_id + credentials_file, scopes=self._scopes, quota_project_id=quota_project_id ) elif credentials is None: credentials, _ = auth.default( - scopes=scopes, quota_project_id=quota_project_id + scopes=self._scopes, quota_project_id=quota_project_id ) # Save the credentials. self._credentials = credentials - # Lifted into its own function so it can be stubbed out during tests. - self._prep_wrapped_messages(client_info) - def _prep_wrapped_messages(self, client_info): # Precompute the wrapped methods. self._wrapped_methods = { @@ -118,6 +118,7 @@ def _prep_wrapped_messages(self, client_info): predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=45.0, ), default_timeout=45.0, client_info=client_info, @@ -131,6 +132,7 @@ def _prep_wrapped_messages(self, client_info): predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=45.0, ), default_timeout=45.0, client_info=client_info, @@ -144,6 +146,7 @@ def _prep_wrapped_messages(self, client_info): predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=45.0, ), default_timeout=45.0, client_info=client_info, diff --git a/google/cloud/trace_v1/services/trace_service/transports/grpc.py b/google/cloud/trace_v1/services/trace_service/transports/grpc.py index 998ffea3..f6c1a2dd 100644 --- a/google/cloud/trace_v1/services/trace_service/transports/grpc.py +++ b/google/cloud/trace_v1/services/trace_service/transports/grpc.py @@ -15,6 +15,7 @@ # limitations under the License. # +import warnings from typing import Callable, Dict, Optional, Sequence, Tuple from google.api_core import grpc_helpers # type: ignore @@ -23,7 +24,6 @@ from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore - import grpc # type: ignore from google.cloud.trace_v1.types import trace @@ -62,6 +62,8 @@ def __init__( channel: grpc.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -82,20 +84,26 @@ def __init__( ignored if ``channel`` is provided. channel (Optional[grpc.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. - client_info (google.api_core.gapic_v1.client_info.ClientInfo): - The client info used to send a user-agent string along with - API requests. If ``None``, then default info will be used. - Generally, you only need to set this if you're developing + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing your own client library. Raises: @@ -104,57 +112,70 @@ def __init__( google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` and ``credentials_file`` are passed. """ + self._grpc_channel = None + self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: - # Sanity check: Ensure that channel and credentials are not both - # provided. + # Ignore credentials if a channel was passed. credentials = False - # If a channel was explicitly provided, set it. self._grpc_channel = channel - elif api_mtls_endpoint: - host = ( - api_mtls_endpoint - if ":" in api_mtls_endpoint - else api_mtls_endpoint + ":443" - ) + self._ssl_channel_credentials = None + + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) - - # Create SSL credentials with client_cert_source or application - # default SSL credentials. - if client_cert_source: - cert, key = client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) else: - ssl_credentials = SslCredentials().ssl_credentials - - # create a new channel. The provided one is ignored. - self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, - credentials_file=credentials_file, - ssl_credentials=ssl_credentials, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - ) - - self._stubs = {} # type: Dict[str, Callable] + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) - # Run the base constructor. + # The base transport sets the host, credentials and scopes super().__init__( host=host, credentials=credentials, credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES, + scopes=scopes, quota_project_id=quota_project_id, client_info=client_info, ) + if not self._grpc_channel: + self._grpc_channel = type(self).create_channel( + self._host, + credentials=self._credentials, + credentials_file=credentials_file, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, + quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Wrap messages. This must be done after self._grpc_channel exists + self._prep_wrapped_messages(client_info) + @classmethod def create_channel( cls, @@ -167,7 +188,7 @@ def create_channel( ) -> grpc.Channel: """Create and return a gRPC channel object. Args: - address (Optionsl[str]): The host for the channel to use. + host (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If @@ -202,19 +223,8 @@ def create_channel( @property def grpc_channel(self) -> grpc.Channel: - """Create the channel designed to connect to this service. - - This property caches on the instance; repeated calls return - the same channel. + """Return the channel designed to connect to this service. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - - # Return the channel from cache. return self._grpc_channel @property diff --git a/google/cloud/trace_v1/services/trace_service/transports/grpc_asyncio.py b/google/cloud/trace_v1/services/trace_service/transports/grpc_asyncio.py index 7ebc87d4..48963ead 100644 --- a/google/cloud/trace_v1/services/trace_service/transports/grpc_asyncio.py +++ b/google/cloud/trace_v1/services/trace_service/transports/grpc_asyncio.py @@ -15,10 +15,12 @@ # limitations under the License. # +import warnings from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple from google.api_core import gapic_v1 # type: ignore from google.api_core import grpc_helpers_async # type: ignore +from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -65,7 +67,7 @@ def create_channel( ) -> aio.Channel: """Create and return a gRPC AsyncIO channel object. Args: - address (Optional[str]): The host for the channel to use. + host (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If @@ -104,6 +106,8 @@ def __init__( channel: aio.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -125,20 +129,26 @@ def __init__( are passed to :func:`google.auth.default`. channel (Optional[aio.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. - client_info (google.api_core.gapic_v1.client_info.ClientInfo): - The client info used to send a user-agent string along with - API requests. If ``None``, then default info will be used. - Generally, you only need to set this if you're developing + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing your own client library. Raises: @@ -147,51 +157,69 @@ def __init__( google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` and ``credentials_file`` are passed. """ + self._grpc_channel = None + self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: - # Sanity check: Ensure that channel and credentials are not both - # provided. + # Ignore credentials if a channel was passed. credentials = False - # If a channel was explicitly provided, set it. self._grpc_channel = channel - elif api_mtls_endpoint: - host = ( - api_mtls_endpoint - if ":" in api_mtls_endpoint - else api_mtls_endpoint + ":443" - ) + self._ssl_channel_credentials = None + + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials - # Create SSL credentials with client_cert_source or application - # default SSL credentials. - if client_cert_source: - cert, key = client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) else: - ssl_credentials = SslCredentials().ssl_credentials - - # create a new channel. The provided one is ignored. - self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, - credentials_file=credentials_file, - ssl_credentials=ssl_credentials, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) - # Run the base constructor. + # The base transport sets the host, credentials and scopes super().__init__( host=host, credentials=credentials, credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES, + scopes=scopes, quota_project_id=quota_project_id, client_info=client_info, ) - self._stubs = {} + if not self._grpc_channel: + self._grpc_channel = type(self).create_channel( + self._host, + credentials=self._credentials, + credentials_file=credentials_file, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, + quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Wrap messages. This must be done after self._grpc_channel exists + self._prep_wrapped_messages(client_info) @property def grpc_channel(self) -> aio.Channel: @@ -200,13 +228,6 @@ def grpc_channel(self) -> aio.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/trace_v1/types/__init__.py b/google/cloud/trace_v1/types/__init__.py index 66dbf851..540e3e00 100644 --- a/google/cloud/trace_v1/types/__init__.py +++ b/google/cloud/trace_v1/types/__init__.py @@ -16,22 +16,21 @@ # from .trace import ( - Trace, - Traces, - TraceSpan, + GetTraceRequest, ListTracesRequest, ListTracesResponse, - GetTraceRequest, PatchTracesRequest, + Trace, + Traces, + TraceSpan, ) - __all__ = ( - "Trace", - "Traces", - "TraceSpan", + "GetTraceRequest", "ListTracesRequest", "ListTracesResponse", - "GetTraceRequest", "PatchTracesRequest", + "Trace", + "Traces", + "TraceSpan", ) diff --git a/google/cloud/trace_v1/types/trace.py b/google/cloud/trace_v1/types/trace.py index 81cfbc93..3e75d906 100644 --- a/google/cloud/trace_v1/types/trace.py +++ b/google/cloud/trace_v1/types/trace.py @@ -48,7 +48,7 @@ class Trace(proto.Message): Globally unique identifier for the trace. This identifier is a 128-bit numeric value formatted as a 32-byte hex string. For example, ``382d4f4c6b7bb2f4a972559d9085001d``. - spans (Sequence[~.trace.TraceSpan]): + spans (Sequence[google.cloud.trace_v1.types.TraceSpan]): Collection of spans in the trace. """ @@ -63,11 +63,11 @@ class Traces(proto.Message): r"""List of new or updated traces. Attributes: - traces (Sequence[~.trace.Trace]): + traces (Sequence[google.cloud.trace_v1.types.Trace]): List of traces. """ - traces = proto.RepeatedField(proto.MESSAGE, number=1, message=Trace,) + traces = proto.RepeatedField(proto.MESSAGE, number=1, message="Trace",) class TraceSpan(proto.Message): @@ -83,7 +83,7 @@ class TraceSpan(proto.Message): Identifier for the span. Must be a 64-bit integer other than 0 and unique within a trace. For example, ``2205310701640571284``. - kind (~.trace.TraceSpan.SpanKind): + kind (google.cloud.trace_v1.types.TraceSpan.SpanKind): Distinguishes between spans generated in a particular context. For example, two spans with the same name may be distinguished using ``RPC_CLIENT`` and ``RPC_SERVER`` to @@ -98,15 +98,15 @@ class TraceSpan(proto.Message): same call point, a best practice is to use a consistent name, which makes it easier to correlate cross-trace spans. - start_time (~.timestamp.Timestamp): + start_time (google.protobuf.timestamp_pb2.Timestamp): Start time of the span in nanoseconds from the UNIX epoch. - end_time (~.timestamp.Timestamp): + end_time (google.protobuf.timestamp_pb2.Timestamp): End time of the span in nanoseconds from the UNIX epoch. parent_span_id (int): Optional. ID of the parent span, if any. - labels (Sequence[~.trace.TraceSpan.LabelsEntry]): + labels (Sequence[google.cloud.trace_v1.types.TraceSpan.LabelsEntry]): Collection of labels associated with the span. Label keys must be less than 128 bytes. Label values must be less than 16 kilobytes (10MB for ``/stacktrace`` values). @@ -176,7 +176,7 @@ class ListTracesRequest(proto.Message): project_id (str): Required. ID of the Cloud project where the trace data is stored. - view (~.trace.ListTracesRequest.ViewType): + view (google.cloud.trace_v1.types.ListTracesRequest.ViewType): Optional. Type of data returned for traces in the list. Default is ``MINIMAL``. page_size (int): @@ -189,11 +189,11 @@ class ListTracesRequest(proto.Message): Token identifying the page of results to return. If provided, use the value of the ``next_page_token`` field from a previous request. - start_time (~.timestamp.Timestamp): + start_time (google.protobuf.timestamp_pb2.Timestamp): Start of the time interval (inclusive) during which the trace data was collected from the application. - end_time (~.timestamp.Timestamp): + end_time (google.protobuf.timestamp_pb2.Timestamp): End of the time interval (inclusive) during which the trace data was collected from the application. @@ -275,7 +275,7 @@ class ListTracesResponse(proto.Message): r"""The response message for the ``ListTraces`` method. Attributes: - traces (Sequence[~.trace.Trace]): + traces (Sequence[google.cloud.trace_v1.types.Trace]): List of trace records as specified by the view parameter. next_page_token (str): @@ -289,7 +289,7 @@ class ListTracesResponse(proto.Message): def raw_page(self): return self - traces = proto.RepeatedField(proto.MESSAGE, number=1, message=Trace,) + traces = proto.RepeatedField(proto.MESSAGE, number=1, message="Trace",) next_page_token = proto.Field(proto.STRING, number=2) @@ -317,13 +317,13 @@ class PatchTracesRequest(proto.Message): project_id (str): Required. ID of the Cloud project where the trace data is stored. - traces (~.trace.Traces): + traces (google.cloud.trace_v1.types.Traces): Required. The body of the message. """ project_id = proto.Field(proto.STRING, number=1) - traces = proto.Field(proto.MESSAGE, number=2, message=Traces,) + traces = proto.Field(proto.MESSAGE, number=2, message="Traces",) __all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/trace_v2/proto/trace.proto b/google/cloud/trace_v2/proto/trace.proto new file mode 100644 index 00000000..66669aa6 --- /dev/null +++ b/google/cloud/trace_v2/proto/trace.proto @@ -0,0 +1,378 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.devtools.cloudtrace.v2; + +import "google/api/field_behavior.proto"; +import "google/api/resource.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; +import "google/rpc/status.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "Google.Cloud.Trace.V2"; +option go_package = "google.golang.org/genproto/googleapis/devtools/cloudtrace/v2;cloudtrace"; +option java_multiple_files = true; +option java_outer_classname = "TraceProto"; +option java_package = "com.google.devtools.cloudtrace.v2"; +option php_namespace = "Google\\Cloud\\Trace\\V2"; +option ruby_package = "Google::Cloud::Trace::V2"; + +// A span represents a single operation within a trace. Spans can be +// nested to form a trace tree. Often, a trace contains a root span +// that describes the end-to-end latency, and one or more subspans for +// its sub-operations. A trace can also contain multiple root spans, +// or none at all. Spans do not need to be contiguous—there may be +// gaps or overlaps between spans in a trace. +message Span { + option (google.api.resource) = { + type: "cloudtrace.googleapis.com/Span" + pattern: "projects/{project}/traces/{trace}/spans/{span}" + }; + + // A set of attributes, each in the format `[KEY]:[VALUE]`. + message Attributes { + // The set of attributes. Each attribute's key can be up to 128 bytes + // long. The value can be a string up to 256 bytes, a signed 64-bit integer, + // or the Boolean values `true` and `false`. For example: + // + // "/instance_id": { "string_value": { "value": "my-instance" } } + // "/http/request_bytes": { "int_value": 300 } + // "abc.com/myattribute": { "bool_value": false } + map attribute_map = 1; + + // The number of attributes that were discarded. Attributes can be discarded + // because their keys are too long or because there are too many attributes. + // If this value is 0 then all attributes are valid. + int32 dropped_attributes_count = 2; + } + + // A time-stamped annotation or message event in the Span. + message TimeEvent { + // Text annotation with a set of attributes. + message Annotation { + // A user-supplied message describing the event. The maximum length for + // the description is 256 bytes. + TruncatableString description = 1; + + // A set of attributes on the annotation. You can have up to 4 attributes + // per Annotation. + Attributes attributes = 2; + } + + // An event describing a message sent/received between Spans. + message MessageEvent { + // Indicates whether the message was sent or received. + enum Type { + // Unknown event type. + TYPE_UNSPECIFIED = 0; + + // Indicates a sent message. + SENT = 1; + + // Indicates a received message. + RECEIVED = 2; + } + + // Type of MessageEvent. Indicates whether the message was sent or + // received. + Type type = 1; + + // An identifier for the MessageEvent's message that can be used to match + // SENT and RECEIVED MessageEvents. It is recommended to be unique within + // a Span. + int64 id = 2; + + // The number of uncompressed bytes sent or received. + int64 uncompressed_size_bytes = 3; + + // The number of compressed bytes sent or received. If missing assumed to + // be the same size as uncompressed. + int64 compressed_size_bytes = 4; + } + + // The timestamp indicating the time the event occurred. + google.protobuf.Timestamp time = 1; + + // A `TimeEvent` can contain either an `Annotation` object or a + // `MessageEvent` object, but not both. + oneof value { + // Text annotation with a set of attributes. + Annotation annotation = 2; + + // An event describing a message sent/received between Spans. + MessageEvent message_event = 3; + } + } + + // A collection of `TimeEvent`s. A `TimeEvent` is a time-stamped annotation + // on the span, consisting of either user-supplied key:value pairs, or + // details of a message sent/received between Spans. + message TimeEvents { + // A collection of `TimeEvent`s. + repeated TimeEvent time_event = 1; + + // The number of dropped annotations in all the included time events. + // If the value is 0, then no annotations were dropped. + int32 dropped_annotations_count = 2; + + // The number of dropped message events in all the included time events. + // If the value is 0, then no message events were dropped. + int32 dropped_message_events_count = 3; + } + + // A pointer from the current span to another span in the same trace or in a + // different trace. For example, this can be used in batching operations, + // where a single batch handler processes multiple requests from different + // traces or when the handler receives a request from a different project. + message Link { + // The relationship of the current span relative to the linked span: child, + // parent, or unspecified. + enum Type { + // The relationship of the two spans is unknown. + TYPE_UNSPECIFIED = 0; + + // The linked span is a child of the current span. + CHILD_LINKED_SPAN = 1; + + // The linked span is a parent of the current span. + PARENT_LINKED_SPAN = 2; + } + + // The [TRACE_ID] for a trace within a project. + string trace_id = 1; + + // The [SPAN_ID] for a span within a trace. + string span_id = 2; + + // The relationship of the current span relative to the linked span. + Type type = 3; + + // A set of attributes on the link. You have have up to 32 attributes per + // link. + Attributes attributes = 4; + } + + // A collection of links, which are references from this span to a span + // in the same or different trace. + message Links { + // A collection of links. + repeated Link link = 1; + + // The number of dropped links after the maximum size was enforced. If + // this value is 0, then no links were dropped. + int32 dropped_links_count = 2; + } + + // Type of span. Can be used to specify additional relationships between spans + // in addition to a parent/child relationship. + enum SpanKind { + // Unspecified. Do NOT use as default. + // Implementations MAY assume SpanKind.INTERNAL to be default. + SPAN_KIND_UNSPECIFIED = 0; + + // Indicates that the span is used internally. Default value. + INTERNAL = 1; + + // Indicates that the span covers server-side handling of an RPC or other + // remote network request. + SERVER = 2; + + // Indicates that the span covers the client-side wrapper around an RPC or + // other remote request. + CLIENT = 3; + + // Indicates that the span describes producer sending a message to a broker. + // Unlike client and server, there is no direct critical path latency + // relationship between producer and consumer spans (e.g. publishing a + // message to a pubsub service). + PRODUCER = 4; + + // Indicates that the span describes consumer receiving a message from a + // broker. Unlike client and server, there is no direct critical path + // latency relationship between producer and consumer spans (e.g. receiving + // a message from a pubsub service subscription). + CONSUMER = 5; + } + + // Required. The resource name of the span in the following format: + // + // projects/[PROJECT_ID]/traces/[TRACE_ID]/spans/[SPAN_ID] + // + // [TRACE_ID] is a unique identifier for a trace within a project; + // it is a 32-character hexadecimal encoding of a 16-byte array. + // + // [SPAN_ID] is a unique identifier for a span within a trace; it + // is a 16-character hexadecimal encoding of an 8-byte array. + string name = 1 [(google.api.field_behavior) = REQUIRED]; + + // Required. The [SPAN_ID] portion of the span's resource name. + string span_id = 2 [(google.api.field_behavior) = REQUIRED]; + + // The [SPAN_ID] of this span's parent span. If this is a root span, + // then this field must be empty. + string parent_span_id = 3; + + // Required. A description of the span's operation (up to 128 bytes). + // Stackdriver Trace displays the description in the + // Google Cloud Platform Console. + // For example, the display name can be a qualified method name or a file name + // and a line number where the operation is called. A best practice is to use + // the same display name within an application and at the same call point. + // This makes it easier to correlate spans in different traces. + TruncatableString display_name = 4 [(google.api.field_behavior) = REQUIRED]; + + // Required. The start time of the span. On the client side, this is the time kept by + // the local machine where the span execution starts. On the server side, this + // is the time when the server's application handler starts running. + google.protobuf.Timestamp start_time = 5 [(google.api.field_behavior) = REQUIRED]; + + // Required. The end time of the span. On the client side, this is the time kept by + // the local machine where the span execution ends. On the server side, this + // is the time when the server application handler stops running. + google.protobuf.Timestamp end_time = 6 [(google.api.field_behavior) = REQUIRED]; + + // A set of attributes on the span. You can have up to 32 attributes per + // span. + Attributes attributes = 7; + + // Stack trace captured at the start of the span. + StackTrace stack_trace = 8; + + // A set of time events. You can have up to 32 annotations and 128 message + // events per span. + TimeEvents time_events = 9; + + // Links associated with the span. You can have up to 128 links per Span. + Links links = 10; + + // Optional. The final status for this span. + google.rpc.Status status = 11 [(google.api.field_behavior) = OPTIONAL]; + + // Optional. Set this parameter to indicate whether this span is in + // the same process as its parent. If you do not set this parameter, + // Stackdriver Trace is unable to take advantage of this helpful + // information. + google.protobuf.BoolValue same_process_as_parent_span = 12 [(google.api.field_behavior) = OPTIONAL]; + + // Optional. The number of child spans that were generated while this span + // was active. If set, allows implementation to detect missing child spans. + google.protobuf.Int32Value child_span_count = 13 [(google.api.field_behavior) = OPTIONAL]; + + // Optional. Distinguishes between spans generated in a particular context. For example, + // two spans with the same name may be distinguished using `CLIENT` (caller) + // and `SERVER` (callee) to identify an RPC call. + SpanKind span_kind = 14 [(google.api.field_behavior) = OPTIONAL]; +} + +// The allowed types for [VALUE] in a `[KEY]:[VALUE]` attribute. +message AttributeValue { + // The type of the value. + oneof value { + // A string up to 256 bytes long. + TruncatableString string_value = 1; + + // A 64-bit signed integer. + int64 int_value = 2; + + // A Boolean value represented by `true` or `false`. + bool bool_value = 3; + } +} + +// A call stack appearing in a trace. +message StackTrace { + // Represents a single stack frame in a stack trace. + message StackFrame { + // The fully-qualified name that uniquely identifies the function or + // method that is active in this frame (up to 1024 bytes). + TruncatableString function_name = 1; + + // An un-mangled function name, if `function_name` is + // [mangled](http://www.avabodh.com/cxxin/namemangling.html). The name can + // be fully-qualified (up to 1024 bytes). + TruncatableString original_function_name = 2; + + // The name of the source file where the function call appears (up to 256 + // bytes). + TruncatableString file_name = 3; + + // The line number in `file_name` where the function call appears. + int64 line_number = 4; + + // The column number where the function call appears, if available. + // This is important in JavaScript because of its anonymous functions. + int64 column_number = 5; + + // The binary module from where the code was loaded. + Module load_module = 6; + + // The version of the deployed source code (up to 128 bytes). + TruncatableString source_version = 7; + } + + // A collection of stack frames, which can be truncated. + message StackFrames { + // Stack frames in this call stack. + repeated StackFrame frame = 1; + + // The number of stack frames that were dropped because there + // were too many stack frames. + // If this value is 0, then no stack frames were dropped. + int32 dropped_frames_count = 2; + } + + // Stack frames in this stack trace. A maximum of 128 frames are allowed. + StackFrames stack_frames = 1; + + // The hash ID is used to conserve network bandwidth for duplicate + // stack traces within a single trace. + // + // Often multiple spans will have identical stack traces. + // The first occurrence of a stack trace should contain both the + // `stackFrame` content and a value in `stackTraceHashId`. + // + // Subsequent spans within the same request can refer + // to that stack trace by only setting `stackTraceHashId`. + int64 stack_trace_hash_id = 2; +} + +// Binary module. +message Module { + // For example: main binary, kernel modules, and dynamic libraries + // such as libc.so, sharedlib.so (up to 256 bytes). + TruncatableString module = 1; + + // A unique identifier for the module, usually a hash of its + // contents (up to 128 bytes). + TruncatableString build_id = 2; +} + +// Represents a string that might be shortened to a specified length. +message TruncatableString { + // The shortened string. For example, if the original string is 500 + // bytes long and the limit of the string is 128 bytes, then + // `value` contains the first 128 bytes of the 500-byte string. + // + // Truncation always happens on a UTF8 character boundary. If there + // are multi-byte characters in the string, then the length of the + // shortened string might be less than the size limit. + string value = 1; + + // The number of bytes removed from the original string. If this + // value is 0, then the string was not shortened. + int32 truncated_byte_count = 2; +} diff --git a/google/cloud/trace_v2/proto/tracing.proto b/google/cloud/trace_v2/proto/tracing.proto new file mode 100644 index 00000000..a25a0dd7 --- /dev/null +++ b/google/cloud/trace_v2/proto/tracing.proto @@ -0,0 +1,79 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.devtools.cloudtrace.v2; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; +import "google/api/resource.proto"; +import "google/devtools/cloudtrace/v2/trace.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; + +option csharp_namespace = "Google.Cloud.Trace.V2"; +option go_package = "google.golang.org/genproto/googleapis/devtools/cloudtrace/v2;cloudtrace"; +option java_multiple_files = true; +option java_outer_classname = "TracingProto"; +option java_package = "com.google.devtools.cloudtrace.v2"; +option php_namespace = "Google\\Cloud\\Trace\\V2"; +option ruby_package = "Google::Cloud::Trace::V2"; + +// This file describes an API for collecting and viewing traces and spans +// within a trace. A Trace is a collection of spans corresponding to a single +// operation or set of operations for an application. A span is an individual +// timed event which forms a node of the trace tree. A single trace may +// contain span(s) from multiple services. +service TraceService { + option (google.api.default_host) = "cloudtrace.googleapis.com"; + option (google.api.oauth_scopes) = + "https://www.googleapis.com/auth/cloud-platform," + "https://www.googleapis.com/auth/trace.append"; + + // Sends new spans to new or existing traces. You cannot update + // existing spans. + rpc BatchWriteSpans(BatchWriteSpansRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v2/{name=projects/*}/traces:batchWrite" + body: "*" + }; + option (google.api.method_signature) = "name,spans"; + } + + // Creates a new span. + rpc CreateSpan(Span) returns (Span) { + option (google.api.http) = { + post: "/v2/{name=projects/*/traces/*/spans/*}" + body: "*" + }; + } +} + +// The request message for the `BatchWriteSpans` method. +message BatchWriteSpansRequest { + // Required. The name of the project where the spans belong. The format is + // `projects/[PROJECT_ID]`. + string name = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + type: "cloudresourcemanager.googleapis.com/Project" + } + ]; + + // Required. A list of new spans. The span names must not match existing + // spans, or the results are undefined. + repeated Span spans = 2 [(google.api.field_behavior) = REQUIRED]; +} diff --git a/google/cloud/trace_v2/services/trace_service/async_client.py b/google/cloud/trace_v2/services/trace_service/async_client.py index a400f60a..16df441a 100644 --- a/google/cloud/trace_v2/services/trace_service/async_client.py +++ b/google/cloud/trace_v2/services/trace_service/async_client.py @@ -54,10 +54,74 @@ class TraceServiceAsyncClient: DEFAULT_MTLS_ENDPOINT = TraceServiceClient.DEFAULT_MTLS_ENDPOINT span_path = staticmethod(TraceServiceClient.span_path) + parse_span_path = staticmethod(TraceServiceClient.parse_span_path) + + common_billing_account_path = staticmethod( + TraceServiceClient.common_billing_account_path + ) + parse_common_billing_account_path = staticmethod( + TraceServiceClient.parse_common_billing_account_path + ) + + common_folder_path = staticmethod(TraceServiceClient.common_folder_path) + parse_common_folder_path = staticmethod(TraceServiceClient.parse_common_folder_path) + + common_organization_path = staticmethod(TraceServiceClient.common_organization_path) + parse_common_organization_path = staticmethod( + TraceServiceClient.parse_common_organization_path + ) + + common_project_path = staticmethod(TraceServiceClient.common_project_path) + parse_common_project_path = staticmethod( + TraceServiceClient.parse_common_project_path + ) + + common_location_path = staticmethod(TraceServiceClient.common_location_path) + parse_common_location_path = staticmethod( + TraceServiceClient.parse_common_location_path + ) + + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + TraceServiceAsyncClient: The constructed client. + """ + return TraceServiceClient.from_service_account_info.__func__(TraceServiceAsyncClient, info, *args, **kwargs) # type: ignore + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + TraceServiceAsyncClient: The constructed client. + """ + return TraceServiceClient.from_service_account_file.__func__(TraceServiceAsyncClient, filename, *args, **kwargs) # type: ignore - from_service_account_file = TraceServiceClient.from_service_account_file from_service_account_json = from_service_account_file + @property + def transport(self) -> TraceServiceTransport: + """Return the transport used by the client instance. + + Returns: + TraceServiceTransport: The transport used by the client instance. + """ + return self._client.transport + get_transport_class = functools.partial( type(TraceServiceClient).get_transport_class, type(TraceServiceClient) ) @@ -84,16 +148,19 @@ def __init__( client_options (ClientOptions): Custom options for the client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the - default endpoint provided by the client. GOOGLE_API_USE_MTLS + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT 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. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -121,19 +188,21 @@ async def batch_write_spans( update existing spans. Args: - request (:class:`~.tracing.BatchWriteSpansRequest`): + request (:class:`google.cloud.trace_v2.types.BatchWriteSpansRequest`): The request object. The request message for the `BatchWriteSpans` method. name (:class:`str`): Required. The name of the project where the spans belong. The format is ``projects/[PROJECT_ID]``. + This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - spans (:class:`Sequence[~.trace.Span]`): + spans (:class:`Sequence[google.cloud.trace_v2.types.Span]`): Required. A list of new spans. The span names must not match existing spans, or the results are undefined. + This corresponds to the ``spans`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -147,7 +216,8 @@ async def batch_write_spans( # Create or coerce a protobuf request object. # Sanity check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. - if request is not None and any([name, spans]): + has_flattened_params = any([name, spans]) + if request is not None and has_flattened_params: raise ValueError( "If the `request` argument is set, then none of " "the individual field arguments should be set." @@ -160,8 +230,9 @@ async def batch_write_spans( if name is not None: request.name = name - if spans is not None: - request.spans = spans + + if spans: + request.spans.extend(spans) # Wrap the RPC method; this adds retry and timeout information, # and friendly error handling. @@ -193,7 +264,7 @@ async def create_span( r"""Creates a new span. Args: - request (:class:`~.trace.Span`): + request (:class:`google.cloud.trace_v2.types.Span`): The request object. A span represents a single operation within a trace. Spans can be nested to form a trace tree. Often, a trace contains a root span that describes @@ -210,7 +281,7 @@ async def create_span( sent along with the request as metadata. Returns: - ~.trace.Span: + google.cloud.trace_v2.types.Span: A span represents a single operation within a trace. Spans can be nested to form a trace tree. Often, a trace @@ -238,6 +309,7 @@ async def create_span( predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=120.0, ), default_timeout=120.0, client_info=DEFAULT_CLIENT_INFO, diff --git a/google/cloud/trace_v2/services/trace_service/client.py b/google/cloud/trace_v2/services/trace_service/client.py index 816ad9d3..19758f21 100644 --- a/google/cloud/trace_v2/services/trace_service/client.py +++ b/google/cloud/trace_v2/services/trace_service/client.py @@ -16,17 +16,19 @@ # from collections import OrderedDict +from distutils import util import os import re -from typing import Callable, Dict, Sequence, Tuple, Type, Union +from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union import pkg_resources -import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import client_options as client_options_lib # type: ignore from google.api_core import exceptions # type: ignore 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.transport.grpc import SslCredentials # type: ignore from google.auth.exceptions import MutualTLSChannelError # type: ignore from google.oauth2 import service_account # type: ignore @@ -115,6 +117,22 @@ def _get_default_mtls_endpoint(api_endpoint): DEFAULT_ENDPOINT ) + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + TraceServiceClient: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_info(info) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + @classmethod def from_service_account_file(cls, filename: str, *args, **kwargs): """Creates an instance of this client using the provided credentials @@ -127,7 +145,7 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): kwargs: Additional arguments to pass to the constructor. Returns: - {@api.name}: The constructed client. + TraceServiceClient: The constructed client. """ credentials = service_account.Credentials.from_service_account_file(filename) kwargs["credentials"] = credentials @@ -135,6 +153,15 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): from_service_account_json = from_service_account_file + @property + def transport(self) -> TraceServiceTransport: + """Return the transport used by the client instance. + + Returns: + TraceServiceTransport: The transport used by the client instance. + """ + return self._transport + @staticmethod def span_path(project: str, trace: str, span: str,) -> str: """Return a fully-qualified span string.""" @@ -151,12 +178,71 @@ def parse_span_path(path: str) -> Dict[str, str]: ) return m.groupdict() if m else {} + @staticmethod + def common_billing_account_path(billing_account: str,) -> str: + """Return a fully-qualified billing_account string.""" + return "billingAccounts/{billing_account}".format( + billing_account=billing_account, + ) + + @staticmethod + def parse_common_billing_account_path(path: str) -> Dict[str, str]: + """Parse a billing_account path into its component segments.""" + m = re.match(r"^billingAccounts/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_folder_path(folder: str,) -> str: + """Return a fully-qualified folder string.""" + return "folders/{folder}".format(folder=folder,) + + @staticmethod + def parse_common_folder_path(path: str) -> Dict[str, str]: + """Parse a folder path into its component segments.""" + m = re.match(r"^folders/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_organization_path(organization: str,) -> str: + """Return a fully-qualified organization string.""" + return "organizations/{organization}".format(organization=organization,) + + @staticmethod + def parse_common_organization_path(path: str) -> Dict[str, str]: + """Parse a organization path into its component segments.""" + m = re.match(r"^organizations/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_project_path(project: str,) -> str: + """Return a fully-qualified project string.""" + return "projects/{project}".format(project=project,) + + @staticmethod + def parse_common_project_path(path: str) -> Dict[str, str]: + """Parse a project path into its component segments.""" + m = re.match(r"^projects/(?P.+?)$", path) + return m.groupdict() if m else {} + + @staticmethod + def common_location_path(project: str, location: str,) -> str: + """Return a fully-qualified location string.""" + return "projects/{project}/locations/{location}".format( + project=project, location=location, + ) + + @staticmethod + def parse_common_location_path(path: str) -> Dict[str, str]: + """Parse a location path into its component segments.""" + m = re.match(r"^projects/(?P.+?)/locations/(?P.+?)$", path) + return m.groupdict() if m else {} + def __init__( self, *, - credentials: credentials.Credentials = None, - transport: Union[str, TraceServiceTransport] = None, - client_options: ClientOptions = None, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, TraceServiceTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the trace service client. @@ -167,26 +253,29 @@ def __init__( credentials identify the application to the service; if none are specified, the client will attempt to ascertain the credentials from the environment. - transport (Union[str, ~.TraceServiceTransport]): The + transport (Union[str, TraceServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (ClientOptions): Custom options for the client. It - won't take effect if a ``transport`` instance is provided. + client_options (google.api_core.client_options.ClientOptions): Custom options for the + client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the - default endpoint provided by the client. GOOGLE_API_USE_MTLS + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT 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. - client_info (google.api_core.gapic_v1.client_info.ClientInfo): - The client info used to send a user-agent string along with - API requests. If ``None``, then default info will be used. - Generally, you only need to set this if you're developing + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing your own client library. Raises: @@ -194,29 +283,43 @@ def __init__( creation failed for any reason. """ if isinstance(client_options, dict): - client_options = ClientOptions.from_dict(client_options) + client_options = client_options_lib.from_dict(client_options) if client_options is None: - client_options = ClientOptions.ClientOptions() + client_options = client_options_lib.ClientOptions() + + # Create SSL credentials for mutual TLS if needed. + use_client_cert = bool( + util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + ) + + client_cert_source_func = None + is_mtls = False + if use_client_cert: + if client_options.client_cert_source: + is_mtls = True + client_cert_source_func = client_options.client_cert_source + else: + is_mtls = mtls.has_default_client_cert_source() + client_cert_source_func = ( + mtls.default_client_cert_source() if is_mtls else None + ) - if client_options.api_endpoint is None: - use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "never") + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + else: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") if use_mtls_env == "never": - client_options.api_endpoint = self.DEFAULT_ENDPOINT + api_endpoint = self.DEFAULT_ENDPOINT elif use_mtls_env == "always": - client_options.api_endpoint = self.DEFAULT_MTLS_ENDPOINT + 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 + api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT ) else: raise MutualTLSChannelError( - "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: never, auto, always" + "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always" ) # Save or instantiate the transport. @@ -240,10 +343,9 @@ def __init__( self._transport = Transport( credentials=credentials, credentials_file=client_options.credentials_file, - host=client_options.api_endpoint, + host=api_endpoint, scopes=client_options.scopes, - api_mtls_endpoint=client_options.api_endpoint, - client_cert_source=client_options.client_cert_source, + client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, ) @@ -262,19 +364,21 @@ def batch_write_spans( update existing spans. Args: - request (:class:`~.tracing.BatchWriteSpansRequest`): + request (google.cloud.trace_v2.types.BatchWriteSpansRequest): The request object. The request message for the `BatchWriteSpans` method. - name (:class:`str`): + name (str): Required. The name of the project where the spans belong. The format is ``projects/[PROJECT_ID]``. + This corresponds to the ``name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - spans (:class:`Sequence[~.trace.Span]`): + spans (Sequence[google.cloud.trace_v2.types.Span]): Required. A list of new spans. The span names must not match existing spans, or the results are undefined. + This corresponds to the ``spans`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -336,7 +440,7 @@ def create_span( r"""Creates a new span. Args: - request (:class:`~.trace.Span`): + request (google.cloud.trace_v2.types.Span): The request object. A span represents a single operation within a trace. Spans can be nested to form a trace tree. Often, a trace contains a root span that describes @@ -353,7 +457,7 @@ def create_span( sent along with the request as metadata. Returns: - ~.trace.Span: + google.cloud.trace_v2.types.Span: A span represents a single operation within a trace. Spans can be nested to form a trace tree. Often, a trace diff --git a/google/cloud/trace_v2/services/trace_service/transports/__init__.py b/google/cloud/trace_v2/services/trace_service/transports/__init__.py index 134fa0ad..b860866b 100644 --- a/google/cloud/trace_v2/services/trace_service/transports/__init__.py +++ b/google/cloud/trace_v2/services/trace_service/transports/__init__.py @@ -28,7 +28,6 @@ _transport_registry["grpc"] = TraceServiceGrpcTransport _transport_registry["grpc_asyncio"] = TraceServiceGrpcAsyncIOTransport - __all__ = ( "TraceServiceTransport", "TraceServiceGrpcTransport", diff --git a/google/cloud/trace_v2/services/trace_service/transports/base.py b/google/cloud/trace_v2/services/trace_service/transports/base.py index a271eea7..43951d62 100644 --- a/google/cloud/trace_v2/services/trace_service/transports/base.py +++ b/google/cloud/trace_v2/services/trace_service/transports/base.py @@ -19,7 +19,7 @@ import typing import pkg_resources -from google import auth +from google import auth # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore @@ -72,10 +72,10 @@ def __init__( scope (Optional[Sequence[str]]): A list of scopes. quota_project_id (Optional[str]): An optional project to use for billing and quota. - client_info (google.api_core.gapic_v1.client_info.ClientInfo): - The client info used to send a user-agent string along with - API requests. If ``None``, then default info will be used. - Generally, you only need to set this if you're developing + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing your own client library. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. @@ -83,6 +83,9 @@ def __init__( host += ":443" self._host = host + # Save the scopes. + self._scopes = scopes or self.AUTH_SCOPES + # If no credentials are provided, then determine the appropriate # defaults. if credentials and credentials_file: @@ -92,20 +95,17 @@ def __init__( if credentials_file is not None: credentials, _ = auth.load_credentials_from_file( - credentials_file, scopes=scopes, quota_project_id=quota_project_id + credentials_file, scopes=self._scopes, quota_project_id=quota_project_id ) elif credentials is None: credentials, _ = auth.default( - scopes=scopes, quota_project_id=quota_project_id + scopes=self._scopes, quota_project_id=quota_project_id ) # Save the credentials. self._credentials = credentials - # Lifted into its own function so it can be stubbed out during tests. - self._prep_wrapped_messages(client_info) - def _prep_wrapped_messages(self, client_info): # Precompute the wrapped methods. self._wrapped_methods = { @@ -121,6 +121,7 @@ def _prep_wrapped_messages(self, client_info): predicate=retries.if_exception_type( exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), + deadline=120.0, ), default_timeout=120.0, client_info=client_info, diff --git a/google/cloud/trace_v2/services/trace_service/transports/grpc.py b/google/cloud/trace_v2/services/trace_service/transports/grpc.py index 3bd20e4f..492b9562 100644 --- a/google/cloud/trace_v2/services/trace_service/transports/grpc.py +++ b/google/cloud/trace_v2/services/trace_service/transports/grpc.py @@ -15,6 +15,7 @@ # limitations under the License. # +import warnings from typing import Callable, Dict, Optional, Sequence, Tuple from google.api_core import grpc_helpers # type: ignore @@ -23,7 +24,6 @@ from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore - import grpc # type: ignore from google.cloud.trace_v2.types import trace @@ -63,6 +63,8 @@ def __init__( channel: grpc.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -83,20 +85,26 @@ def __init__( ignored if ``channel`` is provided. channel (Optional[grpc.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. - client_info (google.api_core.gapic_v1.client_info.ClientInfo): - The client info used to send a user-agent string along with - API requests. If ``None``, then default info will be used. - Generally, you only need to set this if you're developing + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing your own client library. Raises: @@ -105,57 +113,70 @@ def __init__( google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` and ``credentials_file`` are passed. """ + self._grpc_channel = None + self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: - # Sanity check: Ensure that channel and credentials are not both - # provided. + # Ignore credentials if a channel was passed. credentials = False - # If a channel was explicitly provided, set it. self._grpc_channel = channel - elif api_mtls_endpoint: - host = ( - api_mtls_endpoint - if ":" in api_mtls_endpoint - else api_mtls_endpoint + ":443" - ) + self._ssl_channel_credentials = None + + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) - - # Create SSL credentials with client_cert_source or application - # default SSL credentials. - if client_cert_source: - cert, key = client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) else: - ssl_credentials = SslCredentials().ssl_credentials - - # create a new channel. The provided one is ignored. - self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, - credentials_file=credentials_file, - ssl_credentials=ssl_credentials, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - ) - - self._stubs = {} # type: Dict[str, Callable] + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) - # Run the base constructor. + # The base transport sets the host, credentials and scopes super().__init__( host=host, credentials=credentials, credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES, + scopes=scopes, quota_project_id=quota_project_id, client_info=client_info, ) + if not self._grpc_channel: + self._grpc_channel = type(self).create_channel( + self._host, + credentials=self._credentials, + credentials_file=credentials_file, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, + quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Wrap messages. This must be done after self._grpc_channel exists + self._prep_wrapped_messages(client_info) + @classmethod def create_channel( cls, @@ -168,7 +189,7 @@ def create_channel( ) -> grpc.Channel: """Create and return a gRPC channel object. Args: - address (Optionsl[str]): The host for the channel to use. + host (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If @@ -203,19 +224,8 @@ def create_channel( @property def grpc_channel(self) -> grpc.Channel: - """Create the channel designed to connect to this service. - - This property caches on the instance; repeated calls return - the same channel. + """Return the channel designed to connect to this service. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - - # Return the channel from cache. return self._grpc_channel @property diff --git a/google/cloud/trace_v2/services/trace_service/transports/grpc_asyncio.py b/google/cloud/trace_v2/services/trace_service/transports/grpc_asyncio.py index d40f19ee..b19e6cf1 100644 --- a/google/cloud/trace_v2/services/trace_service/transports/grpc_asyncio.py +++ b/google/cloud/trace_v2/services/trace_service/transports/grpc_asyncio.py @@ -15,10 +15,12 @@ # limitations under the License. # +import warnings from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple from google.api_core import gapic_v1 # type: ignore from google.api_core import grpc_helpers_async # type: ignore +from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -66,7 +68,7 @@ def create_channel( ) -> aio.Channel: """Create and return a gRPC AsyncIO channel object. Args: - address (Optional[str]): The host for the channel to use. + host (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If @@ -105,6 +107,8 @@ def __init__( channel: aio.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -126,20 +130,26 @@ def __init__( are passed to :func:`google.auth.default`. channel (Optional[aio.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. - client_info (google.api_core.gapic_v1.client_info.ClientInfo): - The client info used to send a user-agent string along with - API requests. If ``None``, then default info will be used. - Generally, you only need to set this if you're developing + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing your own client library. Raises: @@ -148,51 +158,69 @@ def __init__( google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` and ``credentials_file`` are passed. """ + self._grpc_channel = None + self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) + if channel: - # Sanity check: Ensure that channel and credentials are not both - # provided. + # Ignore credentials if a channel was passed. credentials = False - # If a channel was explicitly provided, set it. self._grpc_channel = channel - elif api_mtls_endpoint: - host = ( - api_mtls_endpoint - if ":" in api_mtls_endpoint - else api_mtls_endpoint + ":443" - ) + self._ssl_channel_credentials = None + + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials - # Create SSL credentials with client_cert_source or application - # default SSL credentials. - if client_cert_source: - cert, key = client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) else: - ssl_credentials = SslCredentials().ssl_credentials - - # create a new channel. The provided one is ignored. - self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, - credentials_file=credentials_file, - ssl_credentials=ssl_credentials, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - ) + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) - # Run the base constructor. + # The base transport sets the host, credentials and scopes super().__init__( host=host, credentials=credentials, credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES, + scopes=scopes, quota_project_id=quota_project_id, client_info=client_info, ) - self._stubs = {} + if not self._grpc_channel: + self._grpc_channel = type(self).create_channel( + self._host, + credentials=self._credentials, + credentials_file=credentials_file, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, + quota_project_id=quota_project_id, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Wrap messages. This must be done after self._grpc_channel exists + self._prep_wrapped_messages(client_info) @property def grpc_channel(self) -> aio.Channel: @@ -201,13 +229,6 @@ def grpc_channel(self) -> aio.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/trace_v2/types/__init__.py b/google/cloud/trace_v2/types/__init__.py index 4b94729a..27530d1f 100644 --- a/google/cloud/trace_v2/types/__init__.py +++ b/google/cloud/trace_v2/types/__init__.py @@ -16,20 +16,19 @@ # from .trace import ( - Span, AttributeValue, - StackTrace, Module, + Span, + StackTrace, TruncatableString, ) from .tracing import BatchWriteSpansRequest - __all__ = ( - "Span", "AttributeValue", - "StackTrace", "Module", + "Span", + "StackTrace", "TruncatableString", "BatchWriteSpansRequest", ) diff --git a/google/cloud/trace_v2/types/trace.py b/google/cloud/trace_v2/types/trace.py index ab8773bb..903d298c 100644 --- a/google/cloud/trace_v2/types/trace.py +++ b/google/cloud/trace_v2/types/trace.py @@ -59,7 +59,7 @@ class Span(proto.Message): parent_span_id (str): The [SPAN_ID] of this span's parent span. If this is a root span, then this field must be empty. - display_name (~.trace.TruncatableString): + display_name (google.cloud.trace_v2.types.TruncatableString): Required. A description of the span's operation (up to 128 bytes). Stackdriver Trace displays the description in the Google Cloud @@ -71,44 +71,44 @@ class Span(proto.Message): application and at the same call point. This makes it easier to correlate spans in different traces. - start_time (~.timestamp.Timestamp): + start_time (google.protobuf.timestamp_pb2.Timestamp): Required. The start time of the span. On the client side, this is the time kept by the local machine where the span execution starts. On the server side, this is the time when the server's application handler starts running. - end_time (~.timestamp.Timestamp): + end_time (google.protobuf.timestamp_pb2.Timestamp): Required. The end time of the span. On the client side, this is the time kept by the local machine where the span execution ends. On the server side, this is the time when the server application handler stops running. - attributes (~.trace.Span.Attributes): + attributes (google.cloud.trace_v2.types.Span.Attributes): A set of attributes on the span. You can have up to 32 attributes per span. - stack_trace (~.trace.StackTrace): + stack_trace (google.cloud.trace_v2.types.StackTrace): Stack trace captured at the start of the span. - time_events (~.trace.Span.TimeEvents): + time_events (google.cloud.trace_v2.types.Span.TimeEvents): A set of time events. You can have up to 32 annotations and 128 message events per span. - links (~.trace.Span.Links): + links (google.cloud.trace_v2.types.Span.Links): Links associated with the span. You can have up to 128 links per Span. - status (~.gr_status.Status): + status (google.rpc.status_pb2.Status): Optional. The final status for this span. - same_process_as_parent_span (~.wrappers.BoolValue): + same_process_as_parent_span (google.protobuf.wrappers_pb2.BoolValue): Optional. Set this parameter to indicate whether this span is in the same process as its parent. If you do not set this parameter, Stackdriver Trace is unable to take advantage of this helpful information. - child_span_count (~.wrappers.Int32Value): + child_span_count (google.protobuf.wrappers_pb2.Int32Value): Optional. The number of child spans that were generated while this span was active. If set, allows implementation to detect missing child spans. - span_kind (~.trace.Span.SpanKind): + span_kind (google.cloud.trace_v2.types.Span.SpanKind): Optional. Distinguishes between spans generated in a particular context. For example, two spans with the same name may be distinguished using ``CLIENT`` (caller) and @@ -130,7 +130,7 @@ class Attributes(proto.Message): r"""A set of attributes, each in the format ``[KEY]:[VALUE]``. Attributes: - attribute_map (Sequence[~.trace.Span.Attributes.AttributeMapEntry]): + attribute_map (Sequence[google.cloud.trace_v2.types.Span.Attributes.AttributeMapEntry]): The set of attributes. Each attribute's key can be up to 128 bytes long. The value can be a string up to 256 bytes, a signed 64-bit integer, or the Boolean values ``true`` and @@ -159,12 +159,12 @@ class TimeEvent(proto.Message): r"""A time-stamped annotation or message event in the Span. Attributes: - time (~.timestamp.Timestamp): + time (google.protobuf.timestamp_pb2.Timestamp): The timestamp indicating the time the event occurred. - annotation (~.trace.Span.TimeEvent.Annotation): + annotation (google.cloud.trace_v2.types.Span.TimeEvent.Annotation): Text annotation with a set of attributes. - message_event (~.trace.Span.TimeEvent.MessageEvent): + message_event (google.cloud.trace_v2.types.Span.TimeEvent.MessageEvent): An event describing a message sent/received between Spans. """ @@ -173,11 +173,11 @@ class Annotation(proto.Message): r"""Text annotation with a set of attributes. Attributes: - description (~.trace.TruncatableString): + description (google.cloud.trace_v2.types.TruncatableString): A user-supplied message describing the event. The maximum length for the description is 256 bytes. - attributes (~.trace.Span.Attributes): + attributes (google.cloud.trace_v2.types.Span.Attributes): A set of attributes on the annotation. You can have up to 4 attributes per Annotation. """ @@ -194,7 +194,7 @@ class MessageEvent(proto.Message): r"""An event describing a message sent/received between Spans. Attributes: - type (~.trace.Span.TimeEvent.MessageEvent.Type): + type (google.cloud.trace_v2.types.Span.TimeEvent.MessageEvent.Type): Type of MessageEvent. Indicates whether the message was sent or received. id (int): @@ -246,7 +246,7 @@ class TimeEvents(proto.Message): pairs, or details of a message sent/received between Spans. Attributes: - time_event (Sequence[~.trace.Span.TimeEvent]): + time_event (Sequence[google.cloud.trace_v2.types.Span.TimeEvent]): A collection of ``TimeEvent``\ s. dropped_annotations_count (int): The number of dropped annotations in all the @@ -278,10 +278,10 @@ class Link(proto.Message): The [TRACE_ID] for a trace within a project. span_id (str): The [SPAN_ID] for a span within a trace. - type (~.trace.Span.Link.Type): + type (google.cloud.trace_v2.types.Span.Link.Type): The relationship of the current span relative to the linked span. - attributes (~.trace.Span.Attributes): + attributes (google.cloud.trace_v2.types.Span.Attributes): A set of attributes on the link. You have have up to 32 attributes per link. """ @@ -307,7 +307,7 @@ class Links(proto.Message): a span in the same or different trace. Attributes: - link (Sequence[~.trace.Span.Link]): + link (Sequence[google.cloud.trace_v2.types.Span.Link]): A collection of links. dropped_links_count (int): The number of dropped links after the maximum @@ -356,7 +356,7 @@ class AttributeValue(proto.Message): r"""The allowed types for [VALUE] in a ``[KEY]:[VALUE]`` attribute. Attributes: - string_value (~.trace.TruncatableString): + string_value (google.cloud.trace_v2.types.TruncatableString): A string up to 256 bytes long. int_value (int): A 64-bit signed integer. @@ -377,7 +377,7 @@ class StackTrace(proto.Message): r"""A call stack appearing in a trace. Attributes: - stack_frames (~.trace.StackTrace.StackFrames): + stack_frames (google.cloud.trace_v2.types.StackTrace.StackFrames): Stack frames in this stack trace. A maximum of 128 frames are allowed. stack_trace_hash_id (int): @@ -396,15 +396,15 @@ class StackFrame(proto.Message): r"""Represents a single stack frame in a stack trace. Attributes: - function_name (~.trace.TruncatableString): + function_name (google.cloud.trace_v2.types.TruncatableString): The fully-qualified name that uniquely identifies the function or method that is active in this frame (up to 1024 bytes). - original_function_name (~.trace.TruncatableString): + original_function_name (google.cloud.trace_v2.types.TruncatableString): An un-mangled function name, if ``function_name`` is `mangled `__. The name can be fully-qualified (up to 1024 bytes). - file_name (~.trace.TruncatableString): + file_name (google.cloud.trace_v2.types.TruncatableString): The name of the source file where the function call appears (up to 256 bytes). line_number (int): @@ -414,10 +414,10 @@ class StackFrame(proto.Message): The column number where the function call appears, if available. This is important in JavaScript because of its anonymous functions. - load_module (~.trace.Module): + load_module (google.cloud.trace_v2.types.Module): The binary module from where the code was loaded. - source_version (~.trace.TruncatableString): + source_version (google.cloud.trace_v2.types.TruncatableString): The version of the deployed source code (up to 128 bytes). """ @@ -446,7 +446,7 @@ class StackFrames(proto.Message): r"""A collection of stack frames, which can be truncated. Attributes: - frame (Sequence[~.trace.StackTrace.StackFrame]): + frame (Sequence[google.cloud.trace_v2.types.StackTrace.StackFrame]): Stack frames in this call stack. dropped_frames_count (int): The number of stack frames that were dropped @@ -470,11 +470,11 @@ class Module(proto.Message): r"""Binary module. Attributes: - module (~.trace.TruncatableString): + module (google.cloud.trace_v2.types.TruncatableString): For example: main binary, kernel modules, and dynamic libraries such as libc.so, sharedlib.so (up to 256 bytes). - build_id (~.trace.TruncatableString): + build_id (google.cloud.trace_v2.types.TruncatableString): A unique identifier for the module, usually a hash of its contents (up to 128 bytes). """ diff --git a/google/cloud/trace_v2/types/tracing.py b/google/cloud/trace_v2/types/tracing.py index b2eaff79..57269d35 100644 --- a/google/cloud/trace_v2/types/tracing.py +++ b/google/cloud/trace_v2/types/tracing.py @@ -33,7 +33,7 @@ class BatchWriteSpansRequest(proto.Message): name (str): Required. The name of the project where the spans belong. The format is ``projects/[PROJECT_ID]``. - spans (Sequence[~.trace.Span]): + spans (Sequence[google.cloud.trace_v2.types.Span]): Required. A list of new spans. The span names must not match existing spans, or the results are undefined. diff --git a/noxfile.py b/noxfile.py index 090ed4b4..4d37cd3a 100644 --- a/noxfile.py +++ b/noxfile.py @@ -18,6 +18,7 @@ from __future__ import absolute_import import os +import pathlib import shutil import nox @@ -28,7 +29,23 @@ DEFAULT_PYTHON_VERSION = "3.8" SYSTEM_TEST_PYTHON_VERSIONS = ["3.8"] -UNIT_TEST_PYTHON_VERSIONS = ["3.6", "3.7", "3.8"] +UNIT_TEST_PYTHON_VERSIONS = ["3.6", "3.7", "3.8", "3.9"] + +CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() + +# 'docfx' is excluded since it only needs to run in 'docs-presubmit' +nox.options.sessions = [ + "unit", + "system", + "cover", + "lint", + "lint_setup_py", + "blacken", + "docs", +] + +# Error if a python version is missing +nox.options.error_on_missing_interpreters = True @nox.session(python=DEFAULT_PYTHON_VERSION) @@ -70,18 +87,23 @@ def lint_setup_py(session): def default(session): # Install all test dependencies, then install this package in-place. - session.install("asyncmock", "pytest-asyncio") - session.install("mock", "pytest", "pytest-cov") - session.install("-e", ".") + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + ) + session.install("asyncmock", "pytest-asyncio", "-c", constraints_path) + + session.install("mock", "pytest", "pytest-cov", "-c", constraints_path) + + session.install("-e", ".", "-c", constraints_path) # Run py.test against the unit tests. session.run( "py.test", "--quiet", - "--cov=google.cloud.cloudtrace", - "--cov=google.cloud", - "--cov=tests.unit", + f"--junitxml=unit_{session.python}_sponge_log.xml", + "--cov=google/cloud", + "--cov=tests/unit", "--cov-append", "--cov-config=.coveragerc", "--cov-report=", @@ -100,6 +122,9 @@ def unit(session): @nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS) def system(session): """Run the system test suite.""" + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + ) system_test_path = os.path.join("tests", "system.py") system_test_folder_path = os.path.join("tests", "system") @@ -109,6 +134,9 @@ def system(session): # Sanity check: Only run tests if the environment variable is set. if not os.environ.get("GOOGLE_APPLICATION_CREDENTIALS", ""): session.skip("Credentials must be set via environment variable") + # Install pyopenssl for mTLS testing. + if os.environ.get("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true": + session.install("pyopenssl") system_test_exists = os.path.exists(system_test_path) system_test_folder_exists = os.path.exists(system_test_folder_path) @@ -121,16 +149,26 @@ def system(session): # Install all test dependencies, then install this package into the # virtualenv's dist-packages. - session.install( - "mock", "pytest", "google-cloud-testutils", - ) - session.install("-e", ".") + session.install("mock", "pytest", "google-cloud-testutils", "-c", constraints_path) + session.install("-e", ".", "-c", constraints_path) # Run py.test against the system tests. if system_test_exists: - session.run("py.test", "--quiet", system_test_path, *session.posargs) + session.run( + "py.test", + "--quiet", + f"--junitxml=system_{session.python}_sponge_log.xml", + system_test_path, + *session.posargs, + ) if system_test_folder_exists: - session.run("py.test", "--quiet", system_test_folder_path, *session.posargs) + session.run( + "py.test", + "--quiet", + f"--junitxml=system_{session.python}_sponge_log.xml", + system_test_folder_path, + *session.posargs, + ) @nox.session(python=DEFAULT_PYTHON_VERSION) @@ -141,7 +179,7 @@ def cover(session): test runs (not system test runs), and then erases coverage data. """ session.install("coverage", "pytest-cov") - session.run("coverage", "report", "--show-missing", "--fail-under=100") + session.run("coverage", "report", "--show-missing", "--fail-under=98") session.run("coverage", "erase") @@ -173,7 +211,7 @@ def docfx(session): """Build the docfx yaml files for this library.""" session.install("-e", ".") - session.install("sphinx", "alabaster", "recommonmark", "sphinx-docfx-yaml") + session.install("sphinx", "alabaster", "recommonmark", "gcp-sphinx-docfx-yaml") shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) session.run( diff --git a/renovate.json b/renovate.json index 4fa94931..f08bc22c 100644 --- a/renovate.json +++ b/renovate.json @@ -1,5 +1,6 @@ { "extends": [ "config:base", ":preserveSemverRanges" - ] + ], + "ignorePaths": [".pre-commit-config.yaml"] } diff --git a/samples/snippets/noxfile.py b/samples/snippets/noxfile.py index 5660f08b..97bf7da8 100644 --- a/samples/snippets/noxfile.py +++ b/samples/snippets/noxfile.py @@ -17,6 +17,7 @@ import os from pathlib import Path import sys +from typing import Callable, Dict, List, Optional import nox @@ -37,22 +38,28 @@ TEST_CONFIG = { # You can opt out from the test for specific Python versions. - "ignored_versions": ["2.7"], + 'ignored_versions': ["2.7"], + + # Old samples are opted out of enforcing Python type hints + # All new samples should feature them + 'enforce_type_hints': False, + # An envvar key for determining the project id to use. Change it # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a # build specific Cloud project. You can also use your own string # to use your own Cloud project. - "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", + 'gcloud_project_env': 'GOOGLE_CLOUD_PROJECT', # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', + # A dictionary you want to inject into your test. Don't put any # secrets here. These values will override predefined values. - "envs": {}, + 'envs': {}, } try: # Ensure we can import noxfile_config in the project's directory. - sys.path.append(".") + sys.path.append('.') from noxfile_config import TEST_CONFIG_OVERRIDE except ImportError as e: print("No user noxfile_config found: detail: {}".format(e)) @@ -62,26 +69,26 @@ TEST_CONFIG.update(TEST_CONFIG_OVERRIDE) -def get_pytest_env_vars(): +def get_pytest_env_vars() -> Dict[str, str]: """Returns a dict for pytest invocation.""" ret = {} # Override the GCLOUD_PROJECT and the alias. - env_key = TEST_CONFIG["gcloud_project_env"] + env_key = TEST_CONFIG['gcloud_project_env'] # This should error out if not set. - ret["GOOGLE_CLOUD_PROJECT"] = os.environ[env_key] + ret['GOOGLE_CLOUD_PROJECT'] = os.environ[env_key] # Apply user supplied envs. - ret.update(TEST_CONFIG["envs"]) + ret.update(TEST_CONFIG['envs']) return ret # DO NOT EDIT - automatically generated. # All versions used to tested samples. -ALL_VERSIONS = ["2.7", "3.6", "3.7", "3.8"] +ALL_VERSIONS = ["2.7", "3.6", "3.7", "3.8", "3.9"] # Any default versions that should be ignored. -IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"] +IGNORED_VERSIONS = TEST_CONFIG['ignored_versions'] TESTED_VERSIONS = sorted([v for v in ALL_VERSIONS if v not in IGNORED_VERSIONS]) @@ -91,7 +98,7 @@ def get_pytest_env_vars(): # -def _determine_local_import_names(start_dir): +def _determine_local_import_names(start_dir: str) -> List[str]: """Determines all import names that should be considered "local". This is used when running the linter to insure that import order is @@ -129,17 +136,30 @@ def _determine_local_import_names(start_dir): @nox.session -def lint(session): - session.install("flake8", "flake8-import-order") +def lint(session: nox.sessions.Session) -> None: + if not TEST_CONFIG['enforce_type_hints']: + session.install("flake8", "flake8-import-order") + else: + session.install("flake8", "flake8-import-order", "flake8-annotations") local_names = _determine_local_import_names(".") args = FLAKE8_COMMON_ARGS + [ "--application-import-names", ",".join(local_names), - ".", + "." ] session.run("flake8", *args) +# +# Black +# + +@nox.session +def blacken(session: nox.sessions.Session) -> None: + session.install("black") + python_files = [path for path in os.listdir(".") if path.endswith(".py")] + + session.run("black", *python_files) # # Sample Tests @@ -149,7 +169,7 @@ def lint(session): PYTEST_COMMON_ARGS = ["--junitxml=sponge_log.xml"] -def _session_tests(session, post_install=None): +def _session_tests(session: nox.sessions.Session, post_install: Callable = None) -> None: """Runs py.test for a particular project.""" if os.path.exists("requirements.txt"): session.install("-r", "requirements.txt") @@ -175,14 +195,14 @@ def _session_tests(session, post_install=None): @nox.session(python=ALL_VERSIONS) -def py(session): +def py(session: nox.sessions.Session) -> None: """Runs py.test for a sample using the specified version of Python.""" if session.python in TESTED_VERSIONS: _session_tests(session) else: - session.skip( - "SKIPPED: {} tests are disabled for this sample.".format(session.python) - ) + session.skip("SKIPPED: {} tests are disabled for this sample.".format( + session.python + )) # @@ -190,7 +210,7 @@ def py(session): # -def _get_repo_root(): +def _get_repo_root() -> Optional[str]: """ Returns the root folder of the project. """ # Get root of this repository. Assume we don't have directories nested deeper than 10 items. p = Path(os.getcwd()) @@ -199,6 +219,11 @@ def _get_repo_root(): break if Path(p / ".git").exists(): return str(p) + # .git is not available in repos cloned via Cloud Build + # setup.py is always in the library's root, so use that instead + # https://github.com/googleapis/synthtool/issues/792 + if Path(p / "setup.py").exists(): + return str(p) p = p.parent raise Exception("Unable to detect repository root.") @@ -208,7 +233,7 @@ def _get_repo_root(): @nox.session @nox.parametrize("path", GENERATED_READMES) -def readmegen(session, path): +def readmegen(session: nox.sessions.Session, path: str) -> None: """(Re-)generates the readme for a sample.""" session.install("jinja2", "pyyaml") dir_ = os.path.dirname(path) diff --git a/scripts/fixup_trace_v1_keywords.py b/scripts/fixup_trace_v1_keywords.py index 71a02d87..29536f6a 100644 --- a/scripts/fixup_trace_v1_keywords.py +++ b/scripts/fixup_trace_v1_keywords.py @@ -1,3 +1,4 @@ +#! /usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright 2020 Google LLC diff --git a/scripts/fixup_trace_v2_keywords.py b/scripts/fixup_trace_v2_keywords.py index f01c1004..ec15f7f3 100644 --- a/scripts/fixup_trace_v2_keywords.py +++ b/scripts/fixup_trace_v2_keywords.py @@ -1,3 +1,4 @@ +#! /usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright 2020 Google LLC diff --git a/setup.py b/setup.py index f0a8058f..5336b75b 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ # 'Development Status :: 5 - Production/Stable' release_status = "Development Status :: 5 - Production/Stable" dependencies = [ - "google-api-core[grpc] >= 1.22.0, < 2.0.0dev", + "google-api-core[grpc] >= 1.22.2, < 2.0.0dev", "proto-plus >= 1.4.0", ] extras = {} diff --git a/synth.metadata b/synth.metadata index d707a262..ef817e01 100644 --- a/synth.metadata +++ b/synth.metadata @@ -3,22 +3,22 @@ { "git": { "name": ".", - "remote": "https://github.com/googleapis/python-trace.git", - "sha": "423fb47f6d3294fca9fe1c942c42d941c3ef3378" + "remote": "git@github.com:googleapis/python-trace", + "sha": "1806034ea0beded6473de7d0938b92243d653c4f" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "05de3e1e14a0b07eab8b474e669164dbd31f81fb" + "sha": "ff39353f34a36e7643b86e97724e4027ab466dc6" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "d302f93d7f47e2852e585ac35ab2d15585717ec0" + "sha": "ff39353f34a36e7643b86e97724e4027ab466dc6" } } ], @@ -41,99 +41,5 @@ "generator": "bazel" } } - ], - "generatedFiles": [ - ".coveragerc", - ".flake8", - ".github/CONTRIBUTING.md", - ".github/ISSUE_TEMPLATE/bug_report.md", - ".github/ISSUE_TEMPLATE/feature_request.md", - ".github/ISSUE_TEMPLATE/support_request.md", - ".github/PULL_REQUEST_TEMPLATE.md", - ".github/release-please.yml", - ".github/snippet-bot.yml", - ".gitignore", - ".kokoro/build.sh", - ".kokoro/continuous/common.cfg", - ".kokoro/continuous/continuous.cfg", - ".kokoro/docker/docs/Dockerfile", - ".kokoro/docker/docs/fetch_gpg_keys.sh", - ".kokoro/docs/common.cfg", - ".kokoro/docs/docs-presubmit.cfg", - ".kokoro/docs/docs.cfg", - ".kokoro/presubmit/common.cfg", - ".kokoro/presubmit/presubmit.cfg", - ".kokoro/publish-docs.sh", - ".kokoro/release.sh", - ".kokoro/release/common.cfg", - ".kokoro/release/release.cfg", - ".kokoro/samples/lint/common.cfg", - ".kokoro/samples/lint/continuous.cfg", - ".kokoro/samples/lint/periodic.cfg", - ".kokoro/samples/lint/presubmit.cfg", - ".kokoro/samples/python3.6/common.cfg", - ".kokoro/samples/python3.6/continuous.cfg", - ".kokoro/samples/python3.6/periodic.cfg", - ".kokoro/samples/python3.6/presubmit.cfg", - ".kokoro/samples/python3.7/common.cfg", - ".kokoro/samples/python3.7/continuous.cfg", - ".kokoro/samples/python3.7/periodic.cfg", - ".kokoro/samples/python3.7/presubmit.cfg", - ".kokoro/samples/python3.8/common.cfg", - ".kokoro/samples/python3.8/continuous.cfg", - ".kokoro/samples/python3.8/periodic.cfg", - ".kokoro/samples/python3.8/presubmit.cfg", - ".kokoro/test-samples.sh", - ".kokoro/trampoline.sh", - ".kokoro/trampoline_v2.sh", - ".trampolinerc", - "CODE_OF_CONDUCT.md", - "CONTRIBUTING.rst", - "LICENSE", - "MANIFEST.in", - "docs/_static/custom.css", - "docs/_templates/layout.html", - "docs/conf.py", - "docs/multiprocessing.rst", - "google/cloud/trace_v1/__init__.py", - "google/cloud/trace_v1/gapic/__init__.py", - "google/cloud/trace_v1/gapic/enums.py", - "google/cloud/trace_v1/gapic/trace_service_client.py", - "google/cloud/trace_v1/gapic/trace_service_client_config.py", - "google/cloud/trace_v1/gapic/transports/__init__.py", - "google/cloud/trace_v1/gapic/transports/trace_service_grpc_transport.py", - "google/cloud/trace_v1/proto/__init__.py", - "google/cloud/trace_v1/proto/trace.proto", - "google/cloud/trace_v1/proto/trace_pb2.py", - "google/cloud/trace_v1/proto/trace_pb2_grpc.py", - "google/cloud/trace_v1/types.py", - "google/cloud/trace_v2/__init__.py", - "google/cloud/trace_v2/gapic/__init__.py", - "google/cloud/trace_v2/gapic/enums.py", - "google/cloud/trace_v2/gapic/trace_service_client.py", - "google/cloud/trace_v2/gapic/trace_service_client_config.py", - "google/cloud/trace_v2/gapic/transports/__init__.py", - "google/cloud/trace_v2/gapic/transports/trace_service_grpc_transport.py", - "google/cloud/trace_v2/proto/__init__.py", - "google/cloud/trace_v2/proto/trace.proto", - "google/cloud/trace_v2/proto/trace_pb2.py", - "google/cloud/trace_v2/proto/trace_pb2_grpc.py", - "google/cloud/trace_v2/proto/tracing.proto", - "google/cloud/trace_v2/proto/tracing_pb2.py", - "google/cloud/trace_v2/proto/tracing_pb2_grpc.py", - "google/cloud/trace_v2/types.py", - "noxfile.py", - "renovate.json", - "scripts/decrypt-secrets.sh", - "scripts/readme-gen/readme_gen.py", - "scripts/readme-gen/templates/README.tmpl.rst", - "scripts/readme-gen/templates/auth.tmpl.rst", - "scripts/readme-gen/templates/auth_api_key.tmpl.rst", - "scripts/readme-gen/templates/install_deps.tmpl.rst", - "scripts/readme-gen/templates/install_portaudio.tmpl.rst", - "setup.cfg", - "testing/.gitignore", - "tests/unit/gapic/v1/test_trace_service_client_v1.py", - "tests/unit/gapic/v2/test_trace_service_client_v2.py" ] } \ No newline at end of file diff --git a/synth.py b/synth.py index 982a2bb7..87ac3b93 100644 --- a/synth.py +++ b/synth.py @@ -36,12 +36,20 @@ s.move(library, excludes=["docs/index.rst", "setup.py"]) +# Rename field `type_` to `type` in v1 and v2 to avoid breaking change +s.replace( + "google/**/types/*.py", + "type_", + "type" +) + # ---------------------------------------------------------------------------- # Add templated files # ---------------------------------------------------------------------------- templated_files = common.py_library( samples=True, # set to True only if there are samples microgenerator=True, + cov_level=98, ) python.py_samples(skip_readmes=True) diff --git a/testing/constraints-3.6.txt b/testing/constraints-3.6.txt index 8bee7da4..a37a34af 100644 --- a/testing/constraints-3.6.txt +++ b/testing/constraints-3.6.txt @@ -5,5 +5,5 @@ # # e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev", # Then this file should have foo==1.14.0 -google-api-core==1.22.0 -proto-plus==1.4.0 \ No newline at end of file +google-api-core==1.22.2 +proto-plus==1.4.0 diff --git a/tests/system/gapic/v1/test_system_trace_service_v1_vpcsc.py b/tests/system/gapic/v1/test_system_trace_service_v1_vpcsc.py deleted file mode 100644 index 4fd73811..00000000 --- a/tests/system/gapic/v1/test_system_trace_service_v1_vpcsc.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2019 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. - -# flake8: noqa - -import os -import pytest - -from google.api_core import exceptions -from google.cloud import trace_v1 -from test_utils.vpcsc_config import vpcsc_config - -_VPCSC_PROHIBITED_MESSAGE = "Request is prohibited by organization's policy." - - -@pytest.fixture -def client(): - return trace_v1.TraceServiceClient() - - -@vpcsc_config.skip_unless_inside_vpcsc -def test_list_traces_w_inside(client): - list(client.list_traces(project_id=vpcsc_config.project_inside)) # no perms issue - - -@vpcsc_config.skip_unless_inside_vpcsc -def test_list_traces_w_outside(client): - with pytest.raises(exceptions.PermissionDenied) as exc: - list(client.list_traces(project_id=vpcsc_config.project_outside)) - - assert _VPCSC_PROHIBITED_MESSAGE in exc.value.message - - -@vpcsc_config.skip_unless_inside_vpcsc -def test_get_trace_w_inside(client): - with pytest.raises(exceptions.InvalidArgument): - client.get_trace( - project_id=vpcsc_config.project_inside, trace_id="" - ) # no perms issue - - -@vpcsc_config.skip_unless_inside_vpcsc -def test_get_trace_w_outside(client): - with pytest.raises(exceptions.PermissionDenied) as exc: - client.get_trace(project_id=vpcsc_config.project_outside, trace_id="") - - assert _VPCSC_PROHIBITED_MESSAGE in exc.value.message - - -@vpcsc_config.skip_unless_inside_vpcsc -def test_patch_traces_w_inside(client): - with pytest.raises(exceptions.InvalidArgument): - client.patch_traces( - project_id=vpcsc_config.project_inside, traces={} - ) # no perms issue - - -@vpcsc_config.skip_unless_inside_vpcsc -def test_patch_traces_w_ouside(client): - with pytest.raises(exceptions.PermissionDenied) as exc: - client.patch_traces(project_id=vpcsc_config.project_outside, traces={}) - - assert _VPCSC_PROHIBITED_MESSAGE in exc.value.message diff --git a/tests/system/gapic/v2/test_system_trace_service_v2.py b/tests/system/gapic/v2/test_system_trace_service_v2.py deleted file mode 100644 index 0a73506a..00000000 --- a/tests/system/gapic/v2/test_system_trace_service_v2.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2018 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. - -import os -import time - -from google.cloud import trace_v2 - - -class TestSystemTraceService(object): - def test_batch_write_spans(self): - project_id = os.environ["PROJECT_ID"] - - client = trace_v2.TraceServiceClient() - name = f"projects/{project_id}" - spans = [] - client.batch_write_spans(name=name, spans=spans) diff --git a/tests/system/gapic/v2/test_system_trace_service_v2_vpcsc.py b/tests/system/gapic/v2/test_system_trace_service_v2_vpcsc.py deleted file mode 100644 index a373ba51..00000000 --- a/tests/system/gapic/v2/test_system_trace_service_v2_vpcsc.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2019 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. - -# flake8: noqa - -import os -import pytest - -from google.api_core import exceptions -from google.cloud import trace_v2 -from test_utils.vpcsc_config import vpcsc_config - -_VPCSC_PROHIBITED_MESSAGE = "Request is prohibited by organization's policy." - - -@pytest.fixture -def client(): - return trace_v2.TraceServiceClient() - - -@vpcsc_config.skip_unless_inside_vpcsc -def test_batch_write_spans_w_inside(client): - project_inside = f"projects/{vpcsc_config.project_inside}" - client.batch_write_spans(name=project_inside, spans=[]) # no raise - - -@vpcsc_config.skip_unless_inside_vpcsc -def test_batch_write_spans_w_outside(client): - project_outside = f"projects/{vpcsc_config.project_outside}" - - with pytest.raises(exceptions.PermissionDenied) as exc: - client.batch_write_spans(name=project_outside, spans=[]) - - assert _VPCSC_PROHIBITED_MESSAGE in exc.value.message diff --git a/tests/unit/gapic/trace_v1/__init__.py b/tests/unit/gapic/trace_v1/__init__.py index 8b137891..42ffdf2b 100644 --- a/tests/unit/gapic/trace_v1/__init__.py +++ b/tests/unit/gapic/trace_v1/__init__.py @@ -1 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/tests/unit/gapic/trace_v1/test_trace_service.py b/tests/unit/gapic/trace_v1/test_trace_service.py index c5883386..779623d5 100644 --- a/tests/unit/gapic/trace_v1/test_trace_service.py +++ b/tests/unit/gapic/trace_v1/test_trace_service.py @@ -82,7 +82,22 @@ def test__get_default_mtls_endpoint(): assert TraceServiceClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi -@pytest.mark.parametrize("client_class", [TraceServiceClient, TraceServiceAsyncClient]) +@pytest.mark.parametrize("client_class", [TraceServiceClient, TraceServiceAsyncClient,]) +def test_trace_service_client_from_service_account_info(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_info" + ) as factory: + factory.return_value = creds + info = {"valid": True} + client = client_class.from_service_account_info(info) + assert client.transport._credentials == creds + assert isinstance(client, client_class) + + assert client.transport._host == "cloudtrace.googleapis.com:443" + + +@pytest.mark.parametrize("client_class", [TraceServiceClient, TraceServiceAsyncClient,]) def test_trace_service_client_from_service_account_file(client_class): creds = credentials.AnonymousCredentials() with mock.patch.object( @@ -90,17 +105,22 @@ def test_trace_service_client_from_service_account_file(client_class): ) as factory: factory.return_value = creds client = client_class.from_service_account_file("dummy/file/path.json") - assert client._transport._credentials == creds + assert client.transport._credentials == creds + assert isinstance(client, client_class) client = client_class.from_service_account_json("dummy/file/path.json") - assert client._transport._credentials == creds + assert client.transport._credentials == creds + assert isinstance(client, client_class) - assert client._transport._host == "cloudtrace.googleapis.com:443" + assert client.transport._host == "cloudtrace.googleapis.com:443" def test_trace_service_client_get_transport_class(): transport = TraceServiceClient.get_transport_class() - assert transport == transports.TraceServiceGrpcTransport + available_transports = [ + transports.TraceServiceGrpcTransport, + ] + assert transport in available_transports transport = TraceServiceClient.get_transport_class("grpc") assert transport == transports.TraceServiceGrpcTransport @@ -149,15 +169,14 @@ def test_trace_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "never". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "never"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -166,15 +185,14 @@ def test_trace_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "always". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "always"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -183,56 +201,140 @@ def test_trace_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + (TraceServiceClient, transports.TraceServiceGrpcTransport, "grpc", "true"), + ( + TraceServiceAsyncClient, + transports.TraceServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + (TraceServiceClient, transports.TraceServiceGrpcTransport, "grpc", "false"), + ( + TraceServiceAsyncClient, + transports.TraceServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + TraceServiceClient, "DEFAULT_ENDPOINT", modify_default_endpoint(TraceServiceClient) +) +@mock.patch.object( + TraceServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TraceServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_trace_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): options = client_options.ClientOptions( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class(client_options=options) + + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT + patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, + host=expected_host, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=client_cert_source_callback, + client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and default_client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( "google.auth.transport.mtls.has_default_client_cert_source", return_value=True, ): - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) - - # 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. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + with mock.patch( + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client.DEFAULT_ENDPOINT + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback + + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( "google.auth.transport.mtls.has_default_client_cert_source", @@ -245,34 +347,11 @@ def test_trace_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has - # unsupported value. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "Unsupported"}): - with pytest.raises(MutualTLSChannelError): - client = client_class() - - # Check the case quota_project_id is provided - options = client_options.ClientOptions(quota_project_id="octopus") - with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id="octopus", - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) - @pytest.mark.parametrize( "client_class,transport_class,transport_name", @@ -298,8 +377,7 @@ def test_trace_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -329,8 +407,7 @@ def test_trace_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -347,8 +424,7 @@ def test_trace_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -364,7 +440,7 @@ def test_list_traces(transport: str = "grpc", request_type=trace.ListTracesReque request = request_type() # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object(type(client._transport.list_traces), "__call__") as call: + with mock.patch.object(type(client.transport.list_traces), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = trace.ListTracesResponse( next_page_token="next_page_token_value", @@ -379,6 +455,7 @@ def test_list_traces(transport: str = "grpc", request_type=trace.ListTracesReque assert args[0] == trace.ListTracesRequest() # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListTracesPager) assert response.next_page_token == "next_page_token_value" @@ -388,20 +465,36 @@ def test_list_traces_from_dict(): test_list_traces(request_type=dict) +def test_list_traces_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = TraceServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.list_traces), "__call__") as call: + client.list_traces() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == trace.ListTracesRequest() + + @pytest.mark.asyncio -async def test_list_traces_async(transport: str = "grpc_asyncio"): +async def test_list_traces_async( + transport: str = "grpc_asyncio", request_type=trace.ListTracesRequest +): client = TraceServiceAsyncClient( credentials=credentials.AnonymousCredentials(), transport=transport, ) # Everything is optional in proto3 as far as the runtime is concerned, # and we are mocking out the actual API, so just send an empty request. - request = trace.ListTracesRequest() + request = request_type() # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object( - type(client._client._transport.list_traces), "__call__" - ) as call: + with mock.patch.object(type(client.transport.list_traces), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( trace.ListTracesResponse(next_page_token="next_page_token_value",) @@ -413,7 +506,7 @@ async def test_list_traces_async(transport: str = "grpc_asyncio"): assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0] == request + assert args[0] == trace.ListTracesRequest() # Establish that the response is the type that we expect. assert isinstance(response, pagers.ListTracesAsyncPager) @@ -421,11 +514,16 @@ async def test_list_traces_async(transport: str = "grpc_asyncio"): assert response.next_page_token == "next_page_token_value" +@pytest.mark.asyncio +async def test_list_traces_async_from_dict(): + await test_list_traces_async(request_type=dict) + + def test_list_traces_flattened(): client = TraceServiceClient(credentials=credentials.AnonymousCredentials(),) # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object(type(client._transport.list_traces), "__call__") as call: + with mock.patch.object(type(client.transport.list_traces), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = trace.ListTracesResponse() @@ -457,9 +555,7 @@ async def test_list_traces_flattened_async(): client = TraceServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object( - type(client._client._transport.list_traces), "__call__" - ) as call: + with mock.patch.object(type(client.transport.list_traces), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = trace.ListTracesResponse() @@ -494,7 +590,7 @@ def test_list_traces_pager(): client = TraceServiceClient(credentials=credentials.AnonymousCredentials,) # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object(type(client._transport.list_traces), "__call__") as call: + with mock.patch.object(type(client.transport.list_traces), "__call__") as call: # Set the response to a series of pages. call.side_effect = ( trace.ListTracesResponse( @@ -521,7 +617,7 @@ def test_list_traces_pages(): client = TraceServiceClient(credentials=credentials.AnonymousCredentials,) # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object(type(client._transport.list_traces), "__call__") as call: + with mock.patch.object(type(client.transport.list_traces), "__call__") as call: # Set the response to a series of pages. call.side_effect = ( trace.ListTracesResponse( @@ -544,9 +640,7 @@ async def test_list_traces_async_pager(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client._client._transport.list_traces), - "__call__", - new_callable=mock.AsyncMock, + type(client.transport.list_traces), "__call__", new_callable=mock.AsyncMock ) as call: # Set the response to a series of pages. call.side_effect = ( @@ -575,9 +669,7 @@ async def test_list_traces_async_pages(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client._client._transport.list_traces), - "__call__", - new_callable=mock.AsyncMock, + type(client.transport.list_traces), "__call__", new_callable=mock.AsyncMock ) as call: # Set the response to a series of pages. call.side_effect = ( @@ -607,7 +699,7 @@ def test_get_trace(transport: str = "grpc", request_type=trace.GetTraceRequest): request = request_type() # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object(type(client._transport.get_trace), "__call__") as call: + with mock.patch.object(type(client.transport.get_trace), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = trace.Trace( project_id="project_id_value", trace_id="trace_id_value", @@ -622,6 +714,7 @@ def test_get_trace(transport: str = "grpc", request_type=trace.GetTraceRequest): assert args[0] == trace.GetTraceRequest() # Establish that the response is the type that we expect. + assert isinstance(response, trace.Trace) assert response.project_id == "project_id_value" @@ -633,20 +726,36 @@ def test_get_trace_from_dict(): test_get_trace(request_type=dict) +def test_get_trace_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = TraceServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.get_trace), "__call__") as call: + client.get_trace() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == trace.GetTraceRequest() + + @pytest.mark.asyncio -async def test_get_trace_async(transport: str = "grpc_asyncio"): +async def test_get_trace_async( + transport: str = "grpc_asyncio", request_type=trace.GetTraceRequest +): client = TraceServiceAsyncClient( credentials=credentials.AnonymousCredentials(), transport=transport, ) # Everything is optional in proto3 as far as the runtime is concerned, # and we are mocking out the actual API, so just send an empty request. - request = trace.GetTraceRequest() + request = request_type() # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object( - type(client._client._transport.get_trace), "__call__" - ) as call: + with mock.patch.object(type(client.transport.get_trace), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( trace.Trace(project_id="project_id_value", trace_id="trace_id_value",) @@ -658,7 +767,7 @@ async def test_get_trace_async(transport: str = "grpc_asyncio"): assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0] == request + assert args[0] == trace.GetTraceRequest() # Establish that the response is the type that we expect. assert isinstance(response, trace.Trace) @@ -668,11 +777,16 @@ async def test_get_trace_async(transport: str = "grpc_asyncio"): assert response.trace_id == "trace_id_value" +@pytest.mark.asyncio +async def test_get_trace_async_from_dict(): + await test_get_trace_async(request_type=dict) + + def test_get_trace_flattened(): client = TraceServiceClient(credentials=credentials.AnonymousCredentials(),) # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object(type(client._transport.get_trace), "__call__") as call: + with mock.patch.object(type(client.transport.get_trace), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = trace.Trace() @@ -710,9 +824,7 @@ async def test_get_trace_flattened_async(): client = TraceServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object( - type(client._client._transport.get_trace), "__call__" - ) as call: + with mock.patch.object(type(client.transport.get_trace), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = trace.Trace() @@ -757,7 +869,7 @@ def test_patch_traces(transport: str = "grpc", request_type=trace.PatchTracesReq request = request_type() # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object(type(client._transport.patch_traces), "__call__") as call: + with mock.patch.object(type(client.transport.patch_traces), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = None @@ -777,20 +889,36 @@ def test_patch_traces_from_dict(): test_patch_traces(request_type=dict) +def test_patch_traces_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = TraceServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.patch_traces), "__call__") as call: + client.patch_traces() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == trace.PatchTracesRequest() + + @pytest.mark.asyncio -async def test_patch_traces_async(transport: str = "grpc_asyncio"): +async def test_patch_traces_async( + transport: str = "grpc_asyncio", request_type=trace.PatchTracesRequest +): client = TraceServiceAsyncClient( credentials=credentials.AnonymousCredentials(), transport=transport, ) # Everything is optional in proto3 as far as the runtime is concerned, # and we are mocking out the actual API, so just send an empty request. - request = trace.PatchTracesRequest() + request = request_type() # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object( - type(client._client._transport.patch_traces), "__call__" - ) as call: + with mock.patch.object(type(client.transport.patch_traces), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) @@ -800,17 +928,22 @@ async def test_patch_traces_async(transport: str = "grpc_asyncio"): assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0] == request + assert args[0] == trace.PatchTracesRequest() # Establish that the response is the type that we expect. assert response is None +@pytest.mark.asyncio +async def test_patch_traces_async_from_dict(): + await test_patch_traces_async(request_type=dict) + + def test_patch_traces_flattened(): client = TraceServiceClient(credentials=credentials.AnonymousCredentials(),) # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object(type(client._transport.patch_traces), "__call__") as call: + with mock.patch.object(type(client.transport.patch_traces), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = None @@ -851,9 +984,7 @@ async def test_patch_traces_flattened_async(): client = TraceServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object( - type(client._client._transport.patch_traces), "__call__" - ) as call: + with mock.patch.object(type(client.transport.patch_traces), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = None @@ -927,7 +1058,7 @@ def test_transport_instance(): credentials=credentials.AnonymousCredentials(), ) client = TraceServiceClient(transport=transport) - assert client._transport is transport + assert client.transport is transport def test_transport_get_channel(): @@ -945,10 +1076,25 @@ def test_transport_get_channel(): assert channel +@pytest.mark.parametrize( + "transport_class", + [ + transports.TraceServiceGrpcTransport, + transports.TraceServiceGrpcAsyncIOTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + def test_transport_grpc_default(): # A client should use the gRPC transport by default. client = TraceServiceClient(credentials=credentials.AnonymousCredentials(),) - assert isinstance(client._transport, transports.TraceServiceGrpcTransport,) + assert isinstance(client.transport, transports.TraceServiceGrpcTransport,) def test_trace_service_base_transport_error(): @@ -1005,6 +1151,17 @@ def test_trace_service_base_transport_with_credentials_file(): ) +def test_trace_service_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(auth, "default") as adc, mock.patch( + "google.cloud.trace_v1.services.trace_service.transports.TraceServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.TraceServiceTransport() + adc.assert_called_once() + + def test_trace_service_auth_adc(): # If no credentials are provided, we should use ADC credentials. with mock.patch.object(auth, "default") as adc: @@ -1038,6 +1195,52 @@ def test_trace_service_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class", + [transports.TraceServiceGrpcTransport, transports.TraceServiceGrpcAsyncIOTransport], +) +def test_trace_service_grpc_transport_client_cert_source_for_mtls(transport_class): + cred = credentials.AnonymousCredentials() + + # Check ssl_channel_credentials is used if provided. + with mock.patch.object(transport_class, "create_channel") as mock_create_channel: + mock_ssl_channel_creds = mock.Mock() + transport_class( + host="squid.clam.whelk", + credentials=cred, + ssl_channel_credentials=mock_ssl_channel_creds, + ) + mock_create_channel.assert_called_once_with( + "squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/trace.append", + "https://www.googleapis.com/auth/trace.readonly", + ), + ssl_credentials=mock_ssl_channel_creds, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls + # is used. + with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()): + with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred: + transport_class( + credentials=cred, + client_cert_source_for_mtls=client_cert_source_callback, + ) + expected_cert, expected_key = client_cert_source_callback() + mock_ssl_cred.assert_called_once_with( + certificate_chain=expected_cert, private_key=expected_key + ) + + def test_trace_service_host_no_port(): client = TraceServiceClient( credentials=credentials.AnonymousCredentials(), @@ -1045,7 +1248,7 @@ def test_trace_service_host_no_port(): api_endpoint="cloudtrace.googleapis.com" ), ) - assert client._transport._host == "cloudtrace.googleapis.com:443" + assert client.transport._host == "cloudtrace.googleapis.com:443" def test_trace_service_host_with_port(): @@ -1055,201 +1258,232 @@ def test_trace_service_host_with_port(): api_endpoint="cloudtrace.googleapis.com:8000" ), ) - assert client._transport._host == "cloudtrace.googleapis.com:8000" + assert client.transport._host == "cloudtrace.googleapis.com:8000" def test_trace_service_grpc_transport_channel(): - channel = grpc.insecure_channel("http://localhost/") + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.TraceServiceGrpcTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called + assert transport._ssl_channel_credentials == None def test_trace_service_grpc_asyncio_transport_channel(): - channel = aio.insecure_channel("http://localhost/") + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.TraceServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called + assert transport._ssl_channel_credentials == None -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_trace_service_grpc_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. +@pytest.mark.parametrize( + "transport_class", + [transports.TraceServiceGrpcTransport, transports.TraceServiceGrpcAsyncIOTransport], +) +def test_trace_service_transport_channel_mtls_with_client_cert_source(transport_class): + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel" + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/trace.append", + "https://www.googleapis.com/auth/trace.readonly", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + assert transport.grpc_channel == mock_grpc_channel + assert transport._ssl_channel_credentials == mock_ssl_cred + +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. +@pytest.mark.parametrize( + "transport_class", + [transports.TraceServiceGrpcTransport, transports.TraceServiceGrpcAsyncIOTransport], +) +def test_trace_service_transport_channel_mtls_with_adc(transport_class): mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred + with mock.patch.multiple( + "google.auth.transport.grpc.SslCredentials", + __init__=mock.Mock(return_value=None), + ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), + ): + with mock.patch.object( + transport_class, "create_channel" + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/trace.append", + "https://www.googleapis.com/auth/trace.readonly", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + assert transport.grpc_channel == mock_grpc_channel - transport = transports.TraceServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/trace.append", - "https://www.googleapis.com/auth/trace.readonly", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, + +def test_common_billing_account_path(): + billing_account = "squid" + + expected = "billingAccounts/{billing_account}".format( + billing_account=billing_account, ) - assert transport.grpc_channel == mock_grpc_channel + actual = TraceServiceClient.common_billing_account_path(billing_account) + assert expected == actual -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_trace_service_grpc_asyncio_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() +def test_parse_common_billing_account_path(): + expected = { + "billing_account": "clam", + } + path = TraceServiceClient.common_billing_account_path(**expected) - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred + # Check that the path construction is reversible. + actual = TraceServiceClient.parse_common_billing_account_path(path) + assert expected == actual - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - transport = transports.TraceServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/trace.append", - "https://www.googleapis.com/auth/trace.readonly", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel +def test_common_folder_path(): + folder = "whelk" + expected = "folders/{folder}".format(folder=folder,) + actual = TraceServiceClient.common_folder_path(folder) + assert expected == actual -@pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] -) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_trace_service_grpc_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - # Mock google.auth.transport.grpc.SslCredentials class. - mock_ssl_cred = mock.Mock() - with mock.patch.multiple( - "google.auth.transport.grpc.SslCredentials", - __init__=mock.Mock(return_value=None), - ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), - ): - mock_cred = mock.Mock() - transport = transports.TraceServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/trace.append", - "https://www.googleapis.com/auth/trace.readonly", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel +def test_parse_common_folder_path(): + expected = { + "folder": "octopus", + } + path = TraceServiceClient.common_folder_path(**expected) + # Check that the path construction is reversible. + actual = TraceServiceClient.parse_common_folder_path(path) + assert expected == actual -@pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] -) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_trace_service_grpc_asyncio_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - # Mock google.auth.transport.grpc.SslCredentials class. - mock_ssl_cred = mock.Mock() - with mock.patch.multiple( - "google.auth.transport.grpc.SslCredentials", - __init__=mock.Mock(return_value=None), - ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), - ): - mock_cred = mock.Mock() - transport = transports.TraceServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/trace.append", - "https://www.googleapis.com/auth/trace.readonly", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel +def test_common_organization_path(): + organization = "oyster" + + expected = "organizations/{organization}".format(organization=organization,) + actual = TraceServiceClient.common_organization_path(organization) + assert expected == actual + + +def test_parse_common_organization_path(): + expected = { + "organization": "nudibranch", + } + path = TraceServiceClient.common_organization_path(**expected) + + # Check that the path construction is reversible. + actual = TraceServiceClient.parse_common_organization_path(path) + assert expected == actual + + +def test_common_project_path(): + project = "cuttlefish" + + expected = "projects/{project}".format(project=project,) + actual = TraceServiceClient.common_project_path(project) + assert expected == actual + + +def test_parse_common_project_path(): + expected = { + "project": "mussel", + } + path = TraceServiceClient.common_project_path(**expected) + + # Check that the path construction is reversible. + actual = TraceServiceClient.parse_common_project_path(path) + assert expected == actual + + +def test_common_location_path(): + project = "winkle" + location = "nautilus" + + expected = "projects/{project}/locations/{location}".format( + project=project, location=location, + ) + actual = TraceServiceClient.common_location_path(project, location) + assert expected == actual + + +def test_parse_common_location_path(): + expected = { + "project": "scallop", + "location": "abalone", + } + path = TraceServiceClient.common_location_path(**expected) + + # Check that the path construction is reversible. + actual = TraceServiceClient.parse_common_location_path(path) + assert expected == actual def test_client_withDEFAULT_CLIENT_INFO(): diff --git a/tests/unit/gapic/trace_v2/__init__.py b/tests/unit/gapic/trace_v2/__init__.py index 8b137891..42ffdf2b 100644 --- a/tests/unit/gapic/trace_v2/__init__.py +++ b/tests/unit/gapic/trace_v2/__init__.py @@ -1 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/tests/unit/gapic/trace_v2/test_trace_service.py b/tests/unit/gapic/trace_v2/test_trace_service.py index 9072a594..d25e90cc 100644 --- a/tests/unit/gapic/trace_v2/test_trace_service.py +++ b/tests/unit/gapic/trace_v2/test_trace_service.py @@ -38,10 +38,9 @@ from google.cloud.trace_v2.types import trace from google.cloud.trace_v2.types import tracing from google.oauth2 import service_account -from google.protobuf import any_pb2 as any # type: ignore +from google.protobuf import any_pb2 as gp_any # type: ignore from google.protobuf import timestamp_pb2 as timestamp # type: ignore from google.protobuf import wrappers_pb2 as wrappers # type: ignore -from google.rpc import status_pb2 as gr_status # type: ignore from google.rpc import status_pb2 as status # type: ignore @@ -86,7 +85,22 @@ def test__get_default_mtls_endpoint(): assert TraceServiceClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi -@pytest.mark.parametrize("client_class", [TraceServiceClient, TraceServiceAsyncClient]) +@pytest.mark.parametrize("client_class", [TraceServiceClient, TraceServiceAsyncClient,]) +def test_trace_service_client_from_service_account_info(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_info" + ) as factory: + factory.return_value = creds + info = {"valid": True} + client = client_class.from_service_account_info(info) + assert client.transport._credentials == creds + assert isinstance(client, client_class) + + assert client.transport._host == "cloudtrace.googleapis.com:443" + + +@pytest.mark.parametrize("client_class", [TraceServiceClient, TraceServiceAsyncClient,]) def test_trace_service_client_from_service_account_file(client_class): creds = credentials.AnonymousCredentials() with mock.patch.object( @@ -94,17 +108,22 @@ def test_trace_service_client_from_service_account_file(client_class): ) as factory: factory.return_value = creds client = client_class.from_service_account_file("dummy/file/path.json") - assert client._transport._credentials == creds + assert client.transport._credentials == creds + assert isinstance(client, client_class) client = client_class.from_service_account_json("dummy/file/path.json") - assert client._transport._credentials == creds + assert client.transport._credentials == creds + assert isinstance(client, client_class) - assert client._transport._host == "cloudtrace.googleapis.com:443" + assert client.transport._host == "cloudtrace.googleapis.com:443" def test_trace_service_client_get_transport_class(): transport = TraceServiceClient.get_transport_class() - assert transport == transports.TraceServiceGrpcTransport + available_transports = [ + transports.TraceServiceGrpcTransport, + ] + assert transport in available_transports transport = TraceServiceClient.get_transport_class("grpc") assert transport == transports.TraceServiceGrpcTransport @@ -153,15 +172,14 @@ def test_trace_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "never". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "never"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -170,15 +188,14 @@ def test_trace_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "always". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "always"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -187,56 +204,140 @@ def test_trace_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + (TraceServiceClient, transports.TraceServiceGrpcTransport, "grpc", "true"), + ( + TraceServiceAsyncClient, + transports.TraceServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + (TraceServiceClient, transports.TraceServiceGrpcTransport, "grpc", "false"), + ( + TraceServiceAsyncClient, + transports.TraceServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + TraceServiceClient, "DEFAULT_ENDPOINT", modify_default_endpoint(TraceServiceClient) +) +@mock.patch.object( + TraceServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TraceServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_trace_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): options = client_options.ClientOptions( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class(client_options=options) + + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT + patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, + host=expected_host, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=client_cert_source_callback, + client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and default_client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( "google.auth.transport.mtls.has_default_client_cert_source", return_value=True, ): - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) - - # 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. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + with mock.patch( + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client.DEFAULT_ENDPOINT + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback + + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( "google.auth.transport.mtls.has_default_client_cert_source", @@ -249,34 +350,11 @@ def test_trace_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has - # unsupported value. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "Unsupported"}): - with pytest.raises(MutualTLSChannelError): - client = client_class() - - # Check the case quota_project_id is provided - options = client_options.ClientOptions(quota_project_id="octopus") - with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id="octopus", - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) - @pytest.mark.parametrize( "client_class,transport_class,transport_name", @@ -302,8 +380,7 @@ def test_trace_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -333,8 +410,7 @@ def test_trace_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -351,8 +427,7 @@ def test_trace_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -371,7 +446,7 @@ def test_batch_write_spans( # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client._transport.batch_write_spans), "__call__" + type(client.transport.batch_write_spans), "__call__" ) as call: # Designate an appropriate return value for the call. call.return_value = None @@ -392,19 +467,39 @@ def test_batch_write_spans_from_dict(): test_batch_write_spans(request_type=dict) +def test_batch_write_spans_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = TraceServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.batch_write_spans), "__call__" + ) as call: + client.batch_write_spans() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == tracing.BatchWriteSpansRequest() + + @pytest.mark.asyncio -async def test_batch_write_spans_async(transport: str = "grpc_asyncio"): +async def test_batch_write_spans_async( + transport: str = "grpc_asyncio", request_type=tracing.BatchWriteSpansRequest +): client = TraceServiceAsyncClient( credentials=credentials.AnonymousCredentials(), transport=transport, ) # Everything is optional in proto3 as far as the runtime is concerned, # and we are mocking out the actual API, so just send an empty request. - request = tracing.BatchWriteSpansRequest() + request = request_type() # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client._client._transport.batch_write_spans), "__call__" + type(client.transport.batch_write_spans), "__call__" ) as call: # Designate an appropriate return value for the call. call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) @@ -415,12 +510,17 @@ async def test_batch_write_spans_async(transport: str = "grpc_asyncio"): assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0] == request + assert args[0] == tracing.BatchWriteSpansRequest() # Establish that the response is the type that we expect. assert response is None +@pytest.mark.asyncio +async def test_batch_write_spans_async_from_dict(): + await test_batch_write_spans_async(request_type=dict) + + def test_batch_write_spans_field_headers(): client = TraceServiceClient(credentials=credentials.AnonymousCredentials(),) @@ -431,7 +531,7 @@ def test_batch_write_spans_field_headers(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client._transport.batch_write_spans), "__call__" + type(client.transport.batch_write_spans), "__call__" ) as call: call.return_value = None @@ -458,7 +558,7 @@ async def test_batch_write_spans_field_headers_async(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client._client._transport.batch_write_spans), "__call__" + type(client.transport.batch_write_spans), "__call__" ) as call: call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) @@ -479,7 +579,7 @@ def test_batch_write_spans_flattened(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client._transport.batch_write_spans), "__call__" + type(client.transport.batch_write_spans), "__call__" ) as call: # Designate an appropriate return value for the call. call.return_value = None @@ -519,7 +619,7 @@ async def test_batch_write_spans_flattened_async(): # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( - type(client._client._transport.batch_write_spans), "__call__" + type(client.transport.batch_write_spans), "__call__" ) as call: # Designate an appropriate return value for the call. call.return_value = None @@ -565,7 +665,7 @@ def test_create_span(transport: str = "grpc", request_type=trace.Span): request = request_type() # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object(type(client._transport.create_span), "__call__") as call: + with mock.patch.object(type(client.transport.create_span), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = trace.Span( name="name_value", @@ -583,6 +683,7 @@ def test_create_span(transport: str = "grpc", request_type=trace.Span): assert args[0] == trace.Span() # Establish that the response is the type that we expect. + assert isinstance(response, trace.Span) assert response.name == "name_value" @@ -598,20 +699,36 @@ def test_create_span_from_dict(): test_create_span(request_type=dict) +def test_create_span_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = TraceServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.create_span), "__call__") as call: + client.create_span() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == trace.Span() + + @pytest.mark.asyncio -async def test_create_span_async(transport: str = "grpc_asyncio"): +async def test_create_span_async( + transport: str = "grpc_asyncio", request_type=trace.Span +): client = TraceServiceAsyncClient( credentials=credentials.AnonymousCredentials(), transport=transport, ) # Everything is optional in proto3 as far as the runtime is concerned, # and we are mocking out the actual API, so just send an empty request. - request = trace.Span() + request = request_type() # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object( - type(client._client._transport.create_span), "__call__" - ) as call: + with mock.patch.object(type(client.transport.create_span), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( trace.Span( @@ -628,7 +745,7 @@ async def test_create_span_async(transport: str = "grpc_asyncio"): assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0] == request + assert args[0] == trace.Span() # Establish that the response is the type that we expect. assert isinstance(response, trace.Span) @@ -642,6 +759,11 @@ async def test_create_span_async(transport: str = "grpc_asyncio"): assert response.span_kind == trace.Span.SpanKind.INTERNAL +@pytest.mark.asyncio +async def test_create_span_async_from_dict(): + await test_create_span_async(request_type=dict) + + def test_create_span_field_headers(): client = TraceServiceClient(credentials=credentials.AnonymousCredentials(),) @@ -651,7 +773,7 @@ def test_create_span_field_headers(): request.name = "name/value" # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object(type(client._transport.create_span), "__call__") as call: + with mock.patch.object(type(client.transport.create_span), "__call__") as call: call.return_value = trace.Span() client.create_span(request) @@ -676,9 +798,7 @@ async def test_create_span_field_headers_async(): request.name = "name/value" # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object( - type(client._client._transport.create_span), "__call__" - ) as call: + with mock.patch.object(type(client.transport.create_span), "__call__") as call: call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(trace.Span()) await client.create_span(request) @@ -729,7 +849,7 @@ def test_transport_instance(): credentials=credentials.AnonymousCredentials(), ) client = TraceServiceClient(transport=transport) - assert client._transport is transport + assert client.transport is transport def test_transport_get_channel(): @@ -747,10 +867,25 @@ def test_transport_get_channel(): assert channel +@pytest.mark.parametrize( + "transport_class", + [ + transports.TraceServiceGrpcTransport, + transports.TraceServiceGrpcAsyncIOTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + def test_transport_grpc_default(): # A client should use the gRPC transport by default. client = TraceServiceClient(credentials=credentials.AnonymousCredentials(),) - assert isinstance(client._transport, transports.TraceServiceGrpcTransport,) + assert isinstance(client.transport, transports.TraceServiceGrpcTransport,) def test_trace_service_base_transport_error(): @@ -805,6 +940,17 @@ def test_trace_service_base_transport_with_credentials_file(): ) +def test_trace_service_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(auth, "default") as adc, mock.patch( + "google.cloud.trace_v2.services.trace_service.transports.TraceServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.TraceServiceTransport() + adc.assert_called_once() + + def test_trace_service_auth_adc(): # If no credentials are provided, we should use ADC credentials. with mock.patch.object(auth, "default") as adc: @@ -836,6 +982,51 @@ def test_trace_service_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class", + [transports.TraceServiceGrpcTransport, transports.TraceServiceGrpcAsyncIOTransport], +) +def test_trace_service_grpc_transport_client_cert_source_for_mtls(transport_class): + cred = credentials.AnonymousCredentials() + + # Check ssl_channel_credentials is used if provided. + with mock.patch.object(transport_class, "create_channel") as mock_create_channel: + mock_ssl_channel_creds = mock.Mock() + transport_class( + host="squid.clam.whelk", + credentials=cred, + ssl_channel_credentials=mock_ssl_channel_creds, + ) + mock_create_channel.assert_called_once_with( + "squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/trace.append", + ), + ssl_credentials=mock_ssl_channel_creds, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + # Check if ssl_channel_credentials is not provided, then client_cert_source_for_mtls + # is used. + with mock.patch.object(transport_class, "create_channel", return_value=mock.Mock()): + with mock.patch("grpc.ssl_channel_credentials") as mock_ssl_cred: + transport_class( + credentials=cred, + client_cert_source_for_mtls=client_cert_source_callback, + ) + expected_cert, expected_key = client_cert_source_callback() + mock_ssl_cred.assert_called_once_with( + certificate_chain=expected_cert, private_key=expected_key + ) + + def test_trace_service_host_no_port(): client = TraceServiceClient( credentials=credentials.AnonymousCredentials(), @@ -843,7 +1034,7 @@ def test_trace_service_host_no_port(): api_endpoint="cloudtrace.googleapis.com" ), ) - assert client._transport._host == "cloudtrace.googleapis.com:443" + assert client.transport._host == "cloudtrace.googleapis.com:443" def test_trace_service_host_with_port(): @@ -853,197 +1044,129 @@ def test_trace_service_host_with_port(): api_endpoint="cloudtrace.googleapis.com:8000" ), ) - assert client._transport._host == "cloudtrace.googleapis.com:8000" + assert client.transport._host == "cloudtrace.googleapis.com:8000" def test_trace_service_grpc_transport_channel(): - channel = grpc.insecure_channel("http://localhost/") + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.TraceServiceGrpcTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called + assert transport._ssl_channel_credentials == None def test_trace_service_grpc_asyncio_transport_channel(): - channel = aio.insecure_channel("http://localhost/") + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.TraceServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_trace_service_grpc_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - transport = transports.TraceServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/trace.append", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_trace_service_grpc_asyncio_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - transport = transports.TraceServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/trace.append", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel + assert transport._ssl_channel_credentials == None +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] + "transport_class", + [transports.TraceServiceGrpcTransport, transports.TraceServiceGrpcAsyncIOTransport], ) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_trace_service_grpc_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. - mock_ssl_cred = mock.Mock() - with mock.patch.multiple( - "google.auth.transport.grpc.SslCredentials", - __init__=mock.Mock(return_value=None), - ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), - ): - mock_cred = mock.Mock() - transport = transports.TraceServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/trace.append", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel +def test_trace_service_transport_channel_mtls_with_client_cert_source(transport_class): + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel" + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/trace.append", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + assert transport.grpc_channel == mock_grpc_channel + assert transport._ssl_channel_credentials == mock_ssl_cred +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] + "transport_class", + [transports.TraceServiceGrpcTransport, transports.TraceServiceGrpcAsyncIOTransport], ) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_trace_service_grpc_asyncio_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. +def test_trace_service_transport_channel_mtls_with_adc(transport_class): mock_ssl_cred = mock.Mock() with mock.patch.multiple( "google.auth.transport.grpc.SslCredentials", __init__=mock.Mock(return_value=None), ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): - mock_cred = mock.Mock() - transport = transports.TraceServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/trace.append", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel + with mock.patch.object( + transport_class, "create_channel" + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) + + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/trace.append", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + assert transport.grpc_channel == mock_grpc_channel def test_span_path(): @@ -1071,6 +1194,107 @@ def test_parse_span_path(): assert expected == actual +def test_common_billing_account_path(): + billing_account = "cuttlefish" + + expected = "billingAccounts/{billing_account}".format( + billing_account=billing_account, + ) + actual = TraceServiceClient.common_billing_account_path(billing_account) + assert expected == actual + + +def test_parse_common_billing_account_path(): + expected = { + "billing_account": "mussel", + } + path = TraceServiceClient.common_billing_account_path(**expected) + + # Check that the path construction is reversible. + actual = TraceServiceClient.parse_common_billing_account_path(path) + assert expected == actual + + +def test_common_folder_path(): + folder = "winkle" + + expected = "folders/{folder}".format(folder=folder,) + actual = TraceServiceClient.common_folder_path(folder) + assert expected == actual + + +def test_parse_common_folder_path(): + expected = { + "folder": "nautilus", + } + path = TraceServiceClient.common_folder_path(**expected) + + # Check that the path construction is reversible. + actual = TraceServiceClient.parse_common_folder_path(path) + assert expected == actual + + +def test_common_organization_path(): + organization = "scallop" + + expected = "organizations/{organization}".format(organization=organization,) + actual = TraceServiceClient.common_organization_path(organization) + assert expected == actual + + +def test_parse_common_organization_path(): + expected = { + "organization": "abalone", + } + path = TraceServiceClient.common_organization_path(**expected) + + # Check that the path construction is reversible. + actual = TraceServiceClient.parse_common_organization_path(path) + assert expected == actual + + +def test_common_project_path(): + project = "squid" + + expected = "projects/{project}".format(project=project,) + actual = TraceServiceClient.common_project_path(project) + assert expected == actual + + +def test_parse_common_project_path(): + expected = { + "project": "clam", + } + path = TraceServiceClient.common_project_path(**expected) + + # Check that the path construction is reversible. + actual = TraceServiceClient.parse_common_project_path(path) + assert expected == actual + + +def test_common_location_path(): + project = "whelk" + location = "octopus" + + expected = "projects/{project}/locations/{location}".format( + project=project, location=location, + ) + actual = TraceServiceClient.common_location_path(project, location) + assert expected == actual + + +def test_parse_common_location_path(): + expected = { + "project": "oyster", + "location": "nudibranch", + } + path = TraceServiceClient.common_location_path(**expected) + + # Check that the path construction is reversible. + actual = TraceServiceClient.parse_common_location_path(path) + assert expected == actual + + def test_client_withDEFAULT_CLIENT_INFO(): client_info = gapic_v1.client_info.ClientInfo()