diff --git a/.coveragerc b/.coveragerc index 10cb72e8..9a9b9f3e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,27 +1,11 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Generated by synthtool. DO NOT EDIT! [run] branch = True [report] fail_under = 100 show_missing = True -omit = google/cloud/errorreporting/__init__.py, .nox/* +omit = + google/cloud/errorreporting/__init__.py exclude_lines = # Re-enable the standard pragma pragma: NO COVER @@ -31,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/.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 2a8a5044..4f96711f 100755 --- a/.kokoro/build.sh +++ b/.kokoro/build.sh @@ -40,6 +40,16 @@ python3 -m pip uninstall --yes --quiet nox-automation 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 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/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/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/.kokoro/test-samples-against-head.sh b/.kokoro/test-samples-against-head.sh new file mode 100755 index 00000000..320a9129 --- /dev/null +++ b/.kokoro/test-samples-against-head.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# A customized test runner for samples. +# +# For periodic builds, you can specify this file for testing against head. + +# `-e` enables the script to automatically fail when a command fails +# `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero +set -eo pipefail +# Enables `**` to include files nested inside sub-folders +shopt -s globstar + +cd github/python-error-reporting + +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 dfbbc8de..fefd09c5 100755 --- a/.kokoro/test-samples.sh +++ b/.kokoro/test-samples.sh @@ -13,6 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +# The default test runner for samples. +# +# For periodic builds, we rewinds the repo to the latest release, and +# run test-samples-impl.sh. # `-e` enables the script to automatically fail when a command fails # `-o pipefail` sets the exit code to the rightmost comment to exit with a non-zero @@ -24,87 +28,19 @@ cd github/python-error-reporting # Run periodic samples tests at latest release if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then + # preserving the test runner implementation. + cp .kokoro/test-samples-impl.sh "${TMPDIR}/test-samples-impl.sh" + echo "--- IMPORTANT IMPORTANT IMPORTANT ---" + echo "Now we rewind the repo back to the latest release..." LATEST_RELEASE=$(git describe --abbrev=0 --tags) git checkout $LATEST_RELEASE -fi - -# Exit early if samples directory doesn't exist -if [ ! -d "./samples" ]; then - echo "No tests run. `./samples` not found" - exit 0 -fi - -# Disable buffering, so that the logs stream through. -export PYTHONUNBUFFERED=1 - -# Debug: show build environment -env | grep KOKORO - -# Install nox -python3.6 -m pip install --upgrade --quiet nox - -# Use secrets acessor service account to get secrets -if [[ -f "${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" ]]; then - gcloud auth activate-service-account \ - --key-file="${KOKORO_GFILE_DIR}/secrets_viewer_service_account.json" \ - --project="cloud-devrel-kokoro-resources" -fi - -# This script will create 3 files: -# - testing/test-env.sh -# - testing/service-account.json -# - testing/client-secrets.json -./scripts/decrypt-secrets.sh - -source ./testing/test-env.sh -export GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/testing/service-account.json - -# For cloud-run session, we activate the service account for gcloud sdk. -gcloud auth activate-service-account \ - --key-file "${GOOGLE_APPLICATION_CREDENTIALS}" - -export GOOGLE_CLIENT_SECRETS=$(pwd)/testing/client-secrets.json - -echo -e "\n******************** TESTING PROJECTS ********************" - -# Switch to 'fail at end' to allow all tests to complete before exiting. -set +e -# Use RTN to return a non-zero value if the test fails. -RTN=0 -ROOT=$(pwd) -# Find all requirements.txt in the samples directory (may break on whitespace). -for file in samples/**/requirements.txt; do - cd "$ROOT" - # Navigate to the project folder. - file=$(dirname "$file") - cd "$file" - - echo "------------------------------------------------------------" - echo "- testing $file" - echo "------------------------------------------------------------" - - # Use nox to execute the tests for the project. - python3.6 -m nox -s "$RUN_TESTS_SESSION" - EXIT=$? - - # If this is a periodic build, send the test log to the FlakyBot. - # See https://github.com/googleapis/repo-automation-bots/tree/master/packages/flakybot. - if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then - chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot - $KOKORO_GFILE_DIR/linux_amd64/flakybot + echo "The current head is: " + echo $(git rev-parse --verify HEAD) + echo "--- IMPORTANT IMPORTANT IMPORTANT ---" + # move back the test runner implementation if there's no file. + if [ ! -f .kokoro/test-samples-impl.sh ]; then + cp "${TMPDIR}/test-samples-impl.sh" .kokoro/test-samples-impl.sh fi +fi - if [[ $EXIT -ne 0 ]]; then - RTN=1 - echo -e "\n Testing failed: Nox returned a non-zero exit code. \n" - else - echo -e "\n Testing completed.\n" - fi - -done -cd "$ROOT" - -# Workaround for Kokoro permissions issue: delete secrets -rm testing/{test-env.sh,client-secrets.json,service-account.json} - -exit "$RTN" +exec .kokoro/test-samples-impl.sh diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a9024b15..32302e48 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,6 +12,6 @@ repos: hooks: - id: black - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + rev: 3.9.0 hooks: - id: flake8 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cb91480..3e874fd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-cloud-error-reporting/#history +### [1.1.2](https://www.github.com/googleapis/python-error-reporting/compare/v1.1.1...v1.1.2) (2021-04-05) + + +### Dependencies + +* upgrade sphinx ([#99](https://www.github.com/googleapis/python-error-reporting/issues/99)) ([a118123](https://www.github.com/googleapis/python-error-reporting/commit/a118123cbfe8b5dd2a7ba260631b248c351cb116)) + ### [1.1.1](https://www.github.com/googleapis/python-error-reporting/compare/v1.1.0...v1.1.1) (2021-02-25) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index c7763dad..e3dee436 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -70,9 +70,14 @@ We use `nox `__ to instrument our tests. - To test your changes, run unit tests with ``nox``:: $ nox -s unit-2.7 - $ nox -s unit-3.7 + $ nox -s unit-3.8 $ ... +- Args to pytest can be passed through the nox command separated by a `--`. For + example, to run a single test:: + + $ nox -s unit-3.8 -- -k + .. note:: The unit tests and system tests are described in the @@ -93,8 +98,12 @@ On Debian/Ubuntu:: ************ Coding Style ************ +- We use the automatic code formatter ``black``. You can run it using + the nox session ``blacken``. This will eliminate many lint errors. Run via:: + + $ nox -s blacken -- PEP8 compliance, with exceptions defined in the linter configuration. +- PEP8 compliance is required, with exceptions defined in the linter configuration. If you have ``nox`` installed, you can test that you have not introduced any non-compliant code via:: @@ -133,13 +142,18 @@ Running System Tests - To run system tests, you can execute:: - $ nox -s system-3.7 + # Run all system tests + $ nox -s system-3.8 $ nox -s system-2.7 + # Run a single system test + $ nox -s system-3.8 -- -k + + .. note:: System tests are only configured to run under Python 2.7 and - Python 3.7. For expediency, we do not run them in older versions + Python 3.8. For expediency, we do not run them in older versions of Python 3. This alone will not run the tests. You'll need to change some local diff --git a/docs/errorreporting_v1beta1/error_group_service.rst b/docs/errorreporting_v1beta1/error_group_service.rst new file mode 100644 index 00000000..dd213525 --- /dev/null +++ b/docs/errorreporting_v1beta1/error_group_service.rst @@ -0,0 +1,6 @@ +ErrorGroupService +----------------------------------- + +.. automodule:: google.cloud.errorreporting_v1beta1.services.error_group_service + :members: + :inherited-members: diff --git a/docs/errorreporting_v1beta1/error_stats_service.rst b/docs/errorreporting_v1beta1/error_stats_service.rst new file mode 100644 index 00000000..30d29e69 --- /dev/null +++ b/docs/errorreporting_v1beta1/error_stats_service.rst @@ -0,0 +1,11 @@ +ErrorStatsService +----------------------------------- + +.. automodule:: google.cloud.errorreporting_v1beta1.services.error_stats_service + :members: + :inherited-members: + + +.. automodule:: google.cloud.errorreporting_v1beta1.services.error_stats_service.pagers + :members: + :inherited-members: diff --git a/docs/errorreporting_v1beta1/report_errors_service.rst b/docs/errorreporting_v1beta1/report_errors_service.rst new file mode 100644 index 00000000..ccddb8b0 --- /dev/null +++ b/docs/errorreporting_v1beta1/report_errors_service.rst @@ -0,0 +1,6 @@ +ReportErrorsService +------------------------------------- + +.. automodule:: google.cloud.errorreporting_v1beta1.services.report_errors_service + :members: + :inherited-members: diff --git a/docs/errorreporting_v1beta1/services.rst b/docs/errorreporting_v1beta1/services.rst index a5ec3b92..e888027f 100644 --- a/docs/errorreporting_v1beta1/services.rst +++ b/docs/errorreporting_v1beta1/services.rst @@ -1,12 +1,8 @@ Services for Google Cloud Errorreporting v1beta1 API ==================================================== +.. toctree:: + :maxdepth: 2 -.. automodule:: google.cloud.errorreporting_v1beta1.services.error_group_service - :members: - :inherited-members: -.. automodule:: google.cloud.errorreporting_v1beta1.services.error_stats_service - :members: - :inherited-members: -.. automodule:: google.cloud.errorreporting_v1beta1.services.report_errors_service - :members: - :inherited-members: + error_group_service + error_stats_service + report_errors_service diff --git a/docs/errorreporting_v1beta1/types.rst b/docs/errorreporting_v1beta1/types.rst index 08851dbe..179256c7 100644 --- a/docs/errorreporting_v1beta1/types.rst +++ b/docs/errorreporting_v1beta1/types.rst @@ -3,4 +3,5 @@ Types for Google Cloud Errorreporting v1beta1 API .. automodule:: google.cloud.errorreporting_v1beta1.types :members: + :undoc-members: :show-inheritance: diff --git a/google/cloud/errorreporting_v1beta1/__init__.py b/google/cloud/errorreporting_v1beta1/__init__.py index 8db40bd7..56f82962 100644 --- a/google/cloud/errorreporting_v1beta1/__init__.py +++ b/google/cloud/errorreporting_v1beta1/__init__.py @@ -22,6 +22,7 @@ from .types.common import ErrorEvent from .types.common import ErrorGroup from .types.common import HttpRequestContext +from .types.common import ResolutionStatus from .types.common import ServiceContext from .types.common import SourceLocation from .types.common import TrackingIssue @@ -64,6 +65,7 @@ "ReportErrorEventResponse", "ReportErrorsServiceClient", "ReportedErrorEvent", + "ResolutionStatus", "ServiceContext", "ServiceContextFilter", "SourceLocation", diff --git a/google/cloud/errorreporting_v1beta1/proto/common.proto b/google/cloud/errorreporting_v1beta1/proto/common.proto index 7a1d2003..e9bb321e 100644 --- a/google/cloud/errorreporting_v1beta1/proto/common.proto +++ b/google/cloud/errorreporting_v1beta1/proto/common.proto @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. +// 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. @@ -16,9 +16,9 @@ syntax = "proto3"; package google.devtools.clouderrorreporting.v1beta1; -import "google/api/annotations.proto"; import "google/api/resource.proto"; import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; option cc_enable_arenas = true; option csharp_namespace = "Google.Cloud.ErrorReporting.V1Beta1"; @@ -37,7 +37,7 @@ message ErrorGroup { }; // The group resource name. - // Example: projects/my-project-123/groups/my-groupid + // Example: projects/my-project-123/groups/CNSgkpnppqKCUw string name = 1; // Group IDs are unique for a given project. If the same kind of error @@ -46,6 +46,10 @@ message ErrorGroup { // Associated tracking issues. repeated TrackingIssue tracking_issues = 3; + + // Error group's resolution status. + // An unspecified resolution status will be interpreted as OPEN + ResolutionStatus resolution_status = 5; } // Information related to tracking the progress on resolving the error. @@ -169,3 +173,24 @@ message SourceLocation { // For example, `my.package.MyClass.method` in case of Java. string function_name = 4; } + +// Resolution status of an error group. +enum ResolutionStatus { + // Status is unknown. When left unspecified in requests, it is treated like + // OPEN. + RESOLUTION_STATUS_UNSPECIFIED = 0; + + // The error group is not being addressed. This is the default for + // new groups. It is also used for errors re-occurring after marked RESOLVED. + OPEN = 1; + + // Error Group manually acknowledged, it can have an issue link attached. + ACKNOWLEDGED = 2; + + // Error Group manually resolved, more events for this group are not expected + // to occur. + RESOLVED = 3; + + // The error group is muted and excluded by default on group stats requests. + MUTED = 4; +} diff --git a/google/cloud/errorreporting_v1beta1/proto/error_group_service.proto b/google/cloud/errorreporting_v1beta1/proto/error_group_service.proto index 18182729..0104b62d 100644 --- a/google/cloud/errorreporting_v1beta1/proto/error_group_service.proto +++ b/google/cloud/errorreporting_v1beta1/proto/error_group_service.proto @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. +// 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. @@ -11,7 +11,6 @@ // 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"; @@ -58,7 +57,7 @@ service ErrorGroupService { // A request to return an individual group. message GetGroupRequest { - // The group resource name. Written as + // Required. The group resource name. Written as // `projects/{projectID}/groups/{group_name}`. Call // [`groupStats.list`](https://cloud.google.com/error-reporting/reference/rest/v1beta1/projects.groupStats/list) // to return a list of groups belonging to this project. diff --git a/google/cloud/errorreporting_v1beta1/proto/error_stats_service.proto b/google/cloud/errorreporting_v1beta1/proto/error_stats_service.proto index 0773f488..6c62edd9 100644 --- a/google/cloud/errorreporting_v1beta1/proto/error_stats_service.proto +++ b/google/cloud/errorreporting_v1beta1/proto/error_stats_service.proto @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. +// 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. @@ -11,7 +11,6 @@ // 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"; @@ -68,11 +67,11 @@ service ErrorStatsService { // Specifies a set of `ErrorGroupStats` to return. message ListGroupStatsRequest { // Required. The resource name of the Google Cloud Platform project. Written - // as projects/ plus the - // Google Cloud - // Platform project ID. + // as `projects/{projectID}` or `projects/{projectNumber}`, where `{projectID}` + // and `{projectNumber}` can be found in the + // [Google Cloud Console](https://support.google.com/cloud/answer/6158840). // - // Example: projects/my-project-123. + // Examples: `projects/my-project-123`, `projects/5551234`. string project_name = 1 [ (google.api.field_behavior) = REQUIRED, (google.api.resource_reference) = { @@ -258,9 +257,10 @@ enum ErrorGroupOrder { // Specifies a set of error events to return. message ListEventsRequest { // Required. The resource name of the Google Cloud Platform project. Written - // as `projects/` plus the + // as `projects/{projectID}`, where `{projectID}` is the // [Google Cloud Platform project // ID](https://support.google.com/cloud/answer/6158840). + // // Example: `projects/my-project-123`. string project_name = 1 [ (google.api.field_behavior) = REQUIRED, @@ -357,9 +357,10 @@ message ServiceContextFilter { // Deletes all events in the project. message DeleteEventsRequest { // Required. The resource name of the Google Cloud Platform project. Written - // as `projects/` plus the + // as `projects/{projectID}`, where `{projectID}` is the // [Google Cloud Platform project // ID](https://support.google.com/cloud/answer/6158840). + // // Example: `projects/my-project-123`. string project_name = 1 [ (google.api.field_behavior) = REQUIRED, diff --git a/google/cloud/errorreporting_v1beta1/proto/report_errors_service.proto b/google/cloud/errorreporting_v1beta1/proto/report_errors_service.proto index f46f546d..cd1e5100 100644 --- a/google/cloud/errorreporting_v1beta1/proto/report_errors_service.proto +++ b/google/cloud/errorreporting_v1beta1/proto/report_errors_service.proto @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. +// 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. @@ -11,7 +11,6 @@ // 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"; @@ -38,7 +37,7 @@ service ReportErrorsService { option (google.api.default_host) = "clouderrorreporting.googleapis.com"; option (google.api.oauth_scopes) = "https://www.googleapis.com/auth/cloud-platform"; - // Report an individual error event. + // Report an individual error event and record the event to a log. // // This endpoint accepts **either** an OAuth token, // **or** an [API key](https://support.google.com/cloud/answer/6158862) @@ -46,7 +45,15 @@ service ReportErrorsService { // a `key` parameter. For example: // // `POST - // https://clouderrorreporting.googleapis.com/v1beta1/projects/example-project/events:report?key=123ABC456` + // https://clouderrorreporting.googleapis.com/v1beta1/{projectName}/events:report?key=123ABC456` + // + // **Note:** [Error Reporting](/error-reporting) is a global service built + // on Cloud Logging and doesn't analyze logs stored + // in regional log buckets or logs routed to other Google Cloud projects. + // + // For more information, see + // [Using Error Reporting with regionalized + // logs](/error-reporting/docs/regionalization). rpc ReportErrorEvent(ReportErrorEventRequest) returns (ReportErrorEventResponse) { option (google.api.http) = { post: "/v1beta1/{project_name=projects/*}/events:report" @@ -59,10 +66,11 @@ service ReportErrorsService { // A request for reporting an individual error event. message ReportErrorEventRequest { // Required. The resource name of the Google Cloud Platform project. Written - // as `projects/` plus the + // as `projects/{projectId}`, where `{projectId}` is the // [Google Cloud Platform project - // ID](https://support.google.com/cloud/answer/6158840). Example: - // `projects/my-project-123`. + // ID](https://support.google.com/cloud/answer/6158840). + // + // Example: // `projects/my-project-123`. string project_name = 1 [ (google.api.field_behavior) = REQUIRED, (google.api.resource_reference) = { diff --git a/google/cloud/errorreporting_v1beta1/services/error_group_service/async_client.py b/google/cloud/errorreporting_v1beta1/services/error_group_service/async_client.py index 26c03996..e2af4bb2 100644 --- a/google/cloud/errorreporting_v1beta1/services/error_group_service/async_client.py +++ b/google/cloud/errorreporting_v1beta1/services/error_group_service/async_client.py @@ -78,7 +78,36 @@ class ErrorGroupServiceAsyncClient: ErrorGroupServiceClient.parse_common_location_path ) - from_service_account_file = ErrorGroupServiceClient.from_service_account_file + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + ErrorGroupServiceAsyncClient: The constructed client. + """ + return ErrorGroupServiceClient.from_service_account_info.__func__(ErrorGroupServiceAsyncClient, 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: + ErrorGroupServiceAsyncClient: The constructed client. + """ + return ErrorGroupServiceClient.from_service_account_file.__func__(ErrorGroupServiceAsyncClient, filename, *args, **kwargs) # type: ignore + from_service_account_json = from_service_account_file @property @@ -154,16 +183,17 @@ async def get_group( r"""Get the specified group. Args: - request (:class:`~.error_group_service.GetGroupRequest`): + request (:class:`google.cloud.errorreporting_v1beta1.types.GetGroupRequest`): The request object. A request to return an individual group. group_name (:class:`str`): - The group resource name. Written as + Required. The group resource name. Written as ``projects/{projectID}/groups/{group_name}``. Call ```groupStats.list`` `__ to return a list of groups belonging to this project. Example: ``projects/my-project-123/groups/my-group`` + This corresponds to the ``group_name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -175,7 +205,7 @@ async def get_group( sent along with the request as metadata. Returns: - ~.common.ErrorGroup: + google.cloud.errorreporting_v1beta1.types.ErrorGroup: Description of a group of similar error events. @@ -233,12 +263,13 @@ async def update_group( Fails if the group does not exist. Args: - request (:class:`~.error_group_service.UpdateGroupRequest`): + request (:class:`google.cloud.errorreporting_v1beta1.types.UpdateGroupRequest`): The request object. A request to replace the existing data for the given group. - group (:class:`~.common.ErrorGroup`): + group (:class:`google.cloud.errorreporting_v1beta1.types.ErrorGroup`): Required. The group which replaces the resource on the server. + This corresponds to the ``group`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -250,7 +281,7 @@ async def update_group( sent along with the request as metadata. Returns: - ~.common.ErrorGroup: + google.cloud.errorreporting_v1beta1.types.ErrorGroup: Description of a group of similar error events. diff --git a/google/cloud/errorreporting_v1beta1/services/error_group_service/client.py b/google/cloud/errorreporting_v1beta1/services/error_group_service/client.py index d1656df2..db374a93 100644 --- a/google/cloud/errorreporting_v1beta1/services/error_group_service/client.py +++ b/google/cloud/errorreporting_v1beta1/services/error_group_service/client.py @@ -112,6 +112,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: + ErrorGroupServiceClient: 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 +140,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. + ErrorGroupServiceClient: The constructed client. """ credentials = service_account.Credentials.from_service_account_file(filename) kwargs["credentials"] = credentials @@ -227,10 +243,10 @@ def __init__( credentials identify the application to the service; if none are specified, the client will attempt to ascertain the credentials from the environment. - transport (Union[str, ~.ErrorGroupServiceTransport]): The + transport (Union[str, ErrorGroupServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (client_options_lib.ClientOptions): Custom options for the + client_options (google.api_core.client_options.ClientOptions): Custom options for the client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT @@ -266,21 +282,17 @@ def __init__( util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) ) - ssl_credentials = None + client_cert_source_func = None is_mtls = False if use_client_cert: if client_options.client_cert_source: - import grpc # type: ignore - - cert, key = client_options.client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) is_mtls = True + client_cert_source_func = client_options.client_cert_source else: - creds = SslCredentials() - is_mtls = creds.is_mtls - ssl_credentials = creds.ssl_credentials if is_mtls else None + is_mtls = mtls.has_default_client_cert_source() + client_cert_source_func = ( + mtls.default_client_cert_source() if is_mtls else None + ) # Figure out which api endpoint to use. if client_options.api_endpoint is not None: @@ -323,7 +335,7 @@ def __init__( credentials_file=client_options.credentials_file, host=api_endpoint, scopes=client_options.scopes, - ssl_channel_credentials=ssl_credentials, + client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, ) @@ -340,16 +352,17 @@ def get_group( r"""Get the specified group. Args: - request (:class:`~.error_group_service.GetGroupRequest`): + request (google.cloud.errorreporting_v1beta1.types.GetGroupRequest): The request object. A request to return an individual group. - group_name (:class:`str`): - The group resource name. Written as + group_name (str): + Required. The group resource name. Written as ``projects/{projectID}/groups/{group_name}``. Call ```groupStats.list`` `__ to return a list of groups belonging to this project. Example: ``projects/my-project-123/groups/my-group`` + This corresponds to the ``group_name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -361,7 +374,7 @@ def get_group( sent along with the request as metadata. Returns: - ~.common.ErrorGroup: + google.cloud.errorreporting_v1beta1.types.ErrorGroup: Description of a group of similar error events. @@ -420,12 +433,13 @@ def update_group( Fails if the group does not exist. Args: - request (:class:`~.error_group_service.UpdateGroupRequest`): + request (google.cloud.errorreporting_v1beta1.types.UpdateGroupRequest): The request object. A request to replace the existing data for the given group. - group (:class:`~.common.ErrorGroup`): + group (google.cloud.errorreporting_v1beta1.types.ErrorGroup): Required. The group which replaces the resource on the server. + This corresponds to the ``group`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -437,7 +451,7 @@ def update_group( sent along with the request as metadata. Returns: - ~.common.ErrorGroup: + google.cloud.errorreporting_v1beta1.types.ErrorGroup: Description of a group of similar error events. diff --git a/google/cloud/errorreporting_v1beta1/services/error_group_service/transports/base.py b/google/cloud/errorreporting_v1beta1/services/error_group_service/transports/base.py index 794cf769..45260605 100644 --- a/google/cloud/errorreporting_v1beta1/services/error_group_service/transports/base.py +++ b/google/cloud/errorreporting_v1beta1/services/error_group_service/transports/base.py @@ -70,10 +70,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. @@ -81,6 +81,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: @@ -90,20 +93,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 = { diff --git a/google/cloud/errorreporting_v1beta1/services/error_group_service/transports/grpc.py b/google/cloud/errorreporting_v1beta1/services/error_group_service/transports/grpc.py index 102cdd39..8514d3d7 100644 --- a/google/cloud/errorreporting_v1beta1/services/error_group_service/transports/grpc.py +++ b/google/cloud/errorreporting_v1beta1/services/error_group_service/transports/grpc.py @@ -58,6 +58,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -88,6 +89,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -102,72 +107,60 @@ def __init__( google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` and ``credentials_file`` are passed. """ + self._grpc_channel = None self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) if channel: - # Sanity check: Ensure that channel and credentials are not both - # provided. + # Ignore credentials if a channel was passed. credentials = False - # If a channel was explicitly provided, set it. self._grpc_channel = channel self._ssl_channel_credentials = None - elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( - api_mtls_endpoint - if ":" in api_mtls_endpoint - else api_mtls_endpoint + ":443" - ) + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) - - # Create SSL credentials with client_cert_source or application - # default SSL credentials. - if client_cert_source: - cert, key = client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) else: - ssl_credentials = SslCredentials().ssl_credentials - - # create a new channel. The provided one is ignored. - self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, - credentials_file=credentials_file, - ssl_credentials=ssl_credentials, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - options=[ - ("grpc.max_send_message_length", -1), - ("grpc.max_receive_message_length", -1), - ], - ) - self._ssl_channel_credentials = ssl_credentials - else: - host = host if ":" in host else host + ":443" + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) + # The base transport sets the host, credentials and scopes + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + client_info=client_info, + ) - # create a new channel. The provided one is ignored. + if not self._grpc_channel: self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, + self._host, + credentials=self._credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, - scopes=scopes or self.AUTH_SCOPES, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, options=[ ("grpc.max_send_message_length", -1), @@ -175,17 +168,8 @@ def __init__( ], ) - self._stubs = {} # type: Dict[str, Callable] - - # Run the base constructor. - super().__init__( - host=host, - credentials=credentials, - credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - client_info=client_info, - ) + # Wrap messages. This must be done after self._grpc_channel exists + self._prep_wrapped_messages(client_info) @classmethod def create_channel( @@ -199,7 +183,7 @@ def create_channel( ) -> grpc.Channel: """Create and return a gRPC channel object. Args: - address (Optional[str]): The host for the channel to use. + host (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If diff --git a/google/cloud/errorreporting_v1beta1/services/error_group_service/transports/grpc_asyncio.py b/google/cloud/errorreporting_v1beta1/services/error_group_service/transports/grpc_asyncio.py index f6ed3855..d09a346b 100644 --- a/google/cloud/errorreporting_v1beta1/services/error_group_service/transports/grpc_asyncio.py +++ b/google/cloud/errorreporting_v1beta1/services/error_group_service/transports/grpc_asyncio.py @@ -62,7 +62,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 @@ -102,6 +102,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -133,12 +134,16 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. - client_info (google.api_core.gapic_v1.client_info.ClientInfo): - The client info used to send a user-agent string along with - API requests. If ``None``, then default info will be used. - Generally, you only need to set this if you're developing + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing your own client library. Raises: @@ -147,72 +152,60 @@ def __init__( google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` and ``credentials_file`` are passed. """ + self._grpc_channel = None self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) if channel: - # Sanity check: Ensure that channel and credentials are not both - # provided. + # Ignore credentials if a channel was passed. credentials = False - # If a channel was explicitly provided, set it. self._grpc_channel = channel self._ssl_channel_credentials = None - elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( - api_mtls_endpoint - if ":" in api_mtls_endpoint - else api_mtls_endpoint + ":443" - ) + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) - - # Create SSL credentials with client_cert_source or application - # default SSL credentials. - if client_cert_source: - cert, key = client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) else: - ssl_credentials = SslCredentials().ssl_credentials - - # create a new channel. The provided one is ignored. - self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, - credentials_file=credentials_file, - ssl_credentials=ssl_credentials, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - options=[ - ("grpc.max_send_message_length", -1), - ("grpc.max_receive_message_length", -1), - ], - ) - self._ssl_channel_credentials = ssl_credentials - else: - host = host if ":" in host else host + ":443" + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) + # The base transport sets the host, credentials and scopes + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + client_info=client_info, + ) - # create a new channel. The provided one is ignored. + if not self._grpc_channel: self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, + self._host, + credentials=self._credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, - scopes=scopes or self.AUTH_SCOPES, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, options=[ ("grpc.max_send_message_length", -1), @@ -220,17 +213,8 @@ def __init__( ], ) - # Run the base constructor. - super().__init__( - host=host, - credentials=credentials, - credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - client_info=client_info, - ) - - self._stubs = {} + # Wrap messages. This must be done after self._grpc_channel exists + self._prep_wrapped_messages(client_info) @property def grpc_channel(self) -> aio.Channel: diff --git a/google/cloud/errorreporting_v1beta1/services/error_stats_service/async_client.py b/google/cloud/errorreporting_v1beta1/services/error_stats_service/async_client.py index 20e91256..ca116141 100644 --- a/google/cloud/errorreporting_v1beta1/services/error_stats_service/async_client.py +++ b/google/cloud/errorreporting_v1beta1/services/error_stats_service/async_client.py @@ -81,7 +81,36 @@ class ErrorStatsServiceAsyncClient: ErrorStatsServiceClient.parse_common_location_path ) - from_service_account_file = ErrorStatsServiceClient.from_service_account_file + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + ErrorStatsServiceAsyncClient: The constructed client. + """ + return ErrorStatsServiceClient.from_service_account_info.__func__(ErrorStatsServiceAsyncClient, 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: + ErrorStatsServiceAsyncClient: The constructed client. + """ + return ErrorStatsServiceClient.from_service_account_file.__func__(ErrorStatsServiceAsyncClient, filename, *args, **kwargs) # type: ignore + from_service_account_json = from_service_account_file @property @@ -158,22 +187,23 @@ async def list_group_stats( r"""Lists the specified groups. Args: - request (:class:`~.error_stats_service.ListGroupStatsRequest`): + request (:class:`google.cloud.errorreporting_v1beta1.types.ListGroupStatsRequest`): The request object. Specifies a set of `ErrorGroupStats` to return. project_name (:class:`str`): - Required. The resource name of the - Google Cloud Platform project. Written - as projects/ plus the Google - Cloud Platform project ID. - - Example: projects/my- - project-123. + Required. The resource name of the Google Cloud Platform + project. Written as ``projects/{projectID}`` or + ``projects/{projectNumber}``, where ``{projectID}`` and + ``{projectNumber}`` can be found in the `Google Cloud + Console `__. + + Examples: ``projects/my-project-123``, + ``projects/5551234``. + This corresponds to the ``project_name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - time_range (:class:`~.error_stats_service.QueryTimeRange`): + time_range (:class:`google.cloud.errorreporting_v1beta1.types.QueryTimeRange`): Optional. List data for the given time range. If not set, a default time range is used. The field time_range_begin in the response will specify the @@ -182,6 +212,7 @@ async def list_group_stats( unless the request contains an explicit group_id list. If a group_id list is given, also ErrorGroupStats with zero occurrences are returned. + This corresponds to the ``time_range`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -193,7 +224,7 @@ async def list_group_stats( sent along with the request as metadata. Returns: - ~.pagers.ListGroupStatsAsyncPager: + google.cloud.errorreporting_v1beta1.services.error_stats_service.pagers.ListGroupStatsAsyncPager: Contains a set of requested error group stats. Iterating over this object will yield @@ -262,21 +293,24 @@ async def list_events( r"""Lists the specified events. Args: - request (:class:`~.error_stats_service.ListEventsRequest`): + request (:class:`google.cloud.errorreporting_v1beta1.types.ListEventsRequest`): The request object. Specifies a set of error events to return. project_name (:class:`str`): Required. The resource name of the Google Cloud Platform - project. Written as ``projects/`` plus the `Google Cloud - Platform project + project. Written as ``projects/{projectID}``, where + ``{projectID}`` is the `Google Cloud Platform project ID `__. + Example: ``projects/my-project-123``. + This corresponds to the ``project_name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. group_id (:class:`str`): Required. The group for which events shall be returned. + This corresponds to the ``group_id`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -288,7 +322,7 @@ async def list_events( sent along with the request as metadata. Returns: - ~.pagers.ListEventsAsyncPager: + google.cloud.errorreporting_v1beta1.services.error_stats_service.pagers.ListEventsAsyncPager: Contains a set of requested error events. Iterating over this object will yield @@ -356,14 +390,16 @@ async def delete_events( r"""Deletes all error events of a given project. Args: - request (:class:`~.error_stats_service.DeleteEventsRequest`): + request (:class:`google.cloud.errorreporting_v1beta1.types.DeleteEventsRequest`): The request object. Deletes all events in the project. project_name (:class:`str`): Required. The resource name of the Google Cloud Platform - project. Written as ``projects/`` plus the `Google Cloud - Platform project + project. Written as ``projects/{projectID}``, where + ``{projectID}`` is the `Google Cloud Platform project ID `__. + Example: ``projects/my-project-123``. + This corresponds to the ``project_name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -375,7 +411,7 @@ async def delete_events( sent along with the request as metadata. Returns: - ~.error_stats_service.DeleteEventsResponse: + google.cloud.errorreporting_v1beta1.types.DeleteEventsResponse: Response message for deleting error events. diff --git a/google/cloud/errorreporting_v1beta1/services/error_stats_service/client.py b/google/cloud/errorreporting_v1beta1/services/error_stats_service/client.py index f04fbfef..7178412c 100644 --- a/google/cloud/errorreporting_v1beta1/services/error_stats_service/client.py +++ b/google/cloud/errorreporting_v1beta1/services/error_stats_service/client.py @@ -115,6 +115,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: + ErrorStatsServiceClient: 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 +143,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. + ErrorStatsServiceClient: The constructed client. """ credentials = service_account.Credentials.from_service_account_file(filename) kwargs["credentials"] = credentials @@ -230,10 +246,10 @@ def __init__( credentials identify the application to the service; if none are specified, the client will attempt to ascertain the credentials from the environment. - transport (Union[str, ~.ErrorStatsServiceTransport]): The + transport (Union[str, ErrorStatsServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (client_options_lib.ClientOptions): Custom options for the + client_options (google.api_core.client_options.ClientOptions): Custom options for the client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT @@ -269,21 +285,17 @@ def __init__( util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) ) - ssl_credentials = None + client_cert_source_func = None is_mtls = False if use_client_cert: if client_options.client_cert_source: - import grpc # type: ignore - - cert, key = client_options.client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) is_mtls = True + client_cert_source_func = client_options.client_cert_source else: - creds = SslCredentials() - is_mtls = creds.is_mtls - ssl_credentials = creds.ssl_credentials if is_mtls else None + is_mtls = mtls.has_default_client_cert_source() + client_cert_source_func = ( + mtls.default_client_cert_source() if is_mtls else None + ) # Figure out which api endpoint to use. if client_options.api_endpoint is not None: @@ -326,7 +338,7 @@ def __init__( credentials_file=client_options.credentials_file, host=api_endpoint, scopes=client_options.scopes, - ssl_channel_credentials=ssl_credentials, + client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, ) @@ -344,22 +356,23 @@ def list_group_stats( r"""Lists the specified groups. Args: - request (:class:`~.error_stats_service.ListGroupStatsRequest`): + request (google.cloud.errorreporting_v1beta1.types.ListGroupStatsRequest): The request object. Specifies a set of `ErrorGroupStats` to return. - project_name (:class:`str`): - Required. The resource name of the - Google Cloud Platform project. Written - as projects/ plus the Google - Cloud Platform project ID. - - Example: projects/my- - project-123. + project_name (str): + Required. The resource name of the Google Cloud Platform + project. Written as ``projects/{projectID}`` or + ``projects/{projectNumber}``, where ``{projectID}`` and + ``{projectNumber}`` can be found in the `Google Cloud + Console `__. + + Examples: ``projects/my-project-123``, + ``projects/5551234``. + This corresponds to the ``project_name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - time_range (:class:`~.error_stats_service.QueryTimeRange`): + time_range (google.cloud.errorreporting_v1beta1.types.QueryTimeRange): Optional. List data for the given time range. If not set, a default time range is used. The field time_range_begin in the response will specify the @@ -368,6 +381,7 @@ def list_group_stats( unless the request contains an explicit group_id list. If a group_id list is given, also ErrorGroupStats with zero occurrences are returned. + This corresponds to the ``time_range`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -379,7 +393,7 @@ def list_group_stats( sent along with the request as metadata. Returns: - ~.pagers.ListGroupStatsPager: + google.cloud.errorreporting_v1beta1.services.error_stats_service.pagers.ListGroupStatsPager: Contains a set of requested error group stats. Iterating over this object will yield @@ -449,21 +463,24 @@ def list_events( r"""Lists the specified events. Args: - request (:class:`~.error_stats_service.ListEventsRequest`): + request (google.cloud.errorreporting_v1beta1.types.ListEventsRequest): The request object. Specifies a set of error events to return. - project_name (:class:`str`): + project_name (str): Required. The resource name of the Google Cloud Platform - project. Written as ``projects/`` plus the `Google Cloud - Platform project + project. Written as ``projects/{projectID}``, where + ``{projectID}`` is the `Google Cloud Platform project ID `__. + Example: ``projects/my-project-123``. + This corresponds to the ``project_name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - group_id (:class:`str`): + group_id (str): Required. The group for which events shall be returned. + This corresponds to the ``group_id`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -475,7 +492,7 @@ def list_events( sent along with the request as metadata. Returns: - ~.pagers.ListEventsPager: + google.cloud.errorreporting_v1beta1.services.error_stats_service.pagers.ListEventsPager: Contains a set of requested error events. Iterating over this object will yield @@ -544,14 +561,16 @@ def delete_events( r"""Deletes all error events of a given project. Args: - request (:class:`~.error_stats_service.DeleteEventsRequest`): + request (google.cloud.errorreporting_v1beta1.types.DeleteEventsRequest): The request object. Deletes all events in the project. - project_name (:class:`str`): + project_name (str): Required. The resource name of the Google Cloud Platform - project. Written as ``projects/`` plus the `Google Cloud - Platform project + project. Written as ``projects/{projectID}``, where + ``{projectID}`` is the `Google Cloud Platform project ID `__. + Example: ``projects/my-project-123``. + This corresponds to the ``project_name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -563,7 +582,7 @@ def delete_events( sent along with the request as metadata. Returns: - ~.error_stats_service.DeleteEventsResponse: + google.cloud.errorreporting_v1beta1.types.DeleteEventsResponse: Response message for deleting error events. diff --git a/google/cloud/errorreporting_v1beta1/services/error_stats_service/pagers.py b/google/cloud/errorreporting_v1beta1/services/error_stats_service/pagers.py index 02a4faa4..a5d6c02f 100644 --- a/google/cloud/errorreporting_v1beta1/services/error_stats_service/pagers.py +++ b/google/cloud/errorreporting_v1beta1/services/error_stats_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.errorreporting_v1beta1.types import common from google.cloud.errorreporting_v1beta1.types import error_stats_service @@ -25,7 +34,7 @@ class ListGroupStatsPager: """A pager for iterating through ``list_group_stats`` requests. This class thinly wraps an initial - :class:`~.error_stats_service.ListGroupStatsResponse` object, and + :class:`google.cloud.errorreporting_v1beta1.types.ListGroupStatsResponse` object, and provides an ``__iter__`` method to iterate through its ``error_group_stats`` field. @@ -34,7 +43,7 @@ class ListGroupStatsPager: through the ``error_group_stats`` field on the corresponding responses. - All the usual :class:`~.error_stats_service.ListGroupStatsResponse` + All the usual :class:`google.cloud.errorreporting_v1beta1.types.ListGroupStatsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -52,9 +61,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.error_stats_service.ListGroupStatsRequest`): + request (google.cloud.errorreporting_v1beta1.types.ListGroupStatsRequest): The initial request object. - response (:class:`~.error_stats_service.ListGroupStatsResponse`): + response (google.cloud.errorreporting_v1beta1.types.ListGroupStatsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. @@ -87,7 +96,7 @@ class ListGroupStatsAsyncPager: """A pager for iterating through ``list_group_stats`` requests. This class thinly wraps an initial - :class:`~.error_stats_service.ListGroupStatsResponse` object, and + :class:`google.cloud.errorreporting_v1beta1.types.ListGroupStatsResponse` object, and provides an ``__aiter__`` method to iterate through its ``error_group_stats`` field. @@ -96,7 +105,7 @@ class ListGroupStatsAsyncPager: through the ``error_group_stats`` field on the corresponding responses. - All the usual :class:`~.error_stats_service.ListGroupStatsResponse` + All the usual :class:`google.cloud.errorreporting_v1beta1.types.ListGroupStatsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -114,9 +123,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.error_stats_service.ListGroupStatsRequest`): + request (google.cloud.errorreporting_v1beta1.types.ListGroupStatsRequest): The initial request object. - response (:class:`~.error_stats_service.ListGroupStatsResponse`): + response (google.cloud.errorreporting_v1beta1.types.ListGroupStatsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. @@ -153,7 +162,7 @@ class ListEventsPager: """A pager for iterating through ``list_events`` requests. This class thinly wraps an initial - :class:`~.error_stats_service.ListEventsResponse` object, and + :class:`google.cloud.errorreporting_v1beta1.types.ListEventsResponse` object, and provides an ``__iter__`` method to iterate through its ``error_events`` field. @@ -162,7 +171,7 @@ class ListEventsPager: through the ``error_events`` field on the corresponding responses. - All the usual :class:`~.error_stats_service.ListEventsResponse` + All the usual :class:`google.cloud.errorreporting_v1beta1.types.ListEventsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -180,9 +189,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.error_stats_service.ListEventsRequest`): + request (google.cloud.errorreporting_v1beta1.types.ListEventsRequest): The initial request object. - response (:class:`~.error_stats_service.ListEventsResponse`): + response (google.cloud.errorreporting_v1beta1.types.ListEventsResponse): The initial response object. metadata (Sequence[Tuple[str, str]]): Strings which should be sent along with the request as metadata. @@ -215,7 +224,7 @@ class ListEventsAsyncPager: """A pager for iterating through ``list_events`` requests. This class thinly wraps an initial - :class:`~.error_stats_service.ListEventsResponse` object, and + :class:`google.cloud.errorreporting_v1beta1.types.ListEventsResponse` object, and provides an ``__aiter__`` method to iterate through its ``error_events`` field. @@ -224,7 +233,7 @@ class ListEventsAsyncPager: through the ``error_events`` field on the corresponding responses. - All the usual :class:`~.error_stats_service.ListEventsResponse` + All the usual :class:`google.cloud.errorreporting_v1beta1.types.ListEventsResponse` attributes are available on the pager. If multiple requests are made, only the most recent response is retained, and thus used for attribute lookup. """ @@ -242,9 +251,9 @@ def __init__( Args: method (Callable): The method that was originally called, and which instantiated this pager. - request (:class:`~.error_stats_service.ListEventsRequest`): + request (google.cloud.errorreporting_v1beta1.types.ListEventsRequest): The initial request object. - response (:class:`~.error_stats_service.ListEventsResponse`): + response (google.cloud.errorreporting_v1beta1.types.ListEventsResponse): 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/errorreporting_v1beta1/services/error_stats_service/transports/base.py b/google/cloud/errorreporting_v1beta1/services/error_stats_service/transports/base.py index 9ee79c34..18470b15 100644 --- a/google/cloud/errorreporting_v1beta1/services/error_stats_service/transports/base.py +++ b/google/cloud/errorreporting_v1beta1/services/error_stats_service/transports/base.py @@ -69,10 +69,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. @@ -80,6 +80,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: @@ -89,20 +92,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 = { diff --git a/google/cloud/errorreporting_v1beta1/services/error_stats_service/transports/grpc.py b/google/cloud/errorreporting_v1beta1/services/error_stats_service/transports/grpc.py index f9595d23..1e033169 100644 --- a/google/cloud/errorreporting_v1beta1/services/error_stats_service/transports/grpc.py +++ b/google/cloud/errorreporting_v1beta1/services/error_stats_service/transports/grpc.py @@ -58,6 +58,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -88,6 +89,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -102,72 +107,60 @@ def __init__( google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` and ``credentials_file`` are passed. """ + self._grpc_channel = None self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) if channel: - # Sanity check: Ensure that channel and credentials are not both - # provided. + # Ignore credentials if a channel was passed. credentials = False - # If a channel was explicitly provided, set it. self._grpc_channel = channel self._ssl_channel_credentials = None - elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( - api_mtls_endpoint - if ":" in api_mtls_endpoint - else api_mtls_endpoint + ":443" - ) + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) - - # Create SSL credentials with client_cert_source or application - # default SSL credentials. - if client_cert_source: - cert, key = client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) else: - ssl_credentials = SslCredentials().ssl_credentials - - # create a new channel. The provided one is ignored. - self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, - credentials_file=credentials_file, - ssl_credentials=ssl_credentials, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - options=[ - ("grpc.max_send_message_length", -1), - ("grpc.max_receive_message_length", -1), - ], - ) - self._ssl_channel_credentials = ssl_credentials - else: - host = host if ":" in host else host + ":443" + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) + # The base transport sets the host, credentials and scopes + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + client_info=client_info, + ) - # create a new channel. The provided one is ignored. + if not self._grpc_channel: self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, + self._host, + credentials=self._credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, - scopes=scopes or self.AUTH_SCOPES, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, options=[ ("grpc.max_send_message_length", -1), @@ -175,17 +168,8 @@ def __init__( ], ) - self._stubs = {} # type: Dict[str, Callable] - - # Run the base constructor. - super().__init__( - host=host, - credentials=credentials, - credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - client_info=client_info, - ) + # Wrap messages. This must be done after self._grpc_channel exists + self._prep_wrapped_messages(client_info) @classmethod def create_channel( @@ -199,7 +183,7 @@ def create_channel( ) -> grpc.Channel: """Create and return a gRPC channel object. Args: - address (Optional[str]): The host for the channel to use. + host (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If diff --git a/google/cloud/errorreporting_v1beta1/services/error_stats_service/transports/grpc_asyncio.py b/google/cloud/errorreporting_v1beta1/services/error_stats_service/transports/grpc_asyncio.py index 4c687c11..402abb49 100644 --- a/google/cloud/errorreporting_v1beta1/services/error_stats_service/transports/grpc_asyncio.py +++ b/google/cloud/errorreporting_v1beta1/services/error_stats_service/transports/grpc_asyncio.py @@ -62,7 +62,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 @@ -102,6 +102,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -133,12 +134,16 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. - client_info (google.api_core.gapic_v1.client_info.ClientInfo): - The client info used to send a user-agent string along with - API requests. If ``None``, then default info will be used. - Generally, you only need to set this if you're developing + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing your own client library. Raises: @@ -147,72 +152,60 @@ def __init__( google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` and ``credentials_file`` are passed. """ + self._grpc_channel = None self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) if channel: - # Sanity check: Ensure that channel and credentials are not both - # provided. + # Ignore credentials if a channel was passed. credentials = False - # If a channel was explicitly provided, set it. self._grpc_channel = channel self._ssl_channel_credentials = None - elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( - api_mtls_endpoint - if ":" in api_mtls_endpoint - else api_mtls_endpoint + ":443" - ) + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) - - # Create SSL credentials with client_cert_source or application - # default SSL credentials. - if client_cert_source: - cert, key = client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) else: - ssl_credentials = SslCredentials().ssl_credentials - - # create a new channel. The provided one is ignored. - self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, - credentials_file=credentials_file, - ssl_credentials=ssl_credentials, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - options=[ - ("grpc.max_send_message_length", -1), - ("grpc.max_receive_message_length", -1), - ], - ) - self._ssl_channel_credentials = ssl_credentials - else: - host = host if ":" in host else host + ":443" + if client_cert_source_for_mtls and not ssl_channel_credentials: + cert, key = client_cert_source_for_mtls() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) + # The base transport sets the host, credentials and scopes + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + client_info=client_info, + ) - # create a new channel. The provided one is ignored. + if not self._grpc_channel: self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, + self._host, + credentials=self._credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, - scopes=scopes or self.AUTH_SCOPES, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, options=[ ("grpc.max_send_message_length", -1), @@ -220,17 +213,8 @@ def __init__( ], ) - # Run the base constructor. - super().__init__( - host=host, - credentials=credentials, - credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - client_info=client_info, - ) - - self._stubs = {} + # Wrap messages. This must be done after self._grpc_channel exists + self._prep_wrapped_messages(client_info) @property def grpc_channel(self) -> aio.Channel: diff --git a/google/cloud/errorreporting_v1beta1/services/report_errors_service/async_client.py b/google/cloud/errorreporting_v1beta1/services/report_errors_service/async_client.py index 3b7c6fcf..61f223b6 100644 --- a/google/cloud/errorreporting_v1beta1/services/report_errors_service/async_client.py +++ b/google/cloud/errorreporting_v1beta1/services/report_errors_service/async_client.py @@ -72,7 +72,36 @@ class ReportErrorsServiceAsyncClient: ReportErrorsServiceClient.parse_common_location_path ) - from_service_account_file = ReportErrorsServiceClient.from_service_account_file + @classmethod + def from_service_account_info(cls, info: dict, *args, **kwargs): + """Creates an instance of this client using the provided credentials info. + + Args: + info (dict): The service account private key info. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + ReportErrorsServiceAsyncClient: The constructed client. + """ + return ReportErrorsServiceClient.from_service_account_info.__func__(ReportErrorsServiceAsyncClient, 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: + ReportErrorsServiceAsyncClient: The constructed client. + """ + return ReportErrorsServiceClient.from_service_account_file.__func__(ReportErrorsServiceAsyncClient, filename, *args, **kwargs) # type: ignore + from_service_account_json = from_service_account_file @property @@ -147,31 +176,42 @@ async def report_error_event( timeout: float = None, metadata: Sequence[Tuple[str, str]] = (), ) -> report_errors_service.ReportErrorEventResponse: - r"""Report an individual error event. + r"""Report an individual error event and record the event to a log. This endpoint accepts **either** an OAuth token, **or** an `API key `__ for authentication. To use an API key, append it to the URL as the value of a ``key`` parameter. For example: - ``POST https://clouderrorreporting.googleapis.com/v1beta1/projects/example-project/events:report?key=123ABC456`` + ``POST https://clouderrorreporting.googleapis.com/v1beta1/{projectName}/events:report?key=123ABC456`` + + **Note:** `Error Reporting `__ is a global + service built on Cloud Logging and doesn't analyze logs stored + in regional log buckets or logs routed to other Google Cloud + projects. + + For more information, see `Using Error Reporting with + regionalized logs `__. Args: - request (:class:`~.report_errors_service.ReportErrorEventRequest`): + request (:class:`google.cloud.errorreporting_v1beta1.types.ReportErrorEventRequest`): The request object. A request for reporting an individual error event. project_name (:class:`str`): Required. The resource name of the Google Cloud Platform - project. Written as ``projects/`` plus the `Google Cloud - Platform project + project. Written as ``projects/{projectId}``, where + ``{projectId}`` is the `Google Cloud Platform project ID `__. - Example: ``projects/my-project-123``. + + Example: // ``projects/my-project-123``. + This corresponds to the ``project_name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - event (:class:`~.report_errors_service.ReportedErrorEvent`): + event (:class:`google.cloud.errorreporting_v1beta1.types.ReportedErrorEvent`): Required. The error event to be reported. + This corresponds to the ``event`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -183,7 +223,7 @@ async def report_error_event( sent along with the request as metadata. Returns: - ~.report_errors_service.ReportErrorEventResponse: + google.cloud.errorreporting_v1beta1.types.ReportErrorEventResponse: Response for reporting an individual error event. Data may be added to this message in the future. diff --git a/google/cloud/errorreporting_v1beta1/services/report_errors_service/client.py b/google/cloud/errorreporting_v1beta1/services/report_errors_service/client.py index b10ecca4..840e449f 100644 --- a/google/cloud/errorreporting_v1beta1/services/report_errors_service/client.py +++ b/google/cloud/errorreporting_v1beta1/services/report_errors_service/client.py @@ -111,6 +111,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: + ReportErrorsServiceClient: 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 @@ -123,7 +139,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. + ReportErrorsServiceClient: The constructed client. """ credentials = service_account.Credentials.from_service_account_file(filename) kwargs["credentials"] = credentials @@ -215,10 +231,10 @@ def __init__( credentials identify the application to the service; if none are specified, the client will attempt to ascertain the credentials from the environment. - transport (Union[str, ~.ReportErrorsServiceTransport]): The + transport (Union[str, ReportErrorsServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (client_options_lib.ClientOptions): Custom options for the + client_options (google.api_core.client_options.ClientOptions): Custom options for the client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT @@ -254,21 +270,17 @@ def __init__( util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) ) - ssl_credentials = None + client_cert_source_func = None is_mtls = False if use_client_cert: if client_options.client_cert_source: - import grpc # type: ignore - - cert, key = client_options.client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) is_mtls = True + client_cert_source_func = client_options.client_cert_source else: - creds = SslCredentials() - is_mtls = creds.is_mtls - ssl_credentials = creds.ssl_credentials if is_mtls else None + is_mtls = mtls.has_default_client_cert_source() + client_cert_source_func = ( + mtls.default_client_cert_source() if is_mtls else None + ) # Figure out which api endpoint to use. if client_options.api_endpoint is not None: @@ -311,7 +323,7 @@ def __init__( credentials_file=client_options.credentials_file, host=api_endpoint, scopes=client_options.scopes, - ssl_channel_credentials=ssl_credentials, + client_cert_source_for_mtls=client_cert_source_func, quota_project_id=client_options.quota_project_id, client_info=client_info, ) @@ -326,31 +338,42 @@ def report_error_event( timeout: float = None, metadata: Sequence[Tuple[str, str]] = (), ) -> report_errors_service.ReportErrorEventResponse: - r"""Report an individual error event. + r"""Report an individual error event and record the event to a log. This endpoint accepts **either** an OAuth token, **or** an `API key `__ for authentication. To use an API key, append it to the URL as the value of a ``key`` parameter. For example: - ``POST https://clouderrorreporting.googleapis.com/v1beta1/projects/example-project/events:report?key=123ABC456`` + ``POST https://clouderrorreporting.googleapis.com/v1beta1/{projectName}/events:report?key=123ABC456`` + + **Note:** `Error Reporting `__ is a global + service built on Cloud Logging and doesn't analyze logs stored + in regional log buckets or logs routed to other Google Cloud + projects. + + For more information, see `Using Error Reporting with + regionalized logs `__. Args: - request (:class:`~.report_errors_service.ReportErrorEventRequest`): + request (google.cloud.errorreporting_v1beta1.types.ReportErrorEventRequest): The request object. A request for reporting an individual error event. - project_name (:class:`str`): + project_name (str): Required. The resource name of the Google Cloud Platform - project. Written as ``projects/`` plus the `Google Cloud - Platform project + project. Written as ``projects/{projectId}``, where + ``{projectId}`` is the `Google Cloud Platform project ID `__. - Example: ``projects/my-project-123``. + + Example: // ``projects/my-project-123``. + This corresponds to the ``project_name`` field on the ``request`` instance; if ``request`` is provided, this should not be set. - event (:class:`~.report_errors_service.ReportedErrorEvent`): + event (google.cloud.errorreporting_v1beta1.types.ReportedErrorEvent): Required. The error event to be reported. + This corresponds to the ``event`` field on the ``request`` instance; if ``request`` is provided, this should not be set. @@ -362,7 +385,7 @@ def report_error_event( sent along with the request as metadata. Returns: - ~.report_errors_service.ReportErrorEventResponse: + google.cloud.errorreporting_v1beta1.types.ReportErrorEventResponse: Response for reporting an individual error event. Data may be added to this message in the future. diff --git a/google/cloud/errorreporting_v1beta1/services/report_errors_service/transports/base.py b/google/cloud/errorreporting_v1beta1/services/report_errors_service/transports/base.py index f1a66243..4adbb0d1 100644 --- a/google/cloud/errorreporting_v1beta1/services/report_errors_service/transports/base.py +++ b/google/cloud/errorreporting_v1beta1/services/report_errors_service/transports/base.py @@ -69,10 +69,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. @@ -80,6 +80,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: @@ -89,20 +92,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 = { diff --git a/google/cloud/errorreporting_v1beta1/services/report_errors_service/transports/grpc.py b/google/cloud/errorreporting_v1beta1/services/report_errors_service/transports/grpc.py index f2c84d92..8ae306d9 100644 --- a/google/cloud/errorreporting_v1beta1/services/report_errors_service/transports/grpc.py +++ b/google/cloud/errorreporting_v1beta1/services/report_errors_service/transports/grpc.py @@ -57,6 +57,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id: Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -87,6 +88,10 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -101,72 +106,60 @@ def __init__( google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` and ``credentials_file`` are passed. """ + self._grpc_channel = None self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) if channel: - # Sanity check: Ensure that channel and credentials are not both - # provided. + # Ignore credentials if a channel was passed. credentials = False - # If a channel was explicitly provided, set it. self._grpc_channel = channel self._ssl_channel_credentials = None - elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( - api_mtls_endpoint - if ":" in api_mtls_endpoint - else api_mtls_endpoint + ":443" - ) + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) - - # Create SSL credentials with client_cert_source or application - # default SSL credentials. - if client_cert_source: - cert, key = client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) else: - ssl_credentials = SslCredentials().ssl_credentials + 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 + ) - # create a new channel. The provided one is ignored. - self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, - credentials_file=credentials_file, - ssl_credentials=ssl_credentials, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - options=[ - ("grpc.max_send_message_length", -1), - ("grpc.max_receive_message_length", -1), - ], - ) - self._ssl_channel_credentials = ssl_credentials - else: - host = host if ":" in host else host + ":443" - - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) + # The base transport sets the host, credentials and scopes + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + client_info=client_info, + ) - # create a new channel. The provided one is ignored. + if not self._grpc_channel: self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, + self._host, + credentials=self._credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, - scopes=scopes or self.AUTH_SCOPES, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, options=[ ("grpc.max_send_message_length", -1), @@ -174,17 +167,8 @@ def __init__( ], ) - self._stubs = {} # type: Dict[str, Callable] - - # Run the base constructor. - super().__init__( - host=host, - credentials=credentials, - credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - client_info=client_info, - ) + # Wrap messages. This must be done after self._grpc_channel exists + self._prep_wrapped_messages(client_info) @classmethod def create_channel( @@ -198,7 +182,7 @@ def create_channel( ) -> grpc.Channel: """Create and return a gRPC channel object. Args: - address (Optional[str]): The host for the channel to use. + host (Optional[str]): The host for the channel to use. credentials (Optional[~.Credentials]): The authorization credentials to attach to requests. These credentials identify this application to the service. If @@ -246,14 +230,22 @@ def report_error_event( ]: r"""Return a callable for the report error event method over gRPC. - Report an individual error event. + Report an individual error event and record the event to a log. This endpoint accepts **either** an OAuth token, **or** an `API key `__ for authentication. To use an API key, append it to the URL as the value of a ``key`` parameter. For example: - ``POST https://clouderrorreporting.googleapis.com/v1beta1/projects/example-project/events:report?key=123ABC456`` + ``POST https://clouderrorreporting.googleapis.com/v1beta1/{projectName}/events:report?key=123ABC456`` + + **Note:** `Error Reporting `__ is a global + service built on Cloud Logging and doesn't analyze logs stored + in regional log buckets or logs routed to other Google Cloud + projects. + + For more information, see `Using Error Reporting with + regionalized logs `__. Returns: Callable[[~.ReportErrorEventRequest], diff --git a/google/cloud/errorreporting_v1beta1/services/report_errors_service/transports/grpc_asyncio.py b/google/cloud/errorreporting_v1beta1/services/report_errors_service/transports/grpc_asyncio.py index 39465e57..115e2446 100644 --- a/google/cloud/errorreporting_v1beta1/services/report_errors_service/transports/grpc_asyncio.py +++ b/google/cloud/errorreporting_v1beta1/services/report_errors_service/transports/grpc_asyncio.py @@ -61,7 +61,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 @@ -101,6 +101,7 @@ def __init__( api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, ssl_channel_credentials: grpc.ChannelCredentials = None, + client_cert_source_for_mtls: Callable[[], Tuple[bytes, bytes]] = None, quota_project_id=None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: @@ -132,12 +133,16 @@ def __init__( ``api_mtls_endpoint`` is None. ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials for grpc channel. It is ignored if ``channel`` is provided. + client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): + A callback to provide client certificate bytes and private key bytes, + both in PEM format. It is used to configure mutual TLS channel. It is + ignored if ``channel`` or ``ssl_channel_credentials`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. - client_info (google.api_core.gapic_v1.client_info.ClientInfo): - The client info used to send a user-agent string along with - API requests. If ``None``, then default info will be used. - Generally, you only need to set this if you're developing + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing your own client library. Raises: @@ -146,72 +151,60 @@ def __init__( google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` and ``credentials_file`` are passed. """ + self._grpc_channel = None self._ssl_channel_credentials = ssl_channel_credentials + self._stubs: Dict[str, Callable] = {} + + if api_mtls_endpoint: + warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) + if client_cert_source: + warnings.warn("client_cert_source is deprecated", DeprecationWarning) if channel: - # Sanity check: Ensure that channel and credentials are not both - # provided. + # Ignore credentials if a channel was passed. credentials = False - # If a channel was explicitly provided, set it. self._grpc_channel = channel self._ssl_channel_credentials = None - elif api_mtls_endpoint: - warnings.warn( - "api_mtls_endpoint and client_cert_source are deprecated", - DeprecationWarning, - ) - host = ( - api_mtls_endpoint - if ":" in api_mtls_endpoint - else api_mtls_endpoint + ":443" - ) + else: + if api_mtls_endpoint: + host = api_mtls_endpoint + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + self._ssl_channel_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + self._ssl_channel_credentials = SslCredentials().ssl_credentials - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) - - # Create SSL credentials with client_cert_source or application - # default SSL credentials. - if client_cert_source: - cert, key = client_cert_source() - ssl_credentials = grpc.ssl_channel_credentials( - certificate_chain=cert, private_key=key - ) else: - ssl_credentials = SslCredentials().ssl_credentials + 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 + ) - # create a new channel. The provided one is ignored. - self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, - credentials_file=credentials_file, - ssl_credentials=ssl_credentials, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - options=[ - ("grpc.max_send_message_length", -1), - ("grpc.max_receive_message_length", -1), - ], - ) - self._ssl_channel_credentials = ssl_credentials - else: - host = host if ":" in host else host + ":443" - - if credentials is None: - credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id - ) + # The base transport sets the host, credentials and scopes + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + client_info=client_info, + ) - # create a new channel. The provided one is ignored. + if not self._grpc_channel: self._grpc_channel = type(self).create_channel( - host, - credentials=credentials, + self._host, + credentials=self._credentials, credentials_file=credentials_file, - ssl_credentials=ssl_channel_credentials, - scopes=scopes or self.AUTH_SCOPES, + scopes=self._scopes, + ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, options=[ ("grpc.max_send_message_length", -1), @@ -219,17 +212,8 @@ def __init__( ], ) - # Run the base constructor. - super().__init__( - host=host, - credentials=credentials, - credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES, - quota_project_id=quota_project_id, - client_info=client_info, - ) - - self._stubs = {} + # Wrap messages. This must be done after self._grpc_channel exists + self._prep_wrapped_messages(client_info) @property def grpc_channel(self) -> aio.Channel: @@ -250,14 +234,22 @@ def report_error_event( ]: r"""Return a callable for the report error event method over gRPC. - Report an individual error event. + Report an individual error event and record the event to a log. This endpoint accepts **either** an OAuth token, **or** an `API key `__ for authentication. To use an API key, append it to the URL as the value of a ``key`` parameter. For example: - ``POST https://clouderrorreporting.googleapis.com/v1beta1/projects/example-project/events:report?key=123ABC456`` + ``POST https://clouderrorreporting.googleapis.com/v1beta1/{projectName}/events:report?key=123ABC456`` + + **Note:** `Error Reporting `__ is a global + service built on Cloud Logging and doesn't analyze logs stored + in regional log buckets or logs routed to other Google Cloud + projects. + + For more information, see `Using Error Reporting with + regionalized logs `__. Returns: Callable[[~.ReportErrorEventRequest], diff --git a/google/cloud/errorreporting_v1beta1/types/__init__.py b/google/cloud/errorreporting_v1beta1/types/__init__.py index fc7243be..9cf5a255 100644 --- a/google/cloud/errorreporting_v1beta1/types/__init__.py +++ b/google/cloud/errorreporting_v1beta1/types/__init__.py @@ -16,61 +16,63 @@ # from .common import ( - ErrorGroup, - TrackingIssue, - ErrorEvent, - ServiceContext, ErrorContext, + ErrorEvent, + ErrorGroup, HttpRequestContext, + ServiceContext, SourceLocation, + TrackingIssue, + ResolutionStatus, ) from .error_group_service import ( GetGroupRequest, UpdateGroupRequest, ) from .error_stats_service import ( - ListGroupStatsRequest, - ListGroupStatsResponse, + DeleteEventsRequest, + DeleteEventsResponse, ErrorGroupStats, - TimedCount, ListEventsRequest, ListEventsResponse, + ListGroupStatsRequest, + ListGroupStatsResponse, QueryTimeRange, ServiceContextFilter, - DeleteEventsRequest, - DeleteEventsResponse, - TimedCountAlignment, + TimedCount, ErrorGroupOrder, + TimedCountAlignment, ) from .report_errors_service import ( + ReportedErrorEvent, ReportErrorEventRequest, ReportErrorEventResponse, - ReportedErrorEvent, ) __all__ = ( - "ErrorGroup", - "TrackingIssue", - "ErrorEvent", - "ServiceContext", "ErrorContext", + "ErrorEvent", + "ErrorGroup", "HttpRequestContext", + "ServiceContext", "SourceLocation", + "TrackingIssue", + "ResolutionStatus", "GetGroupRequest", "UpdateGroupRequest", - "ListGroupStatsRequest", - "ListGroupStatsResponse", + "DeleteEventsRequest", + "DeleteEventsResponse", "ErrorGroupStats", - "TimedCount", "ListEventsRequest", "ListEventsResponse", + "ListGroupStatsRequest", + "ListGroupStatsResponse", "QueryTimeRange", "ServiceContextFilter", - "DeleteEventsRequest", - "DeleteEventsResponse", - "TimedCountAlignment", + "TimedCount", "ErrorGroupOrder", + "TimedCountAlignment", + "ReportedErrorEvent", "ReportErrorEventRequest", "ReportErrorEventResponse", - "ReportedErrorEvent", ) diff --git a/google/cloud/errorreporting_v1beta1/types/common.py b/google/cloud/errorreporting_v1beta1/types/common.py index 779aa453..e0d1a7eb 100644 --- a/google/cloud/errorreporting_v1beta1/types/common.py +++ b/google/cloud/errorreporting_v1beta1/types/common.py @@ -24,6 +24,7 @@ __protobuf__ = proto.module( package="google.devtools.clouderrorreporting.v1beta1", manifest={ + "ResolutionStatus", "ErrorGroup", "TrackingIssue", "ErrorEvent", @@ -35,6 +36,15 @@ ) +class ResolutionStatus(proto.Enum): + r"""Resolution status of an error group.""" + RESOLUTION_STATUS_UNSPECIFIED = 0 + OPEN = 1 + ACKNOWLEDGED = 2 + RESOLVED = 3 + MUTED = 4 + + class ErrorGroup(proto.Message): r"""Description of a group of similar error events. @@ -42,14 +52,18 @@ class ErrorGroup(proto.Message): name (str): The group resource name. Example: projects/my- - project-123/groups/my-groupid + project-123/groups/CNSgkpnppqKCUw group_id (str): Group IDs are unique for a given project. If the same kind of error occurs in different service contexts, it will receive the same group ID. - tracking_issues (Sequence[~.common.TrackingIssue]): + tracking_issues (Sequence[google.cloud.errorreporting_v1beta1.types.TrackingIssue]): Associated tracking issues. + resolution_status (google.cloud.errorreporting_v1beta1.types.ResolutionStatus): + Error group's resolution status. + An unspecified resolution status will be + interpreted as OPEN """ name = proto.Field(proto.STRING, number=1) @@ -60,6 +74,8 @@ class ErrorGroup(proto.Message): proto.MESSAGE, number=3, message="TrackingIssue", ) + resolution_status = proto.Field(proto.ENUM, number=5, enum="ResolutionStatus",) + class TrackingIssue(proto.Message): r"""Information related to tracking the progress on resolving the @@ -80,17 +96,17 @@ class ErrorEvent(proto.Message): system. Attributes: - event_time (~.timestamp.Timestamp): + event_time (google.protobuf.timestamp_pb2.Timestamp): Time when the event occurred as provided in the error report. If the report did not contain a timestamp, the time the error was received by the Error Reporting system is used. - service_context (~.common.ServiceContext): + service_context (google.cloud.errorreporting_v1beta1.types.ServiceContext): The ``ServiceContext`` for which this error was reported. message (str): The stack trace that was reported or logged by the service. - context (~.common.ErrorContext): + context (google.cloud.errorreporting_v1beta1.types.ErrorContext): Data about the context in which the error occurred. """ @@ -149,7 +165,7 @@ class ErrorContext(proto.Message): Engine logs. Attributes: - http_request (~.common.HttpRequestContext): + http_request (google.cloud.errorreporting_v1beta1.types.HttpRequestContext): The HTTP request which was processed when the error was triggered. user (str): @@ -160,7 +176,7 @@ class ErrorContext(proto.Message): this case the Error Reporting system will use other data, such as remote IP address, to distinguish affected users. See ``affected_users_count`` in ``ErrorGroupStats``. - report_location (~.common.SourceLocation): + report_location (google.cloud.errorreporting_v1beta1.types.SourceLocation): The location in the source code where the decision was made to report the error, usually the place where it was logged. For a logged diff --git a/google/cloud/errorreporting_v1beta1/types/error_group_service.py b/google/cloud/errorreporting_v1beta1/types/error_group_service.py index 70aa92a8..e36854b6 100644 --- a/google/cloud/errorreporting_v1beta1/types/error_group_service.py +++ b/google/cloud/errorreporting_v1beta1/types/error_group_service.py @@ -32,7 +32,7 @@ class GetGroupRequest(proto.Message): Attributes: group_name (str): - The group resource name. Written as + Required. The group resource name. Written as ``projects/{projectID}/groups/{group_name}``. Call ```groupStats.list`` `__ to return a list of groups belonging to this project. @@ -47,7 +47,7 @@ class UpdateGroupRequest(proto.Message): r"""A request to replace the existing data for the given group. Attributes: - group (~.common.ErrorGroup): + group (google.cloud.errorreporting_v1beta1.types.ErrorGroup): Required. The group which replaces the resource on the server. """ diff --git a/google/cloud/errorreporting_v1beta1/types/error_stats_service.py b/google/cloud/errorreporting_v1beta1/types/error_stats_service.py index 51a14e89..3973ca04 100644 --- a/google/cloud/errorreporting_v1beta1/types/error_stats_service.py +++ b/google/cloud/errorreporting_v1beta1/types/error_stats_service.py @@ -65,23 +65,23 @@ class ListGroupStatsRequest(proto.Message): Attributes: project_name (str): - Required. The resource name of the Google - Cloud Platform project. Written as - projects/ plus the Google - Cloud Platform project ID. + Required. The resource name of the Google Cloud Platform + project. Written as ``projects/{projectID}`` or + ``projects/{projectNumber}``, where ``{projectID}`` and + ``{projectNumber}`` can be found in the `Google Cloud + Console `__. - Example: projects/my-project-123. + Examples: ``projects/my-project-123``, ``projects/5551234``. group_id (Sequence[str]): Optional. List all ErrorGroupStats with these IDs. - service_filter (~.error_stats_service.ServiceContextFilter): + service_filter (google.cloud.errorreporting_v1beta1.types.ServiceContextFilter): Optional. List only ErrorGroupStats which belong to a service context that matches the filter. Data for all service contexts is returned if this field is not specified. - time_range (~.error_stats_service.QueryTimeRange): + time_range (google.cloud.errorreporting_v1beta1.types.QueryTimeRange): Optional. List data for the given time range. If not set, a default time range is used. The field time_range_begin in the response will specify the beginning of this time range. @@ -89,17 +89,17 @@ class ListGroupStatsRequest(proto.Message): range are returned, unless the request contains an explicit group_id list. If a group_id list is given, also ErrorGroupStats with zero occurrences are returned. - timed_count_duration (~.duration.Duration): + timed_count_duration (google.protobuf.duration_pb2.Duration): Optional. The preferred duration for a single returned ``TimedCount``. If not set, no timed counts are returned. - alignment (~.error_stats_service.TimedCountAlignment): + alignment (google.cloud.errorreporting_v1beta1.types.TimedCountAlignment): Optional. The alignment of the timed counts to be returned. Default is ``ALIGNMENT_EQUAL_AT_END``. - alignment_time (~.timestamp.Timestamp): + alignment_time (google.protobuf.timestamp_pb2.Timestamp): Optional. Time where the timed counts shall be aligned if rounded alignment is chosen. Default is 00:00 UTC. - order (~.error_stats_service.ErrorGroupOrder): + order (google.cloud.errorreporting_v1beta1.types.ErrorGroupOrder): Optional. The sort order in which the results are returned. Default is ``COUNT_DESC``. page_size (int): @@ -140,7 +140,7 @@ class ListGroupStatsResponse(proto.Message): r"""Contains a set of requested error group stats. Attributes: - error_group_stats (Sequence[~.error_stats_service.ErrorGroupStats]): + error_group_stats (Sequence[google.cloud.errorreporting_v1beta1.types.ErrorGroupStats]): The error group stats which match the given request. next_page_token (str): @@ -148,7 +148,7 @@ class ListGroupStatsResponse(proto.Message): Pass this token, along with the same query parameters as the first request, to view the next page of results. - time_range_begin (~.timestamp.Timestamp): + time_range_begin (google.protobuf.timestamp_pb2.Timestamp): The timestamp specifies the start time to which the request was restricted. The start time is set based on the requested time range. It may @@ -177,7 +177,7 @@ class ErrorGroupStats(proto.Message): criteria, such as a given time period and/or service filter. Attributes: - group (~.common.ErrorGroup): + group (google.cloud.errorreporting_v1beta1.types.ErrorGroup): Group data that is independent of the filter criteria. count (int): @@ -195,22 +195,22 @@ class ErrorGroupStats(proto.Message): provided in the error report. If more users are implicitly affected, such as due to a crash of the whole service, this is not reflected here. - timed_counts (Sequence[~.error_stats_service.TimedCount]): + timed_counts (Sequence[google.cloud.errorreporting_v1beta1.types.TimedCount]): Approximate number of occurrences over time. Timed counts returned by ListGroups are guaranteed to be: - Inside the requested time interval - Non-overlapping, and - Ordered by ascending time. - first_seen_time (~.timestamp.Timestamp): + first_seen_time (google.protobuf.timestamp_pb2.Timestamp): Approximate first occurrence that was ever seen for this group and which matches the given filter criteria, ignoring the time_range that was specified in the request. - last_seen_time (~.timestamp.Timestamp): + last_seen_time (google.protobuf.timestamp_pb2.Timestamp): Approximate last occurrence that was ever seen for this group and which matches the given filter criteria, ignoring the time_range that was specified in the request. - affected_services (Sequence[~.common.ServiceContext]): + affected_services (Sequence[google.cloud.errorreporting_v1beta1.types.ServiceContext]): Service contexts with a non-zero error count for the given filter criteria. This list can be truncated if multiple services are affected. Refer to ``num_affected_services`` @@ -218,7 +218,7 @@ class ErrorGroupStats(proto.Message): num_affected_services (int): The total number of services with a non-zero error count for the given filter criteria. - representative (~.common.ErrorEvent): + representative (google.cloud.errorreporting_v1beta1.types.ErrorEvent): An arbitrary event that is chosen as representative for the whole group. The representative event is intended to be used as a @@ -259,10 +259,10 @@ class TimedCount(proto.Message): count (int): Approximate number of occurrences in the given time period. - start_time (~.timestamp.Timestamp): + start_time (google.protobuf.timestamp_pb2.Timestamp): Start of the time period to which ``count`` refers (included). - end_time (~.timestamp.Timestamp): + end_time (google.protobuf.timestamp_pb2.Timestamp): End of the time period to which ``count`` refers (excluded). """ @@ -279,19 +279,20 @@ class ListEventsRequest(proto.Message): Attributes: project_name (str): Required. The resource name of the Google Cloud Platform - project. Written as ``projects/`` plus the `Google Cloud - Platform project + project. Written as ``projects/{projectID}``, where + ``{projectID}`` is the `Google Cloud Platform project ID `__. + Example: ``projects/my-project-123``. group_id (str): Required. The group for which events shall be returned. - service_filter (~.error_stats_service.ServiceContextFilter): + service_filter (google.cloud.errorreporting_v1beta1.types.ServiceContextFilter): Optional. List only ErrorGroups which belong to a service context that matches the filter. Data for all service contexts is returned if this field is not specified. - time_range (~.error_stats_service.QueryTimeRange): + time_range (google.cloud.errorreporting_v1beta1.types.QueryTimeRange): Optional. List only data for the given time range. If not set a default time range is used. The field time_range_begin in the response will specify the beginning of this time @@ -323,7 +324,7 @@ class ListEventsResponse(proto.Message): r"""Contains a set of requested error events. Attributes: - error_events (Sequence[~.common.ErrorEvent]): + error_events (Sequence[google.cloud.errorreporting_v1beta1.types.ErrorEvent]): The error events which match the given request. next_page_token (str): @@ -331,7 +332,7 @@ class ListEventsResponse(proto.Message): Pass this token, along with the same query parameters as the first request, to view the next page of results. - time_range_begin (~.timestamp.Timestamp): + time_range_begin (google.protobuf.timestamp_pb2.Timestamp): The timestamp specifies the start time to which the request was restricted. """ @@ -356,7 +357,7 @@ class QueryTimeRange(proto.Message): durations might be adjusted for lower durations. Attributes: - period (~.error_stats_service.QueryTimeRange.Period): + period (google.cloud.errorreporting_v1beta1.types.QueryTimeRange.Period): Restricts the query to the specified time range. """ @@ -404,9 +405,10 @@ class DeleteEventsRequest(proto.Message): Attributes: project_name (str): Required. The resource name of the Google Cloud Platform - project. Written as ``projects/`` plus the `Google Cloud - Platform project + project. Written as ``projects/{projectID}``, where + ``{projectID}`` is the `Google Cloud Platform project ID `__. + Example: ``projects/my-project-123``. """ diff --git a/google/cloud/errorreporting_v1beta1/types/report_errors_service.py b/google/cloud/errorreporting_v1beta1/types/report_errors_service.py index 1a66727b..bdba6ee8 100644 --- a/google/cloud/errorreporting_v1beta1/types/report_errors_service.py +++ b/google/cloud/errorreporting_v1beta1/types/report_errors_service.py @@ -38,11 +38,12 @@ class ReportErrorEventRequest(proto.Message): Attributes: project_name (str): Required. The resource name of the Google Cloud Platform - project. Written as ``projects/`` plus the `Google Cloud - Platform project + project. Written as ``projects/{projectId}``, where + ``{projectId}`` is the `Google Cloud Platform project ID `__. - Example: ``projects/my-project-123``. - event (~.report_errors_service.ReportedErrorEvent): + + Example: // ``projects/my-project-123``. + event (google.cloud.errorreporting_v1beta1.types.ReportedErrorEvent): Required. The error event to be reported. """ @@ -62,12 +63,12 @@ class ReportedErrorEvent(proto.Message): system. Attributes: - event_time (~.timestamp.Timestamp): + event_time (google.protobuf.timestamp_pb2.Timestamp): Optional. Time when the event occurred. If not provided, the time when the event was received by the Error Reporting system will be used. - service_context (~.common.ServiceContext): + service_context (google.cloud.errorreporting_v1beta1.types.ServiceContext): Required. The service context in which this error has occurred. message (str): @@ -96,7 +97,7 @@ class ReportedErrorEvent(proto.Message): ```(string)$exception`` `__. - **Go**: Must be the return value of ```runtime.Stack()`` `__. - context (~.common.ErrorContext): + context (google.cloud.errorreporting_v1beta1.types.ErrorContext): Optional. A description of the context in which the error occurred. """ diff --git a/noxfile.py b/noxfile.py index 544b90d7..3f66499f 100644 --- a/noxfile.py +++ b/noxfile.py @@ -18,6 +18,7 @@ from __future__ import absolute_import import os +import pathlib import shutil import nox @@ -30,6 +31,8 @@ SYSTEM_TEST_PYTHON_VERSIONS = ["3.8"] UNIT_TEST_PYTHON_VERSIONS = ["3.6", "3.7", "3.8", "3.9"] +CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() + # 'docfx' is excluded since it only needs to run in 'docs-presubmit' nox.options.sessions = [ "unit", @@ -41,6 +44,9 @@ "docs", ] +# Error if a python version is missing +nox.options.error_on_missing_interpreters = True + @nox.session(python=DEFAULT_PYTHON_VERSION) def lint(session): @@ -81,18 +87,21 @@ def lint_setup_py(session): def default(session): # Install all test dependencies, then install this package in-place. - session.install("asyncmock", "pytest-asyncio") - session.install( - "mock", "pytest", "pytest-cov", + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" ) + session.install("asyncmock", "pytest-asyncio", "-c", constraints_path) - session.install("-e", ".") + session.install("mock", "pytest", "pytest-cov", "-c", constraints_path) + + session.install("-e", ".", "-c", constraints_path) # Run py.test against the unit tests. session.run( "py.test", "--quiet", + f"--junitxml=unit_{session.python}_sponge_log.xml", "--cov=google/cloud", "--cov=tests/unit", "--cov-append", @@ -113,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") @@ -122,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) @@ -134,18 +149,28 @@ 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", "test_utils") + session.install("mock", "pytest", "google-cloud-testutils", "-c", constraints_path) + session.install("-e", "test_utils", "-c", constraints_path) - session.install("-e", ".") + 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) @@ -156,7 +181,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") @@ -166,7 +191,7 @@ def docs(session): """Build the docs for this library.""" session.install("-e", ".") - session.install("sphinx<3.0.0", "alabaster", "recommonmark") + session.install("sphinx", "alabaster", "recommonmark") shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) session.run( @@ -188,9 +213,7 @@ def docfx(session): """Build the docfx yaml files for this library.""" session.install("-e", ".") - # sphinx-docfx-yaml supports up to sphinx version 1.5.5. - # https://github.com/docascode/sphinx-docfx-yaml/issues/97 - session.install("sphinx==1.5.5", "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/api/requirements.txt b/samples/snippets/api/requirements.txt index 1d08c2fb..47f476da 100644 --- a/samples/snippets/api/requirements.txt +++ b/samples/snippets/api/requirements.txt @@ -1 +1 @@ -google-cloud-error-reporting==1.1.0 +google-cloud-error-reporting==1.1.1 diff --git a/samples/snippets/fluent_on_compute/requirements.txt b/samples/snippets/fluent_on_compute/requirements.txt index d1c2863f..693841f6 100644 --- a/samples/snippets/fluent_on_compute/requirements.txt +++ b/samples/snippets/fluent_on_compute/requirements.txt @@ -1 +1 @@ -fluent-logger==0.9.6 +fluent-logger==0.10.0 diff --git a/setup.py b/setup.py index 1a3a2792..859ef21d 100644 --- a/setup.py +++ b/setup.py @@ -22,17 +22,16 @@ name = "google-cloud-error-reporting" description = "Error Reporting API client library" -version = "1.1.1" +version = "1.1.2" # Should be one of: # 'Development Status :: 3 - Alpha' # 'Development Status :: 4 - Beta' # 'Development Status :: 5 - Production/Stable' release_status = "Development Status :: 4 - Beta" dependencies = [ - "google-cloud-logging>=1.14.0, <2.2", - "google-api-core[grpc] >= 1.22.0, < 2.0.0dev", + "google-cloud-logging>=1.14.0, <2.4", + "google-api-core[grpc] >= 1.22.2, < 2.0.0dev", "proto-plus >= 1.4.0", - "libcst >= 0.2.5", ] extras = {} diff --git a/synth.metadata b/synth.metadata index 330ea88c..bba4a1eb 100644 --- a/synth.metadata +++ b/synth.metadata @@ -4,29 +4,29 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/python-error-reporting.git", - "sha": "b6887650c28d9fce253ed5894bb2d64a4fedae19" + "sha": "86c5c543d2b278c5b93ea26d42ab3e57281dd4f0" } }, { "git": { "name": "googleapis", "remote": "https://github.com/googleapis/googleapis.git", - "sha": "dd372aa22ded7a8ba6f0e03a80e06358a3fa0907", - "internalRef": "347055288" + "sha": "915925089600094e72e4bfa8cf586c170e6b7109", + "internalRef": "366152684" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "33366574ffb9e11737b3547eb6f020ecae0536e8" + "sha": "6d76df2138f8f841e5a5b9ac427f81def520c15f" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "33366574ffb9e11737b3547eb6f020ecae0536e8" + "sha": "6d76df2138f8f841e5a5b9ac427f81def520c15f" } } ], @@ -42,6 +42,7 @@ } ], "generatedFiles": [ + ".coveragerc", ".flake8", ".github/CONTRIBUTING.md", ".github/ISSUE_TEMPLATE/bug_report.md", @@ -73,16 +74,21 @@ ".kokoro/samples/lint/presubmit.cfg", ".kokoro/samples/python3.6/common.cfg", ".kokoro/samples/python3.6/continuous.cfg", + ".kokoro/samples/python3.6/periodic-head.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-head.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-head.cfg", ".kokoro/samples/python3.8/periodic.cfg", ".kokoro/samples/python3.8/presubmit.cfg", + ".kokoro/test-samples-against-head.sh", + ".kokoro/test-samples-impl.sh", ".kokoro/test-samples.sh", ".kokoro/trampoline.sh", ".kokoro/trampoline_v2.sh", @@ -95,6 +101,9 @@ "docs/_static/custom.css", "docs/_templates/layout.html", "docs/conf.py", + "docs/errorreporting_v1beta1/error_group_service.rst", + "docs/errorreporting_v1beta1/error_stats_service.rst", + "docs/errorreporting_v1beta1/report_errors_service.rst", "docs/errorreporting_v1beta1/services.rst", "docs/errorreporting_v1beta1/types.rst", "docs/multiprocessing.rst", diff --git a/synth.py b/synth.py index 46a9fe17..5fb57f34 100644 --- a/synth.py +++ b/synth.py @@ -37,7 +37,8 @@ templated_files = common.py_library( samples=True, # set to True only if there are samples microgenerator=True, - system_test_dependencies=["test_utils"] + system_test_dependencies=["test_utils"], + cov_level=98, ) s.move(templated_files, excludes=[".coveragerc"]) # microgenerator has a good .coveragerc file @@ -46,8 +47,5 @@ # ---------------------------------------------------------------------------- python.py_samples(skip_readmes=True) -# TODO(busunkim): Use latest sphinx after microgenerator transition -s.replace("noxfile.py", """['"]sphinx['"]""", '"sphinx<3.0.0"') - s.shell.run(["nox", "-s", "blacken"], hide_output=False) diff --git a/testing/constraints-3.6.txt b/testing/constraints-3.6.txt index e88377ad..cf04f5fa 100644 --- a/testing/constraints-3.6.txt +++ b/testing/constraints-3.6.txt @@ -6,6 +6,5 @@ # e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev", # Then this file should have foo==1.14.0 google-cloud-logging==1.14.0 -google-api-core==1.22.0 +google-api-core==1.22.2 proto-plus==1.4.0 -libcst==0.2.5 \ No newline at end of file diff --git a/tests/unit/gapic/errorreporting_v1beta1/__init__.py b/tests/unit/gapic/errorreporting_v1beta1/__init__.py index 8b137891..42ffdf2b 100644 --- a/tests/unit/gapic/errorreporting_v1beta1/__init__.py +++ b/tests/unit/gapic/errorreporting_v1beta1/__init__.py @@ -1 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/tests/unit/gapic/errorreporting_v1beta1/test_error_group_service.py b/tests/unit/gapic/errorreporting_v1beta1/test_error_group_service.py index da93c3df..814cefe8 100644 --- a/tests/unit/gapic/errorreporting_v1beta1/test_error_group_service.py +++ b/tests/unit/gapic/errorreporting_v1beta1/test_error_group_service.py @@ -90,7 +90,24 @@ def test__get_default_mtls_endpoint(): @pytest.mark.parametrize( - "client_class", [ErrorGroupServiceClient, ErrorGroupServiceAsyncClient] + "client_class", [ErrorGroupServiceClient, ErrorGroupServiceAsyncClient,] +) +def test_error_group_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 == "clouderrorreporting.googleapis.com:443" + + +@pytest.mark.parametrize( + "client_class", [ErrorGroupServiceClient, ErrorGroupServiceAsyncClient,] ) def test_error_group_service_client_from_service_account_file(client_class): creds = credentials.AnonymousCredentials() @@ -100,16 +117,21 @@ def test_error_group_service_client_from_service_account_file(client_class): factory.return_value = creds client = client_class.from_service_account_file("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) client = client_class.from_service_account_json("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "clouderrorreporting.googleapis.com:443" def test_error_group_service_client_get_transport_class(): transport = ErrorGroupServiceClient.get_transport_class() - assert transport == transports.ErrorGroupServiceGrpcTransport + available_transports = [ + transports.ErrorGroupServiceGrpcTransport, + ] + assert transport in available_transports transport = ErrorGroupServiceClient.get_transport_class("grpc") assert transport == transports.ErrorGroupServiceGrpcTransport @@ -160,7 +182,7 @@ def test_error_group_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -176,7 +198,7 @@ def test_error_group_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -192,7 +214,7 @@ def test_error_group_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -220,7 +242,7 @@ def test_error_group_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id="octopus", client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -281,29 +303,25 @@ def test_error_group_service_client_mtls_env_auto( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - ssl_channel_creds = mock.Mock() - with mock.patch( - "grpc.ssl_channel_credentials", return_value=ssl_channel_creds - ): - patched.return_value = None - client = client_class(client_options=options) + patched.return_value = None + client = client_class(client_options=options) - if use_client_cert_env == "false": - expected_ssl_channel_creds = None - expected_host = client.DEFAULT_ENDPOINT - else: - expected_ssl_channel_creds = ssl_channel_creds - expected_host = client.DEFAULT_MTLS_ENDPOINT + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) # Check the case ADC client cert is provided. Whether client cert is used depends on # GOOGLE_API_USE_CLIENT_CERTIFICATE value. @@ -312,66 +330,53 @@ def test_error_group_service_client_mtls_env_auto( ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, ): with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.ssl_credentials", - new_callable=mock.PropertyMock, - ) as ssl_credentials_mock: - if use_client_cert_env == "false": - is_mtls_mock.return_value = False - ssl_credentials_mock.return_value = None - expected_host = client.DEFAULT_ENDPOINT - expected_ssl_channel_creds = None - else: - is_mtls_mock.return_value = True - ssl_credentials_mock.return_value = mock.Mock() - expected_host = client.DEFAULT_MTLS_ENDPOINT - expected_ssl_channel_creds = ( - ssl_credentials_mock.return_value - ) - - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client.DEFAULT_ENDPOINT + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback - # Check the case client_cert_source and ADC client cert are not provided. - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} - ): - with mock.patch.object(transport_class, "__init__") as patched: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None - ): - with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - is_mtls_mock.return_value = False patched.return_value = None client = client_class() patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_ENDPOINT, + host=expected_host, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + @pytest.mark.parametrize( "client_class,transport_class,transport_name", @@ -397,7 +402,7 @@ def test_error_group_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -427,7 +432,7 @@ def test_error_group_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -446,7 +451,7 @@ def test_error_group_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -467,7 +472,9 @@ def test_get_group( with mock.patch.object(type(client.transport.get_group), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = common.ErrorGroup( - name="name_value", group_id="group_id_value", + name="name_value", + group_id="group_id_value", + resolution_status=common.ResolutionStatus.OPEN, ) response = client.get_group(request) @@ -486,11 +493,29 @@ def test_get_group( assert response.group_id == "group_id_value" + assert response.resolution_status == common.ResolutionStatus.OPEN + def test_get_group_from_dict(): test_get_group(request_type=dict) +def test_get_group_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 = ErrorGroupServiceClient( + 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_group), "__call__") as call: + client.get_group() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == error_group_service.GetGroupRequest() + + @pytest.mark.asyncio async def test_get_group_async( transport: str = "grpc_asyncio", request_type=error_group_service.GetGroupRequest @@ -507,7 +532,11 @@ async def test_get_group_async( with mock.patch.object(type(client.transport.get_group), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( - common.ErrorGroup(name="name_value", group_id="group_id_value",) + common.ErrorGroup( + name="name_value", + group_id="group_id_value", + resolution_status=common.ResolutionStatus.OPEN, + ) ) response = await client.get_group(request) @@ -525,6 +554,8 @@ async def test_get_group_async( assert response.group_id == "group_id_value" + assert response.resolution_status == common.ResolutionStatus.OPEN + @pytest.mark.asyncio async def test_get_group_async_from_dict(): @@ -666,7 +697,9 @@ def test_update_group( with mock.patch.object(type(client.transport.update_group), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = common.ErrorGroup( - name="name_value", group_id="group_id_value", + name="name_value", + group_id="group_id_value", + resolution_status=common.ResolutionStatus.OPEN, ) response = client.update_group(request) @@ -685,11 +718,29 @@ def test_update_group( assert response.group_id == "group_id_value" + assert response.resolution_status == common.ResolutionStatus.OPEN + def test_update_group_from_dict(): test_update_group(request_type=dict) +def test_update_group_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 = ErrorGroupServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.update_group), "__call__") as call: + client.update_group() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == error_group_service.UpdateGroupRequest() + + @pytest.mark.asyncio async def test_update_group_async( transport: str = "grpc_asyncio", request_type=error_group_service.UpdateGroupRequest @@ -706,7 +757,11 @@ async def test_update_group_async( with mock.patch.object(type(client.transport.update_group), "__call__") as call: # Designate an appropriate return value for the call. call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( - common.ErrorGroup(name="name_value", group_id="group_id_value",) + common.ErrorGroup( + name="name_value", + group_id="group_id_value", + resolution_status=common.ResolutionStatus.OPEN, + ) ) response = await client.update_group(request) @@ -724,6 +779,8 @@ async def test_update_group_async( assert response.group_id == "group_id_value" + assert response.resolution_status == common.ResolutionStatus.OPEN + @pytest.mark.asyncio async def test_update_group_async_from_dict(): @@ -1014,6 +1071,53 @@ def test_error_group_service_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class", + [ + transports.ErrorGroupServiceGrpcTransport, + transports.ErrorGroupServiceGrpcAsyncIOTransport, + ], +) +def test_error_group_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",), + 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_error_group_service_host_no_port(): client = ErrorGroupServiceClient( credentials=credentials.AnonymousCredentials(), @@ -1035,7 +1139,7 @@ def test_error_group_service_host_with_port(): def test_error_group_service_grpc_transport_channel(): - channel = grpc.insecure_channel("http://localhost/") + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.ErrorGroupServiceGrpcTransport( @@ -1047,7 +1151,7 @@ def test_error_group_service_grpc_transport_channel(): def test_error_group_service_grpc_asyncio_transport_channel(): - channel = aio.insecure_channel("http://localhost/") + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.ErrorGroupServiceGrpcAsyncIOTransport( @@ -1058,6 +1162,8 @@ def test_error_group_service_grpc_asyncio_transport_channel(): assert transport._ssl_channel_credentials == None +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ @@ -1072,7 +1178,7 @@ def test_error_group_service_transport_channel_mtls_with_client_cert_source( "grpc.ssl_channel_credentials", autospec=True ) as grpc_ssl_channel_cred: with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_ssl_cred = mock.Mock() grpc_ssl_channel_cred.return_value = mock_ssl_cred @@ -1110,6 +1216,8 @@ def test_error_group_service_transport_channel_mtls_with_client_cert_source( 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", [ @@ -1125,7 +1233,7 @@ def test_error_group_service_transport_channel_mtls_with_adc(transport_class): ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_grpc_channel = mock.Mock() grpc_create_channel.return_value = mock_grpc_channel diff --git a/tests/unit/gapic/errorreporting_v1beta1/test_error_stats_service.py b/tests/unit/gapic/errorreporting_v1beta1/test_error_stats_service.py index 2ea69420..1966becb 100644 --- a/tests/unit/gapic/errorreporting_v1beta1/test_error_stats_service.py +++ b/tests/unit/gapic/errorreporting_v1beta1/test_error_stats_service.py @@ -93,7 +93,24 @@ def test__get_default_mtls_endpoint(): @pytest.mark.parametrize( - "client_class", [ErrorStatsServiceClient, ErrorStatsServiceAsyncClient] + "client_class", [ErrorStatsServiceClient, ErrorStatsServiceAsyncClient,] +) +def test_error_stats_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 == "clouderrorreporting.googleapis.com:443" + + +@pytest.mark.parametrize( + "client_class", [ErrorStatsServiceClient, ErrorStatsServiceAsyncClient,] ) def test_error_stats_service_client_from_service_account_file(client_class): creds = credentials.AnonymousCredentials() @@ -103,16 +120,21 @@ def test_error_stats_service_client_from_service_account_file(client_class): factory.return_value = creds client = client_class.from_service_account_file("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) client = client_class.from_service_account_json("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "clouderrorreporting.googleapis.com:443" def test_error_stats_service_client_get_transport_class(): transport = ErrorStatsServiceClient.get_transport_class() - assert transport == transports.ErrorStatsServiceGrpcTransport + available_transports = [ + transports.ErrorStatsServiceGrpcTransport, + ] + assert transport in available_transports transport = ErrorStatsServiceClient.get_transport_class("grpc") assert transport == transports.ErrorStatsServiceGrpcTransport @@ -163,7 +185,7 @@ def test_error_stats_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -179,7 +201,7 @@ def test_error_stats_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -195,7 +217,7 @@ def test_error_stats_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -223,7 +245,7 @@ def test_error_stats_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id="octopus", client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -284,29 +306,25 @@ def test_error_stats_service_client_mtls_env_auto( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - ssl_channel_creds = mock.Mock() - with mock.patch( - "grpc.ssl_channel_credentials", return_value=ssl_channel_creds - ): - patched.return_value = None - client = client_class(client_options=options) + patched.return_value = None + client = client_class(client_options=options) - if use_client_cert_env == "false": - expected_ssl_channel_creds = None - expected_host = client.DEFAULT_ENDPOINT - else: - expected_ssl_channel_creds = ssl_channel_creds - expected_host = client.DEFAULT_MTLS_ENDPOINT + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) # Check the case ADC client cert is provided. Whether client cert is used depends on # GOOGLE_API_USE_CLIENT_CERTIFICATE value. @@ -315,66 +333,53 @@ def test_error_stats_service_client_mtls_env_auto( ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, ): with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.ssl_credentials", - new_callable=mock.PropertyMock, - ) as ssl_credentials_mock: - if use_client_cert_env == "false": - is_mtls_mock.return_value = False - ssl_credentials_mock.return_value = None - expected_host = client.DEFAULT_ENDPOINT - expected_ssl_channel_creds = None - else: - is_mtls_mock.return_value = True - ssl_credentials_mock.return_value = mock.Mock() - expected_host = client.DEFAULT_MTLS_ENDPOINT - expected_ssl_channel_creds = ( - ssl_credentials_mock.return_value - ) - - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client.DEFAULT_ENDPOINT + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback - # Check the case client_cert_source and ADC client cert are not provided. - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} - ): - with mock.patch.object(transport_class, "__init__") as patched: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None - ): - with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - is_mtls_mock.return_value = False patched.return_value = None client = client_class() patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_ENDPOINT, + host=expected_host, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + @pytest.mark.parametrize( "client_class,transport_class,transport_name", @@ -400,7 +405,7 @@ def test_error_stats_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -430,7 +435,7 @@ def test_error_stats_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -449,7 +454,7 @@ def test_error_stats_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -492,6 +497,22 @@ def test_list_group_stats_from_dict(): test_list_group_stats(request_type=dict) +def test_list_group_stats_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 = ErrorStatsServiceClient( + 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_group_stats), "__call__") as call: + client.list_group_stats() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == error_stats_service.ListGroupStatsRequest() + + @pytest.mark.asyncio async def test_list_group_stats_async( transport: str = "grpc_asyncio", @@ -888,6 +909,22 @@ def test_list_events_from_dict(): test_list_events(request_type=dict) +def test_list_events_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 = ErrorStatsServiceClient( + 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_events), "__call__") as call: + client.list_events() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == error_stats_service.ListEventsRequest() + + @pytest.mark.asyncio async def test_list_events_async( transport: str = "grpc_asyncio", request_type=error_stats_service.ListEventsRequest @@ -1247,6 +1284,22 @@ def test_delete_events_from_dict(): test_delete_events(request_type=dict) +def test_delete_events_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 = ErrorStatsServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.delete_events), "__call__") as call: + client.delete_events() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == error_stats_service.DeleteEventsRequest() + + @pytest.mark.asyncio async def test_delete_events_async( transport: str = "grpc_asyncio", @@ -1575,6 +1628,53 @@ def test_error_stats_service_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class", + [ + transports.ErrorStatsServiceGrpcTransport, + transports.ErrorStatsServiceGrpcAsyncIOTransport, + ], +) +def test_error_stats_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",), + 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_error_stats_service_host_no_port(): client = ErrorStatsServiceClient( credentials=credentials.AnonymousCredentials(), @@ -1596,7 +1696,7 @@ def test_error_stats_service_host_with_port(): def test_error_stats_service_grpc_transport_channel(): - channel = grpc.insecure_channel("http://localhost/") + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.ErrorStatsServiceGrpcTransport( @@ -1608,7 +1708,7 @@ def test_error_stats_service_grpc_transport_channel(): def test_error_stats_service_grpc_asyncio_transport_channel(): - channel = aio.insecure_channel("http://localhost/") + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.ErrorStatsServiceGrpcAsyncIOTransport( @@ -1619,6 +1719,8 @@ def test_error_stats_service_grpc_asyncio_transport_channel(): assert transport._ssl_channel_credentials == None +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ @@ -1633,7 +1735,7 @@ def test_error_stats_service_transport_channel_mtls_with_client_cert_source( "grpc.ssl_channel_credentials", autospec=True ) as grpc_ssl_channel_cred: with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_ssl_cred = mock.Mock() grpc_ssl_channel_cred.return_value = mock_ssl_cred @@ -1671,6 +1773,8 @@ def test_error_stats_service_transport_channel_mtls_with_client_cert_source( 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", [ @@ -1686,7 +1790,7 @@ def test_error_stats_service_transport_channel_mtls_with_adc(transport_class): ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_grpc_channel = mock.Mock() grpc_create_channel.return_value = mock_grpc_channel diff --git a/tests/unit/gapic/errorreporting_v1beta1/test_report_errors_service.py b/tests/unit/gapic/errorreporting_v1beta1/test_report_errors_service.py index f84d267f..98a83947 100644 --- a/tests/unit/gapic/errorreporting_v1beta1/test_report_errors_service.py +++ b/tests/unit/gapic/errorreporting_v1beta1/test_report_errors_service.py @@ -93,7 +93,24 @@ def test__get_default_mtls_endpoint(): @pytest.mark.parametrize( - "client_class", [ReportErrorsServiceClient, ReportErrorsServiceAsyncClient] + "client_class", [ReportErrorsServiceClient, ReportErrorsServiceAsyncClient,] +) +def test_report_errors_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 == "clouderrorreporting.googleapis.com:443" + + +@pytest.mark.parametrize( + "client_class", [ReportErrorsServiceClient, ReportErrorsServiceAsyncClient,] ) def test_report_errors_service_client_from_service_account_file(client_class): creds = credentials.AnonymousCredentials() @@ -103,16 +120,21 @@ def test_report_errors_service_client_from_service_account_file(client_class): factory.return_value = creds client = client_class.from_service_account_file("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) client = client_class.from_service_account_json("dummy/file/path.json") assert client.transport._credentials == creds + assert isinstance(client, client_class) assert client.transport._host == "clouderrorreporting.googleapis.com:443" def test_report_errors_service_client_get_transport_class(): transport = ReportErrorsServiceClient.get_transport_class() - assert transport == transports.ReportErrorsServiceGrpcTransport + available_transports = [ + transports.ReportErrorsServiceGrpcTransport, + ] + assert transport in available_transports transport = ReportErrorsServiceClient.get_transport_class("grpc") assert transport == transports.ReportErrorsServiceGrpcTransport @@ -167,7 +189,7 @@ def test_report_errors_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -183,7 +205,7 @@ def test_report_errors_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -199,7 +221,7 @@ def test_report_errors_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -227,7 +249,7 @@ def test_report_errors_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id="octopus", client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -288,29 +310,25 @@ def test_report_errors_service_client_mtls_env_auto( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - ssl_channel_creds = mock.Mock() - with mock.patch( - "grpc.ssl_channel_credentials", return_value=ssl_channel_creds - ): - patched.return_value = None - client = client_class(client_options=options) + patched.return_value = None + client = client_class(client_options=options) - if use_client_cert_env == "false": - expected_ssl_channel_creds = None - expected_host = client.DEFAULT_ENDPOINT - else: - expected_ssl_channel_creds = ssl_channel_creds - expected_host = client.DEFAULT_MTLS_ENDPOINT + if use_client_cert_env == "false": + expected_client_cert_source = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_client_cert_source = client_cert_source_callback + expected_host = client.DEFAULT_MTLS_ENDPOINT - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + client_cert_source_for_mtls=expected_client_cert_source, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) # Check the case ADC client cert is provided. Whether client cert is used depends on # GOOGLE_API_USE_CLIENT_CERTIFICATE value. @@ -319,66 +337,53 @@ def test_report_errors_service_client_mtls_env_auto( ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, ): with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.ssl_credentials", - new_callable=mock.PropertyMock, - ) as ssl_credentials_mock: - if use_client_cert_env == "false": - is_mtls_mock.return_value = False - ssl_credentials_mock.return_value = None - expected_host = client.DEFAULT_ENDPOINT - expected_ssl_channel_creds = None - else: - is_mtls_mock.return_value = True - ssl_credentials_mock.return_value = mock.Mock() - expected_host = client.DEFAULT_MTLS_ENDPOINT - expected_ssl_channel_creds = ( - ssl_credentials_mock.return_value - ) - - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=expected_host, - scopes=None, - ssl_channel_credentials=expected_ssl_channel_creds, - quota_project_id=None, - client_info=transports.base.DEFAULT_CLIENT_INFO, - ) + "google.auth.transport.mtls.default_client_cert_source", + return_value=client_cert_source_callback, + ): + if use_client_cert_env == "false": + expected_host = client.DEFAULT_ENDPOINT + expected_client_cert_source = None + else: + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_client_cert_source = client_cert_source_callback - # Check the case client_cert_source and ADC client cert are not provided. - with mock.patch.dict( - os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} - ): - with mock.patch.object(transport_class, "__init__") as patched: - with mock.patch( - "google.auth.transport.grpc.SslCredentials.__init__", return_value=None - ): - with mock.patch( - "google.auth.transport.grpc.SslCredentials.is_mtls", - new_callable=mock.PropertyMock, - ) as is_mtls_mock: - is_mtls_mock.return_value = False patched.return_value = None client = client_class() patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_ENDPOINT, + host=expected_host, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=expected_client_cert_source, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + @pytest.mark.parametrize( "client_class,transport_class,transport_name", @@ -408,7 +413,7 @@ def test_report_errors_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -442,7 +447,7 @@ def test_report_errors_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -461,7 +466,7 @@ def test_report_errors_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - ssl_channel_credentials=None, + client_cert_source_for_mtls=None, quota_project_id=None, client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -502,6 +507,24 @@ def test_report_error_event_from_dict(): test_report_error_event(request_type=dict) +def test_report_error_event_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 = ReportErrorsServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client.transport.report_error_event), "__call__" + ) as call: + client.report_error_event() + call.assert_called() + _, args, _ = call.mock_calls[0] + + assert args[0] == report_errors_service.ReportErrorEventRequest() + + @pytest.mark.asyncio async def test_report_error_event_async( transport: str = "grpc_asyncio", @@ -860,6 +883,53 @@ def test_report_errors_service_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class", + [ + transports.ReportErrorsServiceGrpcTransport, + transports.ReportErrorsServiceGrpcAsyncIOTransport, + ], +) +def test_report_errors_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",), + 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_report_errors_service_host_no_port(): client = ReportErrorsServiceClient( credentials=credentials.AnonymousCredentials(), @@ -881,7 +951,7 @@ def test_report_errors_service_host_with_port(): def test_report_errors_service_grpc_transport_channel(): - channel = grpc.insecure_channel("http://localhost/") + channel = grpc.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.ReportErrorsServiceGrpcTransport( @@ -893,7 +963,7 @@ def test_report_errors_service_grpc_transport_channel(): def test_report_errors_service_grpc_asyncio_transport_channel(): - channel = aio.insecure_channel("http://localhost/") + channel = aio.secure_channel("http://localhost/", grpc.local_channel_credentials()) # Check that channel is used if provided. transport = transports.ReportErrorsServiceGrpcAsyncIOTransport( @@ -904,6 +974,8 @@ def test_report_errors_service_grpc_asyncio_transport_channel(): assert transport._ssl_channel_credentials == None +# Remove this test when deprecated arguments (api_mtls_endpoint, client_cert_source) are +# removed from grpc/grpc_asyncio transport constructor. @pytest.mark.parametrize( "transport_class", [ @@ -918,7 +990,7 @@ def test_report_errors_service_transport_channel_mtls_with_client_cert_source( "grpc.ssl_channel_credentials", autospec=True ) as grpc_ssl_channel_cred: with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_ssl_cred = mock.Mock() grpc_ssl_channel_cred.return_value = mock_ssl_cred @@ -956,6 +1028,8 @@ def test_report_errors_service_transport_channel_mtls_with_client_cert_source( 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", [ @@ -971,7 +1045,7 @@ def test_report_errors_service_transport_channel_mtls_with_adc(transport_class): ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): with mock.patch.object( - transport_class, "create_channel", autospec=True + transport_class, "create_channel" ) as grpc_create_channel: mock_grpc_channel = mock.Mock() grpc_create_channel.return_value = mock_grpc_channel