diff --git a/.github/snippet-bot.yml b/.github/snippet-bot.yml
new file mode 100644
index 0000000..e69de29
diff --git a/.gitignore b/.gitignore
index b87e1ed..b9daa52 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,6 +46,7 @@ pip-log.txt
# Built documentation
docs/_build
bigquery/docs/generated
+docs.metadata
# Virtual environment
env/
@@ -57,4 +58,4 @@ system_tests/local_test_setup
# Make sure a generated file isn't accidentally committed.
pylintrc
-pylintrc.test
\ No newline at end of file
+pylintrc.test
diff --git a/.kokoro/build.sh b/.kokoro/build.sh
index 7c1e98a..8d81e50 100755
--- a/.kokoro/build.sh
+++ b/.kokoro/build.sh
@@ -36,4 +36,10 @@ python3.6 -m pip uninstall --yes --quiet nox-automation
python3.6 -m pip install --upgrade --quiet nox
python3.6 -m nox --version
-python3.6 -m nox
+# If NOX_SESSION is set, it only runs the specified session,
+# otherwise run all the sessions.
+if [[ -n "${NOX_SESSION:-}" ]]; then
+ python3.6 -m nox -s "${NOX_SESSION:-}"
+else
+ python3.6 -m nox
+fi
diff --git a/.kokoro/docker/docs/Dockerfile b/.kokoro/docker/docs/Dockerfile
new file mode 100644
index 0000000..412b0b5
--- /dev/null
+++ b/.kokoro/docker/docs/Dockerfile
@@ -0,0 +1,98 @@
+# 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.
+
+from ubuntu:20.04
+
+ENV DEBIAN_FRONTEND noninteractive
+
+# Ensure local Python is preferred over distribution Python.
+ENV PATH /usr/local/bin:$PATH
+
+# Install dependencies.
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+ apt-transport-https \
+ build-essential \
+ ca-certificates \
+ curl \
+ dirmngr \
+ git \
+ gpg-agent \
+ graphviz \
+ libbz2-dev \
+ libdb5.3-dev \
+ libexpat1-dev \
+ libffi-dev \
+ liblzma-dev \
+ libreadline-dev \
+ libsnappy-dev \
+ libssl-dev \
+ libsqlite3-dev \
+ portaudio19-dev \
+ redis-server \
+ software-properties-common \
+ ssh \
+ sudo \
+ tcl \
+ tcl-dev \
+ tk \
+ tk-dev \
+ uuid-dev \
+ wget \
+ zlib1g-dev \
+ && add-apt-repository universe \
+ && apt-get update \
+ && apt-get -y install jq \
+ && apt-get clean autoclean \
+ && apt-get autoremove -y \
+ && rm -rf /var/lib/apt/lists/* \
+ && rm -f /var/cache/apt/archives/*.deb
+
+
+COPY fetch_gpg_keys.sh /tmp
+# Install the desired versions of Python.
+RUN set -ex \
+ && export GNUPGHOME="$(mktemp -d)" \
+ && echo "disable-ipv6" >> "${GNUPGHOME}/dirmngr.conf" \
+ && /tmp/fetch_gpg_keys.sh \
+ && for PYTHON_VERSION in 3.7.8 3.8.5; do \
+ wget --no-check-certificate -O python-${PYTHON_VERSION}.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz" \
+ && wget --no-check-certificate -O python-${PYTHON_VERSION}.tar.xz.asc "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc" \
+ && gpg --batch --verify python-${PYTHON_VERSION}.tar.xz.asc python-${PYTHON_VERSION}.tar.xz \
+ && rm -r python-${PYTHON_VERSION}.tar.xz.asc \
+ && mkdir -p /usr/src/python-${PYTHON_VERSION} \
+ && tar -xJC /usr/src/python-${PYTHON_VERSION} --strip-components=1 -f python-${PYTHON_VERSION}.tar.xz \
+ && rm python-${PYTHON_VERSION}.tar.xz \
+ && cd /usr/src/python-${PYTHON_VERSION} \
+ && ./configure \
+ --enable-shared \
+ # This works only on Python 2.7 and throws a warning on every other
+ # version, but seems otherwise harmless.
+ --enable-unicode=ucs4 \
+ --with-system-ffi \
+ --without-ensurepip \
+ && make -j$(nproc) \
+ && make install \
+ && ldconfig \
+ ; done \
+ && rm -rf "${GNUPGHOME}" \
+ && rm -rf /usr/src/python* \
+ && rm -rf ~/.cache/
+
+RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \
+ && python3.7 /tmp/get-pip.py \
+ && python3.8 /tmp/get-pip.py \
+ && rm /tmp/get-pip.py
+
+CMD ["python3.7"]
diff --git a/.kokoro/docker/docs/fetch_gpg_keys.sh b/.kokoro/docker/docs/fetch_gpg_keys.sh
new file mode 100755
index 0000000..d653dd8
--- /dev/null
+++ b/.kokoro/docker/docs/fetch_gpg_keys.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# A script to fetch gpg keys with retry.
+# Avoid jinja parsing the file.
+#
+
+function retry {
+ if [[ "${#}" -le 1 ]]; then
+ echo "Usage: ${0} retry_count commands.."
+ exit 1
+ fi
+ local retries=${1}
+ local command="${@:2}"
+ until [[ "${retries}" -le 0 ]]; do
+ $command && return 0
+ if [[ $? -ne 0 ]]; then
+ echo "command failed, retrying"
+ ((retries--))
+ fi
+ done
+ return 1
+}
+
+# 3.6.9, 3.7.5 (Ned Deily)
+retry 3 gpg --keyserver ha.pool.sks-keyservers.net --recv-keys \
+ 0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D
+
+# 3.8.0 (Łukasz Langa)
+retry 3 gpg --keyserver ha.pool.sks-keyservers.net --recv-keys \
+ E3FF2839C048B25C084DEBE9B26995E310250568
+
+#
diff --git a/.kokoro/docs/common.cfg b/.kokoro/docs/common.cfg
index a3df692..66286a6 100644
--- a/.kokoro/docs/common.cfg
+++ b/.kokoro/docs/common.cfg
@@ -11,12 +11,12 @@ action {
gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
# Use the trampoline script to run in docker.
-build_file: "python-billingbudgets/.kokoro/trampoline.sh"
+build_file: "python-billingbudgets/.kokoro/trampoline_v2.sh"
# Configure the docker image for kokoro-trampoline.
env_vars: {
key: "TRAMPOLINE_IMAGE"
- value: "gcr.io/cloud-devrel-kokoro-resources/python-multi"
+ value: "gcr.io/cloud-devrel-kokoro-resources/python-lib-docs"
}
env_vars: {
key: "TRAMPOLINE_BUILD_FILE"
@@ -28,6 +28,23 @@ env_vars: {
value: "docs-staging"
}
+env_vars: {
+ key: "V2_STAGING_BUCKET"
+ value: "docs-staging-v2"
+}
+
+# It will upload the docker image after successful builds.
+env_vars: {
+ key: "TRAMPOLINE_IMAGE_UPLOAD"
+ value: "true"
+}
+
+# It will always build the docker image.
+env_vars: {
+ key: "TRAMPOLINE_DOCKERFILE"
+ value: ".kokoro/docker/docs/Dockerfile"
+}
+
# Fetch the token needed for reporting release status to GitHub
before_action {
fetch_keystore {
diff --git a/.kokoro/docs/docs-presubmit.cfg b/.kokoro/docs/docs-presubmit.cfg
new file mode 100644
index 0000000..1118107
--- /dev/null
+++ b/.kokoro/docs/docs-presubmit.cfg
@@ -0,0 +1,17 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+env_vars: {
+ key: "STAGING_BUCKET"
+ value: "gcloud-python-test"
+}
+
+env_vars: {
+ key: "V2_STAGING_BUCKET"
+ value: "gcloud-python-test"
+}
+
+# We only upload the image in the main `docs` build.
+env_vars: {
+ key: "TRAMPOLINE_IMAGE_UPLOAD"
+ value: "false"
+}
diff --git a/.kokoro/populate-secrets.sh b/.kokoro/populate-secrets.sh
new file mode 100755
index 0000000..f525142
--- /dev/null
+++ b/.kokoro/populate-secrets.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+# Copyright 2020 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -eo pipefail
+
+function now { date +"%Y-%m-%d %H:%M:%S" | tr -d '\n' ;}
+function msg { println "$*" >&2 ;}
+function println { printf '%s\n' "$(now) $*" ;}
+
+
+# Populates requested secrets set in SECRET_MANAGER_KEYS from service account:
+# kokoro-trampoline@cloud-devrel-kokoro-resources.iam.gserviceaccount.com
+SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager"
+msg "Creating folder on disk for secrets: ${SECRET_LOCATION}"
+mkdir -p ${SECRET_LOCATION}
+for key in $(echo ${SECRET_MANAGER_KEYS} | sed "s/,/ /g")
+do
+ msg "Retrieving secret ${key}"
+ docker run --entrypoint=gcloud \
+ --volume=${KOKORO_GFILE_DIR}:${KOKORO_GFILE_DIR} \
+ gcr.io/google.com/cloudsdktool/cloud-sdk \
+ secrets versions access latest \
+ --project cloud-devrel-kokoro-resources \
+ --secret ${key} > \
+ "${SECRET_LOCATION}/${key}"
+ if [[ $? == 0 ]]; then
+ msg "Secret written to ${SECRET_LOCATION}/${key}"
+ else
+ msg "Error retrieving secret ${key}"
+ fi
+done
diff --git a/.kokoro/publish-docs.sh b/.kokoro/publish-docs.sh
index 5b0c452..8acb14e 100755
--- a/.kokoro/publish-docs.sh
+++ b/.kokoro/publish-docs.sh
@@ -18,26 +18,16 @@ set -eo pipefail
# Disable buffering, so that the logs stream through.
export PYTHONUNBUFFERED=1
-cd github/python-billingbudgets
-
-# Remove old nox
-python3.6 -m pip uninstall --yes --quiet nox-automation
+export PATH="${HOME}/.local/bin:${PATH}"
# Install nox
-python3.6 -m pip install --upgrade --quiet nox
-python3.6 -m nox --version
+python3 -m pip install --user --upgrade --quiet nox
+python3 -m nox --version
# build docs
nox -s docs
-python3 -m pip install gcp-docuploader
-
-# install a json parser
-sudo apt-get update
-sudo apt-get -y install software-properties-common
-sudo add-apt-repository universe
-sudo apt-get update
-sudo apt-get -y install jq
+python3 -m pip install --user gcp-docuploader
# create metadata
python3 -m docuploader create-metadata \
@@ -52,4 +42,23 @@ python3 -m docuploader create-metadata \
cat docs.metadata
# upload docs
-python3 -m docuploader upload docs/_build/html --metadata-file docs.metadata --staging-bucket docs-staging
+python3 -m docuploader upload docs/_build/html --metadata-file docs.metadata --staging-bucket "${STAGING_BUCKET}"
+
+
+# docfx yaml files
+nox -s docfx
+
+# create metadata.
+python3 -m docuploader create-metadata \
+ --name=$(jq --raw-output '.name // empty' .repo-metadata.json) \
+ --version=$(python3 setup.py --version) \
+ --language=$(jq --raw-output '.language // empty' .repo-metadata.json) \
+ --distribution-name=$(python3 setup.py --name) \
+ --product-page=$(jq --raw-output '.product_documentation // empty' .repo-metadata.json) \
+ --github-repository=$(jq --raw-output '.repo // empty' .repo-metadata.json) \
+ --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json)
+
+cat docs.metadata
+
+# upload docs
+python3 -m docuploader upload docs/_build/html/docfx_yaml --metadata-file docs.metadata --destination-prefix docfx --staging-bucket "${V2_STAGING_BUCKET}"
diff --git a/.kokoro/release/common.cfg b/.kokoro/release/common.cfg
index 2641977..68fa8f1 100644
--- a/.kokoro/release/common.cfg
+++ b/.kokoro/release/common.cfg
@@ -23,42 +23,18 @@ env_vars: {
value: "github/python-billingbudgets/.kokoro/release.sh"
}
-# Fetch the token needed for reporting release status to GitHub
-before_action {
- fetch_keystore {
- keystore_resource {
- keystore_config_id: 73713
- keyname: "yoshi-automation-github-key"
- }
- }
-}
-
-# Fetch PyPI password
-before_action {
- fetch_keystore {
- keystore_resource {
- keystore_config_id: 73713
- keyname: "google_cloud_pypi_password"
- }
- }
-}
-
-# Fetch magictoken to use with Magic Github Proxy
-before_action {
- fetch_keystore {
- keystore_resource {
- keystore_config_id: 73713
- keyname: "releasetool-magictoken"
- }
- }
+# Fetch PyPI password
+before_action {
+ fetch_keystore {
+ keystore_resource {
+ keystore_config_id: 73713
+ keyname: "google_cloud_pypi_password"
+ }
+ }
}
-# Fetch api key to use with Magic Github Proxy
-before_action {
- fetch_keystore {
- keystore_resource {
- keystore_config_id: 73713
- keyname: "magic-github-proxy-api-key"
- }
- }
-}
+# Tokens needed to report release status back to GitHub
+env_vars: {
+ key: "SECRET_MANAGER_KEYS"
+ value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem"
+}
\ No newline at end of file
diff --git a/.kokoro/samples/python3.6/common.cfg b/.kokoro/samples/python3.6/common.cfg
index 0ab1335..d4f0e38 100644
--- a/.kokoro/samples/python3.6/common.cfg
+++ b/.kokoro/samples/python3.6/common.cfg
@@ -13,6 +13,12 @@ env_vars: {
value: "py-3.6"
}
+# Declare build specific Cloud project.
+env_vars: {
+ key: "BUILD_SPECIFIC_GCLOUD_PROJECT"
+ value: "python-docs-samples-tests-py36"
+}
+
env_vars: {
key: "TRAMPOLINE_BUILD_FILE"
value: "github/python-billingbudgets/.kokoro/test-samples.sh"
diff --git a/.kokoro/samples/python3.7/common.cfg b/.kokoro/samples/python3.7/common.cfg
index 49edab1..8c11dd0 100644
--- a/.kokoro/samples/python3.7/common.cfg
+++ b/.kokoro/samples/python3.7/common.cfg
@@ -13,6 +13,12 @@ env_vars: {
value: "py-3.7"
}
+# Declare build specific Cloud project.
+env_vars: {
+ key: "BUILD_SPECIFIC_GCLOUD_PROJECT"
+ value: "python-docs-samples-tests-py37"
+}
+
env_vars: {
key: "TRAMPOLINE_BUILD_FILE"
value: "github/python-billingbudgets/.kokoro/test-samples.sh"
diff --git a/.kokoro/samples/python3.8/common.cfg b/.kokoro/samples/python3.8/common.cfg
index b9885b0..371797b 100644
--- a/.kokoro/samples/python3.8/common.cfg
+++ b/.kokoro/samples/python3.8/common.cfg
@@ -13,6 +13,12 @@ env_vars: {
value: "py-3.8"
}
+# Declare build specific Cloud project.
+env_vars: {
+ key: "BUILD_SPECIFIC_GCLOUD_PROJECT"
+ value: "python-docs-samples-tests-py38"
+}
+
env_vars: {
key: "TRAMPOLINE_BUILD_FILE"
value: "github/python-billingbudgets/.kokoro/test-samples.sh"
diff --git a/.kokoro/test-samples.sh b/.kokoro/test-samples.sh
index 6962321..5d432b7 100755
--- a/.kokoro/test-samples.sh
+++ b/.kokoro/test-samples.sh
@@ -28,6 +28,12 @@ if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then
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
@@ -101,4 +107,4 @@ cd "$ROOT"
# Workaround for Kokoro permissions issue: delete secrets
rm testing/{test-env.sh,client-secrets.json,service-account.json}
-exit "$RTN"
\ No newline at end of file
+exit "$RTN"
diff --git a/.kokoro/trampoline.sh b/.kokoro/trampoline.sh
index e8c4251..f39236e 100755
--- a/.kokoro/trampoline.sh
+++ b/.kokoro/trampoline.sh
@@ -15,9 +15,14 @@
set -eo pipefail
-python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" || ret_code=$?
+# Always run the cleanup script, regardless of the success of bouncing into
+# the container.
+function cleanup() {
+ chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh
+ ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh
+ echo "cleanup";
+}
+trap cleanup EXIT
-chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh
-${KOKORO_GFILE_DIR}/trampoline_cleanup.sh || true
-
-exit ${ret_code}
+$(dirname $0)/populate-secrets.sh # Secret Manager secrets.
+python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py"
\ No newline at end of file
diff --git a/.kokoro/trampoline_v2.sh b/.kokoro/trampoline_v2.sh
new file mode 100755
index 0000000..719bcd5
--- /dev/null
+++ b/.kokoro/trampoline_v2.sh
@@ -0,0 +1,487 @@
+#!/usr/bin/env bash
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# trampoline_v2.sh
+#
+# This script does 3 things.
+#
+# 1. Prepare the Docker image for the test
+# 2. Run the Docker with appropriate flags to run the test
+# 3. Upload the newly built Docker image
+#
+# in a way that is somewhat compatible with trampoline_v1.
+#
+# To run this script, first download few files from gcs to /dev/shm.
+# (/dev/shm is passed into the container as KOKORO_GFILE_DIR).
+#
+# gsutil cp gs://cloud-devrel-kokoro-resources/python-docs-samples/secrets_viewer_service_account.json /dev/shm
+# gsutil cp gs://cloud-devrel-kokoro-resources/python-docs-samples/automl_secrets.txt /dev/shm
+#
+# Then run the script.
+# .kokoro/trampoline_v2.sh
+#
+# These environment variables are required:
+# TRAMPOLINE_IMAGE: The docker image to use.
+# TRAMPOLINE_DOCKERFILE: The location of the Dockerfile.
+#
+# You can optionally change these environment variables:
+# TRAMPOLINE_IMAGE_UPLOAD:
+# (true|false): Whether to upload the Docker image after the
+# successful builds.
+# TRAMPOLINE_BUILD_FILE: The script to run in the docker container.
+# TRAMPOLINE_WORKSPACE: The workspace path in the docker container.
+# Defaults to /workspace.
+# Potentially there are some repo specific envvars in .trampolinerc in
+# the project root.
+
+
+set -euo pipefail
+
+TRAMPOLINE_VERSION="2.0.5"
+
+if command -v tput >/dev/null && [[ -n "${TERM:-}" ]]; then
+ readonly IO_COLOR_RED="$(tput setaf 1)"
+ readonly IO_COLOR_GREEN="$(tput setaf 2)"
+ readonly IO_COLOR_YELLOW="$(tput setaf 3)"
+ readonly IO_COLOR_RESET="$(tput sgr0)"
+else
+ readonly IO_COLOR_RED=""
+ readonly IO_COLOR_GREEN=""
+ readonly IO_COLOR_YELLOW=""
+ readonly IO_COLOR_RESET=""
+fi
+
+function function_exists {
+ [ $(LC_ALL=C type -t $1)"" == "function" ]
+}
+
+# Logs a message using the given color. The first argument must be one
+# of the IO_COLOR_* variables defined above, such as
+# "${IO_COLOR_YELLOW}". The remaining arguments will be logged in the
+# given color. The log message will also have an RFC-3339 timestamp
+# prepended (in UTC). You can disable the color output by setting
+# TERM=vt100.
+function log_impl() {
+ local color="$1"
+ shift
+ local timestamp="$(date -u "+%Y-%m-%dT%H:%M:%SZ")"
+ echo "================================================================"
+ echo "${color}${timestamp}:" "$@" "${IO_COLOR_RESET}"
+ echo "================================================================"
+}
+
+# Logs the given message with normal coloring and a timestamp.
+function log() {
+ log_impl "${IO_COLOR_RESET}" "$@"
+}
+
+# Logs the given message in green with a timestamp.
+function log_green() {
+ log_impl "${IO_COLOR_GREEN}" "$@"
+}
+
+# Logs the given message in yellow with a timestamp.
+function log_yellow() {
+ log_impl "${IO_COLOR_YELLOW}" "$@"
+}
+
+# Logs the given message in red with a timestamp.
+function log_red() {
+ log_impl "${IO_COLOR_RED}" "$@"
+}
+
+readonly tmpdir=$(mktemp -d -t ci-XXXXXXXX)
+readonly tmphome="${tmpdir}/h"
+mkdir -p "${tmphome}"
+
+function cleanup() {
+ rm -rf "${tmpdir}"
+}
+trap cleanup EXIT
+
+RUNNING_IN_CI="${RUNNING_IN_CI:-false}"
+
+# The workspace in the container, defaults to /workspace.
+TRAMPOLINE_WORKSPACE="${TRAMPOLINE_WORKSPACE:-/workspace}"
+
+pass_down_envvars=(
+ # TRAMPOLINE_V2 variables.
+ # Tells scripts whether they are running as part of CI or not.
+ "RUNNING_IN_CI"
+ # Indicates which CI system we're in.
+ "TRAMPOLINE_CI"
+ # Indicates the version of the script.
+ "TRAMPOLINE_VERSION"
+)
+
+log_yellow "Building with Trampoline ${TRAMPOLINE_VERSION}"
+
+# Detect which CI systems we're in. If we're in any of the CI systems
+# we support, `RUNNING_IN_CI` will be true and `TRAMPOLINE_CI` will be
+# the name of the CI system. Both envvars will be passing down to the
+# container for telling which CI system we're in.
+if [[ -n "${KOKORO_BUILD_ID:-}" ]]; then
+ # descriptive env var for indicating it's on CI.
+ RUNNING_IN_CI="true"
+ TRAMPOLINE_CI="kokoro"
+ if [[ "${TRAMPOLINE_USE_LEGACY_SERVICE_ACCOUNT:-}" == "true" ]]; then
+ if [[ ! -f "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" ]]; then
+ log_red "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json does not exist. Did you forget to mount cloud-devrel-kokoro-resources/trampoline? Aborting."
+ exit 1
+ fi
+ # This service account will be activated later.
+ TRAMPOLINE_SERVICE_ACCOUNT="${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json"
+ else
+ if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then
+ gcloud auth list
+ fi
+ log_yellow "Configuring Container Registry access"
+ gcloud auth configure-docker --quiet
+ fi
+ pass_down_envvars+=(
+ # KOKORO dynamic variables.
+ "KOKORO_BUILD_NUMBER"
+ "KOKORO_BUILD_ID"
+ "KOKORO_JOB_NAME"
+ "KOKORO_GIT_COMMIT"
+ "KOKORO_GITHUB_COMMIT"
+ "KOKORO_GITHUB_PULL_REQUEST_NUMBER"
+ "KOKORO_GITHUB_PULL_REQUEST_COMMIT"
+ # For Build Cop Bot
+ "KOKORO_GITHUB_COMMIT_URL"
+ "KOKORO_GITHUB_PULL_REQUEST_URL"
+ )
+elif [[ "${TRAVIS:-}" == "true" ]]; then
+ RUNNING_IN_CI="true"
+ TRAMPOLINE_CI="travis"
+ pass_down_envvars+=(
+ "TRAVIS_BRANCH"
+ "TRAVIS_BUILD_ID"
+ "TRAVIS_BUILD_NUMBER"
+ "TRAVIS_BUILD_WEB_URL"
+ "TRAVIS_COMMIT"
+ "TRAVIS_COMMIT_MESSAGE"
+ "TRAVIS_COMMIT_RANGE"
+ "TRAVIS_JOB_NAME"
+ "TRAVIS_JOB_NUMBER"
+ "TRAVIS_JOB_WEB_URL"
+ "TRAVIS_PULL_REQUEST"
+ "TRAVIS_PULL_REQUEST_BRANCH"
+ "TRAVIS_PULL_REQUEST_SHA"
+ "TRAVIS_PULL_REQUEST_SLUG"
+ "TRAVIS_REPO_SLUG"
+ "TRAVIS_SECURE_ENV_VARS"
+ "TRAVIS_TAG"
+ )
+elif [[ -n "${GITHUB_RUN_ID:-}" ]]; then
+ RUNNING_IN_CI="true"
+ TRAMPOLINE_CI="github-workflow"
+ pass_down_envvars+=(
+ "GITHUB_WORKFLOW"
+ "GITHUB_RUN_ID"
+ "GITHUB_RUN_NUMBER"
+ "GITHUB_ACTION"
+ "GITHUB_ACTIONS"
+ "GITHUB_ACTOR"
+ "GITHUB_REPOSITORY"
+ "GITHUB_EVENT_NAME"
+ "GITHUB_EVENT_PATH"
+ "GITHUB_SHA"
+ "GITHUB_REF"
+ "GITHUB_HEAD_REF"
+ "GITHUB_BASE_REF"
+ )
+elif [[ "${CIRCLECI:-}" == "true" ]]; then
+ RUNNING_IN_CI="true"
+ TRAMPOLINE_CI="circleci"
+ pass_down_envvars+=(
+ "CIRCLE_BRANCH"
+ "CIRCLE_BUILD_NUM"
+ "CIRCLE_BUILD_URL"
+ "CIRCLE_COMPARE_URL"
+ "CIRCLE_JOB"
+ "CIRCLE_NODE_INDEX"
+ "CIRCLE_NODE_TOTAL"
+ "CIRCLE_PREVIOUS_BUILD_NUM"
+ "CIRCLE_PROJECT_REPONAME"
+ "CIRCLE_PROJECT_USERNAME"
+ "CIRCLE_REPOSITORY_URL"
+ "CIRCLE_SHA1"
+ "CIRCLE_STAGE"
+ "CIRCLE_USERNAME"
+ "CIRCLE_WORKFLOW_ID"
+ "CIRCLE_WORKFLOW_JOB_ID"
+ "CIRCLE_WORKFLOW_UPSTREAM_JOB_IDS"
+ "CIRCLE_WORKFLOW_WORKSPACE_ID"
+ )
+fi
+
+# Configure the service account for pulling the docker image.
+function repo_root() {
+ local dir="$1"
+ while [[ ! -d "${dir}/.git" ]]; do
+ dir="$(dirname "$dir")"
+ done
+ echo "${dir}"
+}
+
+# Detect the project root. In CI builds, we assume the script is in
+# the git tree and traverse from there, otherwise, traverse from `pwd`
+# to find `.git` directory.
+if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then
+ PROGRAM_PATH="$(realpath "$0")"
+ PROGRAM_DIR="$(dirname "${PROGRAM_PATH}")"
+ PROJECT_ROOT="$(repo_root "${PROGRAM_DIR}")"
+else
+ PROJECT_ROOT="$(repo_root $(pwd))"
+fi
+
+log_yellow "Changing to the project root: ${PROJECT_ROOT}."
+cd "${PROJECT_ROOT}"
+
+# To support relative path for `TRAMPOLINE_SERVICE_ACCOUNT`, we need
+# to use this environment variable in `PROJECT_ROOT`.
+if [[ -n "${TRAMPOLINE_SERVICE_ACCOUNT:-}" ]]; then
+
+ mkdir -p "${tmpdir}/gcloud"
+ gcloud_config_dir="${tmpdir}/gcloud"
+
+ log_yellow "Using isolated gcloud config: ${gcloud_config_dir}."
+ export CLOUDSDK_CONFIG="${gcloud_config_dir}"
+
+ log_yellow "Using ${TRAMPOLINE_SERVICE_ACCOUNT} for authentication."
+ gcloud auth activate-service-account \
+ --key-file "${TRAMPOLINE_SERVICE_ACCOUNT}"
+ log_yellow "Configuring Container Registry access"
+ gcloud auth configure-docker --quiet
+fi
+
+required_envvars=(
+ # The basic trampoline configurations.
+ "TRAMPOLINE_IMAGE"
+ "TRAMPOLINE_BUILD_FILE"
+)
+
+if [[ -f "${PROJECT_ROOT}/.trampolinerc" ]]; then
+ source "${PROJECT_ROOT}/.trampolinerc"
+fi
+
+log_yellow "Checking environment variables."
+for e in "${required_envvars[@]}"
+do
+ if [[ -z "${!e:-}" ]]; then
+ log "Missing ${e} env var. Aborting."
+ exit 1
+ fi
+done
+
+# We want to support legacy style TRAMPOLINE_BUILD_FILE used with V1
+# script: e.g. "github/repo-name/.kokoro/run_tests.sh"
+TRAMPOLINE_BUILD_FILE="${TRAMPOLINE_BUILD_FILE#github/*/}"
+log_yellow "Using TRAMPOLINE_BUILD_FILE: ${TRAMPOLINE_BUILD_FILE}"
+
+# ignore error on docker operations and test execution
+set +e
+
+log_yellow "Preparing Docker image."
+# We only download the docker image in CI builds.
+if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then
+ # Download the docker image specified by `TRAMPOLINE_IMAGE`
+
+ # We may want to add --max-concurrent-downloads flag.
+
+ log_yellow "Start pulling the Docker image: ${TRAMPOLINE_IMAGE}."
+ if docker pull "${TRAMPOLINE_IMAGE}"; then
+ log_green "Finished pulling the Docker image: ${TRAMPOLINE_IMAGE}."
+ has_image="true"
+ else
+ log_red "Failed pulling the Docker image: ${TRAMPOLINE_IMAGE}."
+ has_image="false"
+ fi
+else
+ # For local run, check if we have the image.
+ if docker images "${TRAMPOLINE_IMAGE}:latest" | grep "${TRAMPOLINE_IMAGE}"; then
+ has_image="true"
+ else
+ has_image="false"
+ fi
+fi
+
+
+# The default user for a Docker container has uid 0 (root). To avoid
+# creating root-owned files in the build directory we tell docker to
+# use the current user ID.
+user_uid="$(id -u)"
+user_gid="$(id -g)"
+user_name="$(id -un)"
+
+# To allow docker in docker, we add the user to the docker group in
+# the host os.
+docker_gid=$(cut -d: -f3 < <(getent group docker))
+
+update_cache="false"
+if [[ "${TRAMPOLINE_DOCKERFILE:-none}" != "none" ]]; then
+ # Build the Docker image from the source.
+ context_dir=$(dirname "${TRAMPOLINE_DOCKERFILE}")
+ docker_build_flags=(
+ "-f" "${TRAMPOLINE_DOCKERFILE}"
+ "-t" "${TRAMPOLINE_IMAGE}"
+ "--build-arg" "UID=${user_uid}"
+ "--build-arg" "USERNAME=${user_name}"
+ )
+ if [[ "${has_image}" == "true" ]]; then
+ docker_build_flags+=("--cache-from" "${TRAMPOLINE_IMAGE}")
+ fi
+
+ log_yellow "Start building the docker image."
+ if [[ "${TRAMPOLINE_VERBOSE:-false}" == "true" ]]; then
+ echo "docker build" "${docker_build_flags[@]}" "${context_dir}"
+ fi
+
+ # ON CI systems, we want to suppress docker build logs, only
+ # output the logs when it fails.
+ if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then
+ if docker build "${docker_build_flags[@]}" "${context_dir}" \
+ > "${tmpdir}/docker_build.log" 2>&1; then
+ if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then
+ cat "${tmpdir}/docker_build.log"
+ fi
+
+ log_green "Finished building the docker image."
+ update_cache="true"
+ else
+ log_red "Failed to build the Docker image, aborting."
+ log_yellow "Dumping the build logs:"
+ cat "${tmpdir}/docker_build.log"
+ exit 1
+ fi
+ else
+ if docker build "${docker_build_flags[@]}" "${context_dir}"; then
+ log_green "Finished building the docker image."
+ update_cache="true"
+ else
+ log_red "Failed to build the Docker image, aborting."
+ exit 1
+ fi
+ fi
+else
+ if [[ "${has_image}" != "true" ]]; then
+ log_red "We do not have ${TRAMPOLINE_IMAGE} locally, aborting."
+ exit 1
+ fi
+fi
+
+# We use an array for the flags so they are easier to document.
+docker_flags=(
+ # Remove the container after it exists.
+ "--rm"
+
+ # Use the host network.
+ "--network=host"
+
+ # Run in priviledged mode. We are not using docker for sandboxing or
+ # isolation, just for packaging our dev tools.
+ "--privileged"
+
+ # Run the docker script with the user id. Because the docker image gets to
+ # write in ${PWD} you typically want this to be your user id.
+ # To allow docker in docker, we need to use docker gid on the host.
+ "--user" "${user_uid}:${docker_gid}"
+
+ # Pass down the USER.
+ "--env" "USER=${user_name}"
+
+ # Mount the project directory inside the Docker container.
+ "--volume" "${PROJECT_ROOT}:${TRAMPOLINE_WORKSPACE}"
+ "--workdir" "${TRAMPOLINE_WORKSPACE}"
+ "--env" "PROJECT_ROOT=${TRAMPOLINE_WORKSPACE}"
+
+ # Mount the temporary home directory.
+ "--volume" "${tmphome}:/h"
+ "--env" "HOME=/h"
+
+ # Allow docker in docker.
+ "--volume" "/var/run/docker.sock:/var/run/docker.sock"
+
+ # Mount the /tmp so that docker in docker can mount the files
+ # there correctly.
+ "--volume" "/tmp:/tmp"
+ # Pass down the KOKORO_GFILE_DIR and KOKORO_KEYSTORE_DIR
+ # TODO(tmatsuo): This part is not portable.
+ "--env" "TRAMPOLINE_SECRET_DIR=/secrets"
+ "--volume" "${KOKORO_GFILE_DIR:-/dev/shm}:/secrets/gfile"
+ "--env" "KOKORO_GFILE_DIR=/secrets/gfile"
+ "--volume" "${KOKORO_KEYSTORE_DIR:-/dev/shm}:/secrets/keystore"
+ "--env" "KOKORO_KEYSTORE_DIR=/secrets/keystore"
+)
+
+# Add an option for nicer output if the build gets a tty.
+if [[ -t 0 ]]; then
+ docker_flags+=("-it")
+fi
+
+# Passing down env vars
+for e in "${pass_down_envvars[@]}"
+do
+ if [[ -n "${!e:-}" ]]; then
+ docker_flags+=("--env" "${e}=${!e}")
+ fi
+done
+
+# If arguments are given, all arguments will become the commands run
+# in the container, otherwise run TRAMPOLINE_BUILD_FILE.
+if [[ $# -ge 1 ]]; then
+ log_yellow "Running the given commands '" "${@:1}" "' in the container."
+ readonly commands=("${@:1}")
+ if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then
+ echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}"
+ fi
+ docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}"
+else
+ log_yellow "Running the tests in a Docker container."
+ docker_flags+=("--entrypoint=${TRAMPOLINE_BUILD_FILE}")
+ if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then
+ echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}"
+ fi
+ docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}"
+fi
+
+
+test_retval=$?
+
+if [[ ${test_retval} -eq 0 ]]; then
+ log_green "Build finished with ${test_retval}"
+else
+ log_red "Build finished with ${test_retval}"
+fi
+
+# Only upload it when the test passes.
+if [[ "${update_cache}" == "true" ]] && \
+ [[ $test_retval == 0 ]] && \
+ [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]]; then
+ log_yellow "Uploading the Docker image."
+ if docker push "${TRAMPOLINE_IMAGE}"; then
+ log_green "Finished uploading the Docker image."
+ else
+ log_red "Failed uploading the Docker image."
+ fi
+ # Call trampoline_after_upload_hook if it's defined.
+ if function_exists trampoline_after_upload_hook; then
+ trampoline_after_upload_hook
+ fi
+
+fi
+
+exit "${test_retval}"
diff --git a/.trampolinerc b/.trampolinerc
new file mode 100644
index 0000000..995ee29
--- /dev/null
+++ b/.trampolinerc
@@ -0,0 +1,51 @@
+# 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.
+
+# Template for .trampolinerc
+
+# Add required env vars here.
+required_envvars+=(
+ "STAGING_BUCKET"
+ "V2_STAGING_BUCKET"
+)
+
+# Add env vars which are passed down into the container here.
+pass_down_envvars+=(
+ "STAGING_BUCKET"
+ "V2_STAGING_BUCKET"
+)
+
+# Prevent unintentional override on the default image.
+if [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]] && \
+ [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then
+ echo "Please set TRAMPOLINE_IMAGE if you want to upload the Docker image."
+ exit 1
+fi
+
+# Define the default value if it makes sense.
+if [[ -z "${TRAMPOLINE_IMAGE_UPLOAD:-}" ]]; then
+ TRAMPOLINE_IMAGE_UPLOAD=""
+fi
+
+if [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then
+ TRAMPOLINE_IMAGE=""
+fi
+
+if [[ -z "${TRAMPOLINE_DOCKERFILE:-}" ]]; then
+ TRAMPOLINE_DOCKERFILE=""
+fi
+
+if [[ -z "${TRAMPOLINE_BUILD_FILE:-}" ]]; then
+ TRAMPOLINE_BUILD_FILE=""
+fi
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index b3d1f60..039f436 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,44 +1,95 @@
-# Contributor Code of Conduct
+# Code of Conduct
-As contributors and maintainers of this project,
-and in the interest of fostering an open and welcoming community,
-we pledge to respect all people who contribute through reporting issues,
-posting feature requests, updating documentation,
-submitting pull requests or patches, and other activities.
+## Our Pledge
-We are committed to making participation in this project
-a harassment-free experience for everyone,
-regardless of level of experience, gender, gender identity and expression,
-sexual orientation, disability, personal appearance,
-body size, race, ethnicity, age, religion, or nationality.
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of
+experience, education, socio-economic status, nationality, personal appearance,
+race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
-* The use of sexualized language or imagery
-* Personal attacks
-* Trolling or insulting/derogatory comments
-* Public or private harassment
-* Publishing other's private information,
-such as physical or electronic
-addresses, without explicit permission
-* Other unethical or unprofessional conduct.
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject
-comments, commits, code, wiki edits, issues, and other contributions
-that are not aligned to this Code of Conduct.
-By adopting this Code of Conduct,
-project maintainers commit themselves to fairly and consistently
-applying these principles to every aspect of managing this project.
-Project maintainers who do not follow or enforce the Code of Conduct
-may be permanently removed from the project team.
-
-This code of conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community.
-
-Instances of abusive, harassing, or otherwise unacceptable behavior
-may be reported by opening an issue
-or contacting one or more of the project maintainers.
-
-This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
-available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, or to ban temporarily or permanently any
+contributor for other behaviors that they deem inappropriate, threatening,
+offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+This Code of Conduct also applies outside the project spaces when the Project
+Steward has a reasonable belief that an individual's behavior may have a
+negative impact on the project or its community.
+
+## Conflict Resolution
+
+We do not believe that all conflict is bad; healthy debate and disagreement
+often yield positive results. However, it is never okay to be disrespectful or
+to engage in behavior that violates the project’s code of conduct.
+
+If you see someone violating the code of conduct, you are encouraged to address
+the behavior directly with those involved. Many issues can be resolved quickly
+and easily, and this gives people more control over the outcome of their
+dispute. If you are unable to resolve the matter for any reason, or if the
+behavior is threatening or harassing, report it. We are dedicated to providing
+an environment where participants feel welcome and safe.
+
+
+Reports should be directed to *googleapis-stewards@google.com*, the
+Project Steward(s) for *Google Cloud Client Libraries*. It is the Project Steward’s duty to
+receive and address reported violations of the code of conduct. They will then
+work with a committee consisting of representatives from the Open Source
+Programs Office and the Google Open Source Strategy team. If for any reason you
+are uncomfortable reaching out to the Project Steward, please email
+opensource@google.com.
+
+We will investigate every complaint, but you may not receive a direct response.
+We will use our discretion in determining when and how to follow up on reported
+incidents, which may range from not taking action to permanent expulsion from
+the project and project-sponsored spaces. We will notify the accused of the
+report and provide them an opportunity to discuss it before any action is taken.
+The identity of the reporter will be omitted from the details of the report
+supplied to the accused. In potentially harmful situations, such as ongoing
+harassment or threats to anyone's safety, we may take action without notice.
+
+## Attribution
+
+This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
+available at
+https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
\ No newline at end of file
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 216cfb5..903bf40 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -80,25 +80,6 @@ We use `nox `__ to instrument our tests.
.. nox: https://pypi.org/project/nox/
-Note on Editable Installs / Develop Mode
-========================================
-
-- As mentioned previously, using ``setuptools`` in `develop mode`_
- or a ``pip`` `editable install`_ is not possible with this
- library. This is because this library uses `namespace packages`_.
- For context see `Issue #2316`_ and the relevant `PyPA issue`_.
-
- Since ``editable`` / ``develop`` mode can't be used, packages
- need to be installed directly. Hence your changes to the source
- tree don't get incorporated into the **already installed**
- package.
-
-.. _namespace packages: https://www.python.org/dev/peps/pep-0420/
-.. _Issue #2316: https://github.com/GoogleCloudPlatform/google-cloud-python/issues/2316
-.. _PyPA issue: https://github.com/pypa/packaging-problems/issues/12
-.. _develop mode: https://setuptools.readthedocs.io/en/latest/setuptools.html#development-mode
-.. _editable install: https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs
-
*****************************************
I'm getting weird errors... Can you help?
*****************************************
diff --git a/docs/budgets_v1/services.rst b/docs/budgets_v1/services.rst
new file mode 100644
index 0000000..ad9f447
--- /dev/null
+++ b/docs/budgets_v1/services.rst
@@ -0,0 +1,6 @@
+Services for Google Cloud Billing Budgets v1 API
+================================================
+
+.. automodule:: google.cloud.billing.budgets_v1.services.budget_service
+ :members:
+ :inherited-members:
diff --git a/docs/budgets_v1/types.rst b/docs/budgets_v1/types.rst
new file mode 100644
index 0000000..632739c
--- /dev/null
+++ b/docs/budgets_v1/types.rst
@@ -0,0 +1,6 @@
+Types for Google Cloud Billing Budgets v1 API
+=============================================
+
+.. automodule:: google.cloud.billing.budgets_v1.types
+ :members:
+ :show-inheritance:
diff --git a/docs/budgets_v1beta1/types.rst b/docs/budgets_v1beta1/types.rst
index 78279ce..ecd04b4 100644
--- a/docs/budgets_v1beta1/types.rst
+++ b/docs/budgets_v1beta1/types.rst
@@ -3,3 +3,4 @@ Types for Google Cloud Billing Budgets v1beta1 API
.. automodule:: google.cloud.billing.budgets_v1beta1.types
:members:
+ :show-inheritance:
diff --git a/docs/conf.py b/docs/conf.py
index f1452e2..fb4f52a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -20,12 +20,16 @@
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath(".."))
+# For plugins that can not read conf.py.
+# See also: https://github.com/docascode/sphinx-docfx-yaml/issues/85
+sys.path.insert(0, os.path.abspath("."))
+
__version__ = ""
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
-needs_sphinx = "1.6.3"
+needs_sphinx = "1.5.5"
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
@@ -35,6 +39,7 @@
"sphinx.ext.autosummary",
"sphinx.ext.intersphinx",
"sphinx.ext.coverage",
+ "sphinx.ext.doctest",
"sphinx.ext.napoleon",
"sphinx.ext.todo",
"sphinx.ext.viewcode",
@@ -90,7 +95,12 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
-exclude_patterns = ["_build"]
+exclude_patterns = [
+ "_build",
+ "samples/AUTHORING_GUIDE.md",
+ "samples/CONTRIBUTING.md",
+ "samples/snippets/README.rst",
+]
# The reST default role (used for this markup: `text`) to use for all
# documents.
@@ -339,6 +349,7 @@
"google-auth": ("https://google-auth.readthedocs.io/en/stable", None),
"google.api_core": ("https://googleapis.dev/python/google-api-core/latest/", None,),
"grpc": ("https://grpc.io/grpc/python/", None),
+ "proto-plus": ("https://proto-plus-python.readthedocs.io/en/latest/", None),
}
diff --git a/docs/index.rst b/docs/index.rst
index 6c090c3..21f3a35 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -7,6 +7,8 @@ Api Reference
.. toctree::
:maxdepth: 2
+ budgets_v1/services
+ budgets_v1/types
budgets_v1beta1/services
budgets_v1beta1/types
diff --git a/google/cloud/billing/budgets/__init__.py b/google/cloud/billing/budgets/__init__.py
index 65880ac..e684f5b 100644
--- a/google/cloud/billing/budgets/__init__.py
+++ b/google/cloud/billing/budgets/__init__.py
@@ -15,35 +15,26 @@
# limitations under the License.
#
-from google.cloud.billing.budgets_v1beta1.services.budget_service.async_client import (
+from google.cloud.billing.budgets_v1.services.budget_service.async_client import (
BudgetServiceAsyncClient,
)
-from google.cloud.billing.budgets_v1beta1.services.budget_service.client import (
+from google.cloud.billing.budgets_v1.services.budget_service.client import (
BudgetServiceClient,
)
-from google.cloud.billing.budgets_v1beta1.types.budget_model import AllUpdatesRule
-from google.cloud.billing.budgets_v1beta1.types.budget_model import Budget
-from google.cloud.billing.budgets_v1beta1.types.budget_model import BudgetAmount
-from google.cloud.billing.budgets_v1beta1.types.budget_model import Filter
-from google.cloud.billing.budgets_v1beta1.types.budget_model import LastPeriodAmount
-from google.cloud.billing.budgets_v1beta1.types.budget_model import ThresholdRule
-from google.cloud.billing.budgets_v1beta1.types.budget_service import (
- CreateBudgetRequest,
-)
-from google.cloud.billing.budgets_v1beta1.types.budget_service import (
- DeleteBudgetRequest,
-)
-from google.cloud.billing.budgets_v1beta1.types.budget_service import GetBudgetRequest
-from google.cloud.billing.budgets_v1beta1.types.budget_service import ListBudgetsRequest
-from google.cloud.billing.budgets_v1beta1.types.budget_service import (
- ListBudgetsResponse,
-)
-from google.cloud.billing.budgets_v1beta1.types.budget_service import (
- UpdateBudgetRequest,
-)
+from google.cloud.billing.budgets_v1.types.budget_model import Budget
+from google.cloud.billing.budgets_v1.types.budget_model import BudgetAmount
+from google.cloud.billing.budgets_v1.types.budget_model import Filter
+from google.cloud.billing.budgets_v1.types.budget_model import LastPeriodAmount
+from google.cloud.billing.budgets_v1.types.budget_model import NotificationsRule
+from google.cloud.billing.budgets_v1.types.budget_model import ThresholdRule
+from google.cloud.billing.budgets_v1.types.budget_service import CreateBudgetRequest
+from google.cloud.billing.budgets_v1.types.budget_service import DeleteBudgetRequest
+from google.cloud.billing.budgets_v1.types.budget_service import GetBudgetRequest
+from google.cloud.billing.budgets_v1.types.budget_service import ListBudgetsRequest
+from google.cloud.billing.budgets_v1.types.budget_service import ListBudgetsResponse
+from google.cloud.billing.budgets_v1.types.budget_service import UpdateBudgetRequest
__all__ = (
- "AllUpdatesRule",
"Budget",
"BudgetAmount",
"BudgetServiceAsyncClient",
@@ -55,6 +46,7 @@
"LastPeriodAmount",
"ListBudgetsRequest",
"ListBudgetsResponse",
+ "NotificationsRule",
"ThresholdRule",
"UpdateBudgetRequest",
)
diff --git a/google/cloud/billing/budgets_v1/__init__.py b/google/cloud/billing/budgets_v1/__init__.py
new file mode 100644
index 0000000..92aaeb0
--- /dev/null
+++ b/google/cloud/billing/budgets_v1/__init__.py
@@ -0,0 +1,47 @@
+# -*- 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.
+#
+
+from .services.budget_service import BudgetServiceClient
+from .types.budget_model import Budget
+from .types.budget_model import BudgetAmount
+from .types.budget_model import Filter
+from .types.budget_model import LastPeriodAmount
+from .types.budget_model import NotificationsRule
+from .types.budget_model import ThresholdRule
+from .types.budget_service import CreateBudgetRequest
+from .types.budget_service import DeleteBudgetRequest
+from .types.budget_service import GetBudgetRequest
+from .types.budget_service import ListBudgetsRequest
+from .types.budget_service import ListBudgetsResponse
+from .types.budget_service import UpdateBudgetRequest
+
+
+__all__ = (
+ "Budget",
+ "BudgetAmount",
+ "CreateBudgetRequest",
+ "DeleteBudgetRequest",
+ "Filter",
+ "GetBudgetRequest",
+ "LastPeriodAmount",
+ "ListBudgetsRequest",
+ "ListBudgetsResponse",
+ "NotificationsRule",
+ "ThresholdRule",
+ "UpdateBudgetRequest",
+ "BudgetServiceClient",
+)
diff --git a/google/cloud/billing/budgets_v1/proto/budget_model.proto b/google/cloud/billing/budgets_v1/proto/budget_model.proto
new file mode 100644
index 0000000..a23e080
--- /dev/null
+++ b/google/cloud/billing/budgets_v1/proto/budget_model.proto
@@ -0,0 +1,232 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+package google.cloud.billing.budgets.v1;
+
+import "google/api/field_behavior.proto";
+import "google/api/resource.proto";
+import "google/protobuf/struct.proto";
+import "google/type/money.proto";
+
+option go_package = "google.golang.org/genproto/googleapis/cloud/billing/budgets/v1;budgets";
+option java_multiple_files = true;
+option java_outer_classname = "BudgetModelProto";
+option java_package = "com.google.cloud.billing.budgets.v1";
+
+// A budget is a plan that describes what you expect to spend on Cloud
+// projects, plus the rules to execute as spend is tracked against that plan,
+// (for example, send an alert when 90% of the target spend is met).
+// Currently all plans are monthly budgets so the usage period(s) tracked are
+// implied (calendar months of usage back-to-back).
+message Budget {
+ option (google.api.resource) = {
+ type: "billingbudgets.googleapis.com/Budget"
+ pattern: "billingAccounts/{billing_account}/budgets/{budget}"
+ };
+
+ // Output only. Resource name of the budget.
+ // The resource name implies the scope of a budget. Values are of the form
+ // `billingAccounts/{billingAccountId}/budgets/{budgetId}`.
+ string name = 1 [(google.api.field_behavior) = OUTPUT_ONLY];
+
+ // User data for display name in UI. The name must be less than or equal to 60
+ // characters.
+ string display_name = 2;
+
+ // Optional. Filters that define which resources are used to compute
+ // the actual spend against the budget.
+ Filter budget_filter = 3 [(google.api.field_behavior) = OPTIONAL];
+
+ // Required. Budgeted amount.
+ BudgetAmount amount = 4 [(google.api.field_behavior) = REQUIRED];
+
+ // Optional. Rules that trigger alerts (notifications of thresholds
+ // being crossed) when spend exceeds the specified percentages of the budget.
+ repeated ThresholdRule threshold_rules = 5
+ [(google.api.field_behavior) = OPTIONAL];
+
+ // Optional. Rules to apply to notifications sent based on budget spend and
+ // thresholds.
+ NotificationsRule notifications_rule = 6
+ [(google.api.field_behavior) = OPTIONAL];
+
+ // Optional. Etag to validate that the object is unchanged for a
+ // read-modify-write operation.
+ // An empty etag will cause an update to overwrite other changes.
+ string etag = 7 [(google.api.field_behavior) = OPTIONAL];
+}
+
+// The budgeted amount for each usage period.
+message BudgetAmount {
+ // Specification for what amount to use as the budget.
+ oneof budget_amount {
+ // A specified amount to use as the budget.
+ // `currency_code` is optional. If specified, it must match the
+ // currency of the billing account. The `currency_code` is provided on
+ // output.
+ google.type.Money specified_amount = 1;
+
+ // Use the last period's actual spend as the budget for the present period.
+ LastPeriodAmount last_period_amount = 2;
+ }
+}
+
+// Describes a budget amount targeted to last period's spend.
+// At this time, the amount is automatically 100% of last period's spend;
+// that is, there are no other options yet.
+// Future configuration will be described here (for example, configuring a
+// percentage of last period's spend).
+message LastPeriodAmount {}
+
+// ThresholdRule contains a definition of a threshold which triggers
+// an alert (a notification of a threshold being crossed) to be sent when
+// spend goes above the specified amount.
+// Alerts are automatically e-mailed to users with the Billing Account
+// Administrator role or the Billing Account User role.
+// The thresholds here have no effect on notifications sent to anything
+// configured under `Budget.all_updates_rule`.
+message ThresholdRule {
+ // The type of basis used to determine if spend has passed the threshold.
+ enum Basis {
+ // Unspecified threshold basis.
+ BASIS_UNSPECIFIED = 0;
+
+ // Use current spend as the basis for comparison against the threshold.
+ CURRENT_SPEND = 1;
+
+ // Use forecasted spend for the period as the basis for comparison against
+ // the threshold.
+ FORECASTED_SPEND = 2;
+ }
+
+ // Required. Send an alert when this threshold is exceeded.
+ // This is a 1.0-based percentage, so 0.5 = 50%.
+ // Validation: non-negative number.
+ double threshold_percent = 1 [(google.api.field_behavior) = REQUIRED];
+
+ // Optional. The type of basis used to determine if spend has passed the
+ // threshold. Behavior defaults to CURRENT_SPEND if not set.
+ Basis spend_basis = 2 [(google.api.field_behavior) = OPTIONAL];
+}
+
+// NotificationsRule defines notifications that are sent based on budget spend
+// and thresholds.
+message NotificationsRule {
+ // Optional. The name of the Pub/Sub topic where budget related messages will
+ // be published, in the form `projects/{project_id}/topics/{topic_id}`.
+ // Updates are sent at regular intervals to the topic. The topic needs to be
+ // created before the budget is created; see
+ // https://cloud.google.com/billing/docs/how-to/budgets#manage-notifications
+ // for more details.
+ // Caller is expected to have
+ // `pubsub.topics.setIamPolicy` permission on the topic when it's set for a
+ // budget, otherwise, the API call will fail with PERMISSION_DENIED. See
+ // https://cloud.google.com/billing/docs/how-to/budgets-programmatic-notifications
+ // for more details on Pub/Sub roles and permissions.
+ string pubsub_topic = 1 [(google.api.field_behavior) = OPTIONAL];
+
+ // Optional. The schema version of the notification sent to `pubsub_topic`.
+ // Only "1.0" is accepted. It represents the JSON schema as defined in
+ // https://cloud.google.com/billing/docs/how-to/budgets-programmatic-notifications#notification_format
+ string schema_version = 2 [(google.api.field_behavior) = OPTIONAL];
+
+ // Optional. Targets to send notifications to when a threshold is exceeded.
+ // This is in addition to default recipients who have billing account IAM
+ // roles. The value is the full REST resource name of a monitoring
+ // notification channel with the form
+ // `projects/{project_id}/notificationChannels/{channel_id}`. A maximum of 5
+ // channels are allowed. See
+ // https://cloud.google.com/billing/docs/how-to/budgets-notification-recipients
+ // for more details.
+ repeated string monitoring_notification_channels = 3
+ [(google.api.field_behavior) = OPTIONAL];
+
+ // Optional. When set to true, disables default notifications sent when a
+ // threshold is exceeded. Default notifications are sent to those with Billing
+ // Account Administrator and Billing Account User IAM roles for the target
+ // account.
+ bool disable_default_iam_recipients = 4
+ [(google.api.field_behavior) = OPTIONAL];
+}
+
+// A filter for a budget, limiting the scope of the cost to calculate.
+message Filter {
+ // Specifies how credits should be treated when determining spend for
+ // threshold calculations.
+ enum CreditTypesTreatment {
+ // This is an invalid value.
+ CREDIT_TYPES_TREATMENT_UNSPECIFIED = 0;
+
+ // All types of credit are subtracted from the gross cost to determine the
+ // spend for threshold calculations.
+ INCLUDE_ALL_CREDITS = 1;
+
+ // All types of credit are added to the net cost to determine the spend for
+ // threshold calculations.
+ EXCLUDE_ALL_CREDITS = 2;
+
+ // Credit types specified in the credit_types field are subtracted from the
+ // gross cost to determine the spend for threshold calculations.
+ INCLUDE_SPECIFIED_CREDITS = 3;
+ }
+
+ // Optional. A set of projects of the form `projects/{project}`,
+ // specifying that usage from only this set of projects should be
+ // included in the budget. If omitted, the report will include all usage for
+ // the billing account, regardless of which project the usage occurred on.
+ // Only zero or one project can be specified currently.
+ repeated string projects = 1 [(google.api.field_behavior) = OPTIONAL];
+
+ // Optional. If
+ // [Filter.credit_types_treatment][google.cloud.billing.budgets.v1.Filter.credit_types_treatment]
+ // is INCLUDE_SPECIFIED_CREDITS, this is a list of credit types to be
+ // subtracted from gross cost to determine the spend for threshold
+ // calculations.
+ //
+ // If
+ // [Filter.credit_types_treatment][google.cloud.billing.budgets.v1.Filter.credit_types_treatment]
+ // is **not** INCLUDE_SPECIFIED_CREDITS, this field must be empty. See [a list
+ // of acceptable credit type
+ // values](https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables#credits-type).
+ repeated string credit_types = 7 [(google.api.field_behavior) = OPTIONAL];
+
+ // Optional. If not set, default behavior is `INCLUDE_ALL_CREDITS`.
+ CreditTypesTreatment credit_types_treatment = 4
+ [(google.api.field_behavior) = OPTIONAL];
+
+ // Optional. A set of services of the form `services/{service_id}`,
+ // specifying that usage from only this set of services should be
+ // included in the budget. If omitted, the report will include usage for
+ // all the services.
+ // The service names are available through the Catalog API:
+ // https://cloud.google.com/billing/v1/how-tos/catalog-api.
+ repeated string services = 3 [(google.api.field_behavior) = OPTIONAL];
+
+ // Optional. A set of subaccounts of the form `billingAccounts/{account_id}`,
+ // specifying that usage from only this set of subaccounts should be included
+ // in the budget. If a subaccount is set to the name of the parent account,
+ // usage from the parent account will be included. If the field is omitted,
+ // the report will include usage from the parent account and all subaccounts,
+ // if they exist.
+ repeated string subaccounts = 5 [(google.api.field_behavior) = OPTIONAL];
+
+ // Optional. A single label and value pair specifying that usage from only
+ // this set of labeled resources should be included in the budget. Currently,
+ // multiple entries or multiple values per entry are not allowed. If omitted,
+ // the report will include all labeled and unlabeled usage.
+ map labels = 6
+ [(google.api.field_behavior) = OPTIONAL];
+}
diff --git a/google/cloud/billing/budgets_v1/proto/budget_service.proto b/google/cloud/billing/budgets_v1/proto/budget_service.proto
new file mode 100644
index 0000000..bb2be21
--- /dev/null
+++ b/google/cloud/billing/budgets_v1/proto/budget_service.proto
@@ -0,0 +1,182 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+package google.cloud.billing.budgets.v1;
+
+import "google/api/annotations.proto";
+import "google/api/client.proto";
+import "google/api/field_behavior.proto";
+import "google/api/resource.proto";
+import "google/cloud/billing/budgets/v1/budget_model.proto";
+import "google/protobuf/empty.proto";
+import "google/protobuf/field_mask.proto";
+
+option go_package = "google.golang.org/genproto/googleapis/cloud/billing/budgets/v1;budgets";
+option java_multiple_files = true;
+option java_outer_classname = "BudgetServiceProto";
+option java_package = "com.google.cloud.billing.budgets.v1";
+
+// BudgetService stores Cloud Billing budgets, which define a
+// budget plan and rules to execute as we track spend against that plan.
+service BudgetService {
+ option (google.api.default_host) = "billingbudgets.googleapis.com";
+ option (google.api.oauth_scopes) =
+ "https://www.googleapis.com/auth/cloud-billing,"
+ "https://www.googleapis.com/auth/cloud-platform";
+
+ // Creates a new budget. See
+ // [Quotas and limits](https://cloud.google.com/billing/quotas)
+ // for more information on the limits of the number of budgets you can create.
+ rpc CreateBudget(CreateBudgetRequest) returns (Budget) {
+ option (google.api.http) = {
+ post: "/v1/{parent=billingAccounts/*}/budgets"
+ body: "budget"
+ };
+ option (google.api.method_signature) = "parent,budget";
+ }
+
+ // Updates a budget and returns the updated budget.
+ //
+ // WARNING: There are some fields exposed on the Google Cloud Console that
+ // aren't available on this API. Budget fields that are not exposed in
+ // this API will not be changed by this method.
+ rpc UpdateBudget(UpdateBudgetRequest) returns (Budget) {
+ option (google.api.http) = {
+ patch: "/v1/{budget.name=billingAccounts/*/budgets/*}"
+ body: "budget"
+ };
+ option (google.api.method_signature) = "budget,update_mask";
+ }
+
+ // Returns a budget.
+ //
+ // WARNING: There are some fields exposed on the Google Cloud Console that
+ // aren't available on this API. When reading from the API, you will not
+ // see these fields in the return value, though they may have been set
+ // in the Cloud Console.
+ rpc GetBudget(GetBudgetRequest) returns (Budget) {
+ option (google.api.http) = {
+ get: "/v1/{name=billingAccounts/*/budgets/*}"
+ };
+ option (google.api.method_signature) = "name";
+ }
+
+ // Returns a list of budgets for a billing account.
+ //
+ // WARNING: There are some fields exposed on the Google Cloud Console that
+ // aren't available on this API. When reading from the API, you will not
+ // see these fields in the return value, though they may have been set
+ // in the Cloud Console.
+ rpc ListBudgets(ListBudgetsRequest) returns (ListBudgetsResponse) {
+ option (google.api.http) = {
+ get: "/v1/{parent=billingAccounts/*}/budgets"
+ };
+ option (google.api.method_signature) = "parent";
+ }
+
+ // Deletes a budget. Returns successfully if already deleted.
+ rpc DeleteBudget(DeleteBudgetRequest) returns (google.protobuf.Empty) {
+ option (google.api.http) = {
+ delete: "/v1/{name=billingAccounts/*/budgets/*}"
+ };
+ option (google.api.method_signature) = "name";
+ }
+}
+
+// Request for CreateBudget
+message CreateBudgetRequest {
+ // Required. The name of the billing account to create the budget in. Values
+ // are of the form `billingAccounts/{billingAccountId}`.
+ string parent = 1 [
+ (google.api.field_behavior) = REQUIRED,
+ (google.api.resource_reference) = {
+ child_type: "billingbudgets.googleapis.com/Budget"
+ }
+ ];
+
+ // Required. Budget to create.
+ Budget budget = 2 [(google.api.field_behavior) = REQUIRED];
+}
+
+// Request for UpdateBudget
+message UpdateBudgetRequest {
+ // Required. The updated budget object.
+ // The budget to update is specified by the budget name in the budget.
+ Budget budget = 1 [(google.api.field_behavior) = REQUIRED];
+
+ // Optional. Indicates which fields in the provided budget to update.
+ // Read-only fields (such as `name`) cannot be changed. If this is not
+ // provided, then only fields with non-default values from the request are
+ // updated. See
+ // https://developers.google.com/protocol-buffers/docs/proto3#default for more
+ // details about default values.
+ google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = OPTIONAL];
+}
+
+// Request for GetBudget
+message GetBudgetRequest {
+ // Required. Name of budget to get. Values are of the form
+ // `billingAccounts/{billingAccountId}/budgets/{budgetId}`.
+ string name = 1 [
+ (google.api.field_behavior) = REQUIRED,
+ (google.api.resource_reference) = {
+ type: "billingbudgets.googleapis.com/Budget"
+ }
+ ];
+}
+
+// Request for ListBudgets
+message ListBudgetsRequest {
+ // Required. Name of billing account to list budgets under. Values
+ // are of the form `billingAccounts/{billingAccountId}`.
+ string parent = 1 [
+ (google.api.field_behavior) = REQUIRED,
+ (google.api.resource_reference) = {
+ child_type: "billingbudgets.googleapis.com/Budget"
+ }
+ ];
+
+ // Optional. The maximum number of budgets to return per page.
+ // The default and maximum value are 100.
+ int32 page_size = 2 [(google.api.field_behavior) = OPTIONAL];
+
+ // Optional. The value returned by the last `ListBudgetsResponse` which
+ // indicates that this is a continuation of a prior `ListBudgets` call,
+ // and that the system should return the next page of data.
+ string page_token = 3 [(google.api.field_behavior) = OPTIONAL];
+}
+
+// Response for ListBudgets
+message ListBudgetsResponse {
+ // List of the budgets owned by the requested billing account.
+ repeated Budget budgets = 1;
+
+ // If not empty, indicates that there may be more budgets that match the
+ // request; this value should be passed in a new `ListBudgetsRequest`.
+ string next_page_token = 2;
+}
+
+// Request for DeleteBudget
+message DeleteBudgetRequest {
+ // Required. Name of the budget to delete. Values are of the form
+ // `billingAccounts/{billingAccountId}/budgets/{budgetId}`.
+ string name = 1 [
+ (google.api.field_behavior) = REQUIRED,
+ (google.api.resource_reference) = {
+ type: "billingbudgets.googleapis.com/Budget"
+ }
+ ];
+}
diff --git a/google/cloud/billing/budgets_v1/py.typed b/google/cloud/billing/budgets_v1/py.typed
new file mode 100644
index 0000000..b067b19
--- /dev/null
+++ b/google/cloud/billing/budgets_v1/py.typed
@@ -0,0 +1,2 @@
+# Marker file for PEP 561.
+# The google-cloud-billing-budgets package uses inline types.
diff --git a/google/cloud/billing/budgets_v1/services/__init__.py b/google/cloud/billing/budgets_v1/services/__init__.py
new file mode 100644
index 0000000..42ffdf2
--- /dev/null
+++ b/google/cloud/billing/budgets_v1/services/__init__.py
@@ -0,0 +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/google/cloud/billing/budgets_v1/services/budget_service/__init__.py b/google/cloud/billing/budgets_v1/services/budget_service/__init__.py
new file mode 100644
index 0000000..97d50ab
--- /dev/null
+++ b/google/cloud/billing/budgets_v1/services/budget_service/__init__.py
@@ -0,0 +1,24 @@
+# -*- 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.
+#
+
+from .client import BudgetServiceClient
+from .async_client import BudgetServiceAsyncClient
+
+__all__ = (
+ "BudgetServiceClient",
+ "BudgetServiceAsyncClient",
+)
diff --git a/google/cloud/billing/budgets_v1/services/budget_service/async_client.py b/google/cloud/billing/budgets_v1/services/budget_service/async_client.py
new file mode 100644
index 0000000..1ab8d15
--- /dev/null
+++ b/google/cloud/billing/budgets_v1/services/budget_service/async_client.py
@@ -0,0 +1,613 @@
+# -*- 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.
+#
+
+from collections import OrderedDict
+import functools
+import re
+from typing import Dict, Sequence, Tuple, Type, Union
+import pkg_resources
+
+import google.api_core.client_options as ClientOptions # type: ignore
+from google.api_core import exceptions # type: ignore
+from google.api_core import gapic_v1 # type: ignore
+from google.api_core import retry as retries # type: ignore
+from google.auth import credentials # type: ignore
+from google.oauth2 import service_account # type: ignore
+
+from google.cloud.billing.budgets_v1.services.budget_service import pagers
+from google.cloud.billing.budgets_v1.types import budget_model
+from google.cloud.billing.budgets_v1.types import budget_service
+from google.protobuf import field_mask_pb2 as field_mask # type: ignore
+
+from .transports.base import BudgetServiceTransport, DEFAULT_CLIENT_INFO
+from .transports.grpc_asyncio import BudgetServiceGrpcAsyncIOTransport
+from .client import BudgetServiceClient
+
+
+class BudgetServiceAsyncClient:
+ """BudgetService stores Cloud Billing budgets, which define a
+ budget plan and rules to execute as we track spend against that
+ plan.
+ """
+
+ _client: BudgetServiceClient
+
+ DEFAULT_ENDPOINT = BudgetServiceClient.DEFAULT_ENDPOINT
+ DEFAULT_MTLS_ENDPOINT = BudgetServiceClient.DEFAULT_MTLS_ENDPOINT
+
+ budget_path = staticmethod(BudgetServiceClient.budget_path)
+ parse_budget_path = staticmethod(BudgetServiceClient.parse_budget_path)
+
+ common_billing_account_path = staticmethod(
+ BudgetServiceClient.common_billing_account_path
+ )
+ parse_common_billing_account_path = staticmethod(
+ BudgetServiceClient.parse_common_billing_account_path
+ )
+
+ common_folder_path = staticmethod(BudgetServiceClient.common_folder_path)
+ parse_common_folder_path = staticmethod(
+ BudgetServiceClient.parse_common_folder_path
+ )
+
+ common_organization_path = staticmethod(
+ BudgetServiceClient.common_organization_path
+ )
+ parse_common_organization_path = staticmethod(
+ BudgetServiceClient.parse_common_organization_path
+ )
+
+ common_project_path = staticmethod(BudgetServiceClient.common_project_path)
+ parse_common_project_path = staticmethod(
+ BudgetServiceClient.parse_common_project_path
+ )
+
+ common_location_path = staticmethod(BudgetServiceClient.common_location_path)
+ parse_common_location_path = staticmethod(
+ BudgetServiceClient.parse_common_location_path
+ )
+
+ from_service_account_file = BudgetServiceClient.from_service_account_file
+ from_service_account_json = from_service_account_file
+
+ @property
+ def transport(self) -> BudgetServiceTransport:
+ """Return the transport used by the client instance.
+
+ Returns:
+ BudgetServiceTransport: The transport used by the client instance.
+ """
+ return self._client.transport
+
+ get_transport_class = functools.partial(
+ type(BudgetServiceClient).get_transport_class, type(BudgetServiceClient)
+ )
+
+ def __init__(
+ self,
+ *,
+ credentials: credentials.Credentials = None,
+ transport: Union[str, BudgetServiceTransport] = "grpc_asyncio",
+ client_options: ClientOptions = None,
+ client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
+ ) -> None:
+ """Instantiate the budget service client.
+
+ Args:
+ credentials (Optional[google.auth.credentials.Credentials]): The
+ authorization credentials to attach to requests. These
+ 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, ~.BudgetServiceTransport]): The
+ transport to use. If set to None, a transport is chosen
+ automatically.
+ client_options (ClientOptions): Custom options for the client. It
+ won't take effect if a ``transport`` instance is provided.
+ (1) The ``api_endpoint`` property can be used to override the
+ default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT
+ environment variable can also be used to override the endpoint:
+ "always" (always use the default mTLS endpoint), "never" (always
+ use the default regular endpoint) and "auto" (auto switch to the
+ default mTLS endpoint if client certificate is present, this is
+ the default value). However, the ``api_endpoint`` property takes
+ precedence if provided.
+ (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
+ is "true", then the ``client_cert_source`` property can be used
+ to provide client certificate for mutual TLS transport. If
+ not provided, the default SSL client certificate will be used if
+ present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
+ set, no client certificate will be used.
+
+ Raises:
+ google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
+ creation failed for any reason.
+ """
+
+ self._client = BudgetServiceClient(
+ credentials=credentials,
+ transport=transport,
+ client_options=client_options,
+ client_info=client_info,
+ )
+
+ async def create_budget(
+ self,
+ request: budget_service.CreateBudgetRequest = None,
+ *,
+ parent: str = None,
+ budget: budget_model.Budget = None,
+ retry: retries.Retry = gapic_v1.method.DEFAULT,
+ timeout: float = None,
+ metadata: Sequence[Tuple[str, str]] = (),
+ ) -> budget_model.Budget:
+ r"""Creates a new budget. See `Quotas and
+ limits `__ for more
+ information on the limits of the number of budgets you can
+ create.
+
+ Args:
+ request (:class:`~.budget_service.CreateBudgetRequest`):
+ The request object. Request for CreateBudget
+ parent (:class:`str`):
+ Required. The name of the billing account to create the
+ budget in. Values are of the form
+ ``billingAccounts/{billingAccountId}``.
+ This corresponds to the ``parent`` field
+ on the ``request`` instance; if ``request`` is provided, this
+ should not be set.
+ budget (:class:`~.budget_model.Budget`):
+ Required. Budget to create.
+ This corresponds to the ``budget`` field
+ on the ``request`` instance; if ``request`` is provided, this
+ should not be set.
+
+ retry (google.api_core.retry.Retry): Designation of what errors, if any,
+ should be retried.
+ timeout (float): The timeout for this request.
+ metadata (Sequence[Tuple[str, str]]): Strings which should be
+ sent along with the request as metadata.
+
+ Returns:
+ ~.budget_model.Budget:
+ A budget is a plan that describes
+ what you expect to spend on Cloud
+ projects, plus the rules to execute as
+ spend is tracked against that plan, (for
+ example, send an alert when 90% of the
+ target spend is met). Currently all
+ plans are monthly budgets so the usage
+ period(s) tracked are implied (calendar
+ months of usage back-to-back).
+
+ """
+ # Create or coerce a protobuf request object.
+ # Sanity check: If we got a request object, we should *not* have
+ # gotten any keyword arguments that map to the request.
+ has_flattened_params = any([parent, budget])
+ if request is not None and has_flattened_params:
+ raise ValueError(
+ "If the `request` argument is set, then none of "
+ "the individual field arguments should be set."
+ )
+
+ request = budget_service.CreateBudgetRequest(request)
+
+ # If we have keyword arguments corresponding to fields on the
+ # request, apply these.
+
+ if parent is not None:
+ request.parent = parent
+ if budget is not None:
+ request.budget = budget
+
+ # Wrap the RPC method; this adds retry and timeout information,
+ # and friendly error handling.
+ rpc = gapic_v1.method_async.wrap_method(
+ self._client._transport.create_budget,
+ default_timeout=60.0,
+ client_info=DEFAULT_CLIENT_INFO,
+ )
+
+ # Certain fields should be provided within the metadata header;
+ # add these here.
+ metadata = tuple(metadata) + (
+ gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
+ )
+
+ # Send the request.
+ response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,)
+
+ # Done; return the response.
+ return response
+
+ async def update_budget(
+ self,
+ request: budget_service.UpdateBudgetRequest = None,
+ *,
+ budget: budget_model.Budget = None,
+ update_mask: field_mask.FieldMask = None,
+ retry: retries.Retry = gapic_v1.method.DEFAULT,
+ timeout: float = None,
+ metadata: Sequence[Tuple[str, str]] = (),
+ ) -> budget_model.Budget:
+ r"""Updates a budget and returns the updated budget.
+ WARNING: There are some fields exposed on the Google
+ Cloud Console that aren't available on this API. Budget
+ fields that are not exposed in this API will not be
+ changed by this method.
+
+ Args:
+ request (:class:`~.budget_service.UpdateBudgetRequest`):
+ The request object. Request for UpdateBudget
+ budget (:class:`~.budget_model.Budget`):
+ Required. The updated budget object.
+ The budget to update is specified by the
+ budget name in the budget.
+ This corresponds to the ``budget`` field
+ on the ``request`` instance; if ``request`` is provided, this
+ should not be set.
+ update_mask (:class:`~.field_mask.FieldMask`):
+ Optional. Indicates which fields in the provided budget
+ to update. Read-only fields (such as ``name``) cannot be
+ changed. If this is not provided, then only fields with
+ non-default values from the request are updated. See
+ https://developers.google.com/protocol-buffers/docs/proto3#default
+ for more details about default values.
+ This corresponds to the ``update_mask`` field
+ on the ``request`` instance; if ``request`` is provided, this
+ should not be set.
+
+ retry (google.api_core.retry.Retry): Designation of what errors, if any,
+ should be retried.
+ timeout (float): The timeout for this request.
+ metadata (Sequence[Tuple[str, str]]): Strings which should be
+ sent along with the request as metadata.
+
+ Returns:
+ ~.budget_model.Budget:
+ A budget is a plan that describes
+ what you expect to spend on Cloud
+ projects, plus the rules to execute as
+ spend is tracked against that plan, (for
+ example, send an alert when 90% of the
+ target spend is met). Currently all
+ plans are monthly budgets so the usage
+ period(s) tracked are implied (calendar
+ months of usage back-to-back).
+
+ """
+ # Create or coerce a protobuf request object.
+ # Sanity check: If we got a request object, we should *not* have
+ # gotten any keyword arguments that map to the request.
+ has_flattened_params = any([budget, update_mask])
+ if request is not None and has_flattened_params:
+ raise ValueError(
+ "If the `request` argument is set, then none of "
+ "the individual field arguments should be set."
+ )
+
+ request = budget_service.UpdateBudgetRequest(request)
+
+ # If we have keyword arguments corresponding to fields on the
+ # request, apply these.
+
+ if budget is not None:
+ request.budget = budget
+ if update_mask is not None:
+ request.update_mask = update_mask
+
+ # Wrap the RPC method; this adds retry and timeout information,
+ # and friendly error handling.
+ rpc = gapic_v1.method_async.wrap_method(
+ self._client._transport.update_budget,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.DeadlineExceeded, exceptions.ServiceUnavailable,
+ ),
+ ),
+ default_timeout=60.0,
+ client_info=DEFAULT_CLIENT_INFO,
+ )
+
+ # Certain fields should be provided within the metadata header;
+ # add these here.
+ metadata = tuple(metadata) + (
+ gapic_v1.routing_header.to_grpc_metadata(
+ (("budget.name", request.budget.name),)
+ ),
+ )
+
+ # Send the request.
+ response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,)
+
+ # Done; return the response.
+ return response
+
+ async def get_budget(
+ self,
+ request: budget_service.GetBudgetRequest = None,
+ *,
+ name: str = None,
+ retry: retries.Retry = gapic_v1.method.DEFAULT,
+ timeout: float = None,
+ metadata: Sequence[Tuple[str, str]] = (),
+ ) -> budget_model.Budget:
+ r"""Returns a budget.
+ WARNING: There are some fields exposed on the Google
+ Cloud Console that aren't available on this API. When
+ reading from the API, you will not see these fields in
+ the return value, though they may have been set in the
+ Cloud Console.
+
+ Args:
+ request (:class:`~.budget_service.GetBudgetRequest`):
+ The request object. Request for GetBudget
+ name (:class:`str`):
+ Required. Name of budget to get. Values are of the form
+ ``billingAccounts/{billingAccountId}/budgets/{budgetId}``.
+ This corresponds to the ``name`` field
+ on the ``request`` instance; if ``request`` is provided, this
+ should not be set.
+
+ retry (google.api_core.retry.Retry): Designation of what errors, if any,
+ should be retried.
+ timeout (float): The timeout for this request.
+ metadata (Sequence[Tuple[str, str]]): Strings which should be
+ sent along with the request as metadata.
+
+ Returns:
+ ~.budget_model.Budget:
+ A budget is a plan that describes
+ what you expect to spend on Cloud
+ projects, plus the rules to execute as
+ spend is tracked against that plan, (for
+ example, send an alert when 90% of the
+ target spend is met). Currently all
+ plans are monthly budgets so the usage
+ period(s) tracked are implied (calendar
+ months of usage back-to-back).
+
+ """
+ # Create or coerce a protobuf request object.
+ # Sanity check: If we got a request object, we should *not* have
+ # gotten any keyword arguments that map to the request.
+ has_flattened_params = any([name])
+ if request is not None and has_flattened_params:
+ raise ValueError(
+ "If the `request` argument is set, then none of "
+ "the individual field arguments should be set."
+ )
+
+ request = budget_service.GetBudgetRequest(request)
+
+ # If we have keyword arguments corresponding to fields on the
+ # request, apply these.
+
+ if name is not None:
+ request.name = name
+
+ # Wrap the RPC method; this adds retry and timeout information,
+ # and friendly error handling.
+ rpc = gapic_v1.method_async.wrap_method(
+ self._client._transport.get_budget,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.DeadlineExceeded, exceptions.ServiceUnavailable,
+ ),
+ ),
+ default_timeout=60.0,
+ client_info=DEFAULT_CLIENT_INFO,
+ )
+
+ # Certain fields should be provided within the metadata header;
+ # add these here.
+ metadata = tuple(metadata) + (
+ gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
+ )
+
+ # Send the request.
+ response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,)
+
+ # Done; return the response.
+ return response
+
+ async def list_budgets(
+ self,
+ request: budget_service.ListBudgetsRequest = None,
+ *,
+ parent: str = None,
+ retry: retries.Retry = gapic_v1.method.DEFAULT,
+ timeout: float = None,
+ metadata: Sequence[Tuple[str, str]] = (),
+ ) -> pagers.ListBudgetsAsyncPager:
+ r"""Returns a list of budgets for a billing account.
+ WARNING: There are some fields exposed on the Google
+ Cloud Console that aren't available on this API. When
+ reading from the API, you will not see these fields in
+ the return value, though they may have been set in the
+ Cloud Console.
+
+ Args:
+ request (:class:`~.budget_service.ListBudgetsRequest`):
+ The request object. Request for ListBudgets
+ parent (:class:`str`):
+ Required. Name of billing account to list budgets under.
+ Values are of the form
+ ``billingAccounts/{billingAccountId}``.
+ This corresponds to the ``parent`` field
+ on the ``request`` instance; if ``request`` is provided, this
+ should not be set.
+
+ retry (google.api_core.retry.Retry): Designation of what errors, if any,
+ should be retried.
+ timeout (float): The timeout for this request.
+ metadata (Sequence[Tuple[str, str]]): Strings which should be
+ sent along with the request as metadata.
+
+ Returns:
+ ~.pagers.ListBudgetsAsyncPager:
+ Response for ListBudgets
+ Iterating over this object will yield
+ results and resolve additional pages
+ automatically.
+
+ """
+ # Create or coerce a protobuf request object.
+ # Sanity check: If we got a request object, we should *not* have
+ # gotten any keyword arguments that map to the request.
+ has_flattened_params = any([parent])
+ if request is not None and has_flattened_params:
+ raise ValueError(
+ "If the `request` argument is set, then none of "
+ "the individual field arguments should be set."
+ )
+
+ request = budget_service.ListBudgetsRequest(request)
+
+ # If we have keyword arguments corresponding to fields on the
+ # request, apply these.
+
+ if parent is not None:
+ request.parent = parent
+
+ # Wrap the RPC method; this adds retry and timeout information,
+ # and friendly error handling.
+ rpc = gapic_v1.method_async.wrap_method(
+ self._client._transport.list_budgets,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.DeadlineExceeded, exceptions.ServiceUnavailable,
+ ),
+ ),
+ default_timeout=60.0,
+ client_info=DEFAULT_CLIENT_INFO,
+ )
+
+ # Certain fields should be provided within the metadata header;
+ # add these here.
+ metadata = tuple(metadata) + (
+ gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
+ )
+
+ # Send the request.
+ response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,)
+
+ # This method is paged; wrap the response in a pager, which provides
+ # an `__aiter__` convenience method.
+ response = pagers.ListBudgetsAsyncPager(
+ method=rpc, request=request, response=response, metadata=metadata,
+ )
+
+ # Done; return the response.
+ return response
+
+ async def delete_budget(
+ self,
+ request: budget_service.DeleteBudgetRequest = None,
+ *,
+ name: str = None,
+ retry: retries.Retry = gapic_v1.method.DEFAULT,
+ timeout: float = None,
+ metadata: Sequence[Tuple[str, str]] = (),
+ ) -> None:
+ r"""Deletes a budget. Returns successfully if already
+ deleted.
+
+ Args:
+ request (:class:`~.budget_service.DeleteBudgetRequest`):
+ The request object. Request for DeleteBudget
+ name (:class:`str`):
+ Required. Name of the budget to delete. Values are of
+ the form
+ ``billingAccounts/{billingAccountId}/budgets/{budgetId}``.
+ This corresponds to the ``name`` field
+ on the ``request`` instance; if ``request`` is provided, this
+ should not be set.
+
+ retry (google.api_core.retry.Retry): Designation of what errors, if any,
+ should be retried.
+ timeout (float): The timeout for this request.
+ metadata (Sequence[Tuple[str, str]]): Strings which should be
+ sent along with the request as metadata.
+ """
+ # Create or coerce a protobuf request object.
+ # Sanity check: If we got a request object, we should *not* have
+ # gotten any keyword arguments that map to the request.
+ has_flattened_params = any([name])
+ if request is not None and has_flattened_params:
+ raise ValueError(
+ "If the `request` argument is set, then none of "
+ "the individual field arguments should be set."
+ )
+
+ request = budget_service.DeleteBudgetRequest(request)
+
+ # If we have keyword arguments corresponding to fields on the
+ # request, apply these.
+
+ if name is not None:
+ request.name = name
+
+ # Wrap the RPC method; this adds retry and timeout information,
+ # and friendly error handling.
+ rpc = gapic_v1.method_async.wrap_method(
+ self._client._transport.delete_budget,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.DeadlineExceeded, exceptions.ServiceUnavailable,
+ ),
+ ),
+ default_timeout=60.0,
+ client_info=DEFAULT_CLIENT_INFO,
+ )
+
+ # Certain fields should be provided within the metadata header;
+ # add these here.
+ metadata = tuple(metadata) + (
+ gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
+ )
+
+ # Send the request.
+ await rpc(
+ request, retry=retry, timeout=timeout, metadata=metadata,
+ )
+
+
+try:
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
+ gapic_version=pkg_resources.get_distribution(
+ "google-cloud-billing-budgets",
+ ).version,
+ )
+except pkg_resources.DistributionNotFound:
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo()
+
+
+__all__ = ("BudgetServiceAsyncClient",)
diff --git a/google/cloud/billing/budgets_v1/services/budget_service/client.py b/google/cloud/billing/budgets_v1/services/budget_service/client.py
new file mode 100644
index 0000000..25ed583
--- /dev/null
+++ b/google/cloud/billing/budgets_v1/services/budget_service/client.py
@@ -0,0 +1,774 @@
+# -*- 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.
+#
+
+from collections import OrderedDict
+from distutils import util
+import os
+import re
+from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union
+import pkg_resources
+
+from google.api_core import client_options as client_options_lib # type: ignore
+from google.api_core import exceptions # type: ignore
+from google.api_core import gapic_v1 # type: ignore
+from google.api_core import retry as retries # type: ignore
+from google.auth import credentials # type: ignore
+from google.auth.transport import mtls # type: ignore
+from google.auth.transport.grpc import SslCredentials # type: ignore
+from google.auth.exceptions import MutualTLSChannelError # type: ignore
+from google.oauth2 import service_account # type: ignore
+
+from google.cloud.billing.budgets_v1.services.budget_service import pagers
+from google.cloud.billing.budgets_v1.types import budget_model
+from google.cloud.billing.budgets_v1.types import budget_service
+from google.protobuf import field_mask_pb2 as field_mask # type: ignore
+
+from .transports.base import BudgetServiceTransport, DEFAULT_CLIENT_INFO
+from .transports.grpc import BudgetServiceGrpcTransport
+from .transports.grpc_asyncio import BudgetServiceGrpcAsyncIOTransport
+
+
+class BudgetServiceClientMeta(type):
+ """Metaclass for the BudgetService client.
+
+ This provides class-level methods for building and retrieving
+ support objects (e.g. transport) without polluting the client instance
+ objects.
+ """
+
+ _transport_registry = OrderedDict() # type: Dict[str, Type[BudgetServiceTransport]]
+ _transport_registry["grpc"] = BudgetServiceGrpcTransport
+ _transport_registry["grpc_asyncio"] = BudgetServiceGrpcAsyncIOTransport
+
+ def get_transport_class(cls, label: str = None,) -> Type[BudgetServiceTransport]:
+ """Return an appropriate transport class.
+
+ Args:
+ label: The name of the desired transport. If none is
+ provided, then the first transport in the registry is used.
+
+ Returns:
+ The transport class to use.
+ """
+ # If a specific transport is requested, return that one.
+ if label:
+ return cls._transport_registry[label]
+
+ # No transport is requested; return the default (that is, the first one
+ # in the dictionary).
+ return next(iter(cls._transport_registry.values()))
+
+
+class BudgetServiceClient(metaclass=BudgetServiceClientMeta):
+ """BudgetService stores Cloud Billing budgets, which define a
+ budget plan and rules to execute as we track spend against that
+ plan.
+ """
+
+ @staticmethod
+ def _get_default_mtls_endpoint(api_endpoint):
+ """Convert api endpoint to mTLS endpoint.
+ Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
+ "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
+ Args:
+ api_endpoint (Optional[str]): the api endpoint to convert.
+ Returns:
+ str: converted mTLS api endpoint.
+ """
+ if not api_endpoint:
+ return api_endpoint
+
+ mtls_endpoint_re = re.compile(
+ r"(?P[^.]+)(?P\.mtls)?(?P\.sandbox)?(?P\.googleapis\.com)?"
+ )
+
+ m = mtls_endpoint_re.match(api_endpoint)
+ name, mtls, sandbox, googledomain = m.groups()
+ if mtls or not googledomain:
+ return api_endpoint
+
+ if sandbox:
+ return api_endpoint.replace(
+ "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
+ )
+
+ return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")
+
+ DEFAULT_ENDPOINT = "billingbudgets.googleapis.com"
+ DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__( # type: ignore
+ DEFAULT_ENDPOINT
+ )
+
+ @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:
+ {@api.name}: The constructed client.
+ """
+ credentials = service_account.Credentials.from_service_account_file(filename)
+ kwargs["credentials"] = credentials
+ return cls(*args, **kwargs)
+
+ from_service_account_json = from_service_account_file
+
+ @property
+ def transport(self) -> BudgetServiceTransport:
+ """Return the transport used by the client instance.
+
+ Returns:
+ BudgetServiceTransport: The transport used by the client instance.
+ """
+ return self._transport
+
+ @staticmethod
+ def budget_path(billing_account: str, budget: str,) -> str:
+ """Return a fully-qualified budget string."""
+ return "billingAccounts/{billing_account}/budgets/{budget}".format(
+ billing_account=billing_account, budget=budget,
+ )
+
+ @staticmethod
+ def parse_budget_path(path: str) -> Dict[str, str]:
+ """Parse a budget path into its component segments."""
+ m = re.match(
+ r"^billingAccounts/(?P.+?)/budgets/(?P.+?)$", path
+ )
+ return m.groupdict() if m else {}
+
+ @staticmethod
+ def common_billing_account_path(billing_account: str,) -> str:
+ """Return a fully-qualified billing_account string."""
+ return "billingAccounts/{billing_account}".format(
+ billing_account=billing_account,
+ )
+
+ @staticmethod
+ def parse_common_billing_account_path(path: str) -> Dict[str, str]:
+ """Parse a billing_account path into its component segments."""
+ m = re.match(r"^billingAccounts/(?P.+?)$", path)
+ return m.groupdict() if m else {}
+
+ @staticmethod
+ def common_folder_path(folder: str,) -> str:
+ """Return a fully-qualified folder string."""
+ return "folders/{folder}".format(folder=folder,)
+
+ @staticmethod
+ def parse_common_folder_path(path: str) -> Dict[str, str]:
+ """Parse a folder path into its component segments."""
+ m = re.match(r"^folders/(?P.+?)$", path)
+ return m.groupdict() if m else {}
+
+ @staticmethod
+ def common_organization_path(organization: str,) -> str:
+ """Return a fully-qualified organization string."""
+ return "organizations/{organization}".format(organization=organization,)
+
+ @staticmethod
+ def parse_common_organization_path(path: str) -> Dict[str, str]:
+ """Parse a organization path into its component segments."""
+ m = re.match(r"^organizations/(?P.+?)$", path)
+ return m.groupdict() if m else {}
+
+ @staticmethod
+ def common_project_path(project: str,) -> str:
+ """Return a fully-qualified project string."""
+ return "projects/{project}".format(project=project,)
+
+ @staticmethod
+ def parse_common_project_path(path: str) -> Dict[str, str]:
+ """Parse a project path into its component segments."""
+ m = re.match(r"^projects/(?P.+?)$", path)
+ return m.groupdict() if m else {}
+
+ @staticmethod
+ def common_location_path(project: str, location: str,) -> str:
+ """Return a fully-qualified location string."""
+ return "projects/{project}/locations/{location}".format(
+ project=project, location=location,
+ )
+
+ @staticmethod
+ def parse_common_location_path(path: str) -> Dict[str, str]:
+ """Parse a location path into its component segments."""
+ m = re.match(r"^projects/(?P.+?)/locations/(?P.+?)$", path)
+ return m.groupdict() if m else {}
+
+ def __init__(
+ self,
+ *,
+ credentials: Optional[credentials.Credentials] = None,
+ transport: Union[str, BudgetServiceTransport, None] = None,
+ client_options: Optional[client_options_lib.ClientOptions] = None,
+ client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
+ ) -> None:
+ """Instantiate the budget service client.
+
+ Args:
+ credentials (Optional[google.auth.credentials.Credentials]): The
+ authorization credentials to attach to requests. These
+ 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, ~.BudgetServiceTransport]): The
+ transport to use. If set to None, a transport is chosen
+ automatically.
+ client_options (client_options_lib.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
+ environment variable can also be used to override the endpoint:
+ "always" (always use the default mTLS endpoint), "never" (always
+ use the default regular endpoint) and "auto" (auto switch to the
+ default mTLS endpoint if client certificate is present, this is
+ the default value). However, the ``api_endpoint`` property takes
+ precedence if provided.
+ (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
+ is "true", then the ``client_cert_source`` property can be used
+ to provide client certificate for mutual TLS transport. If
+ not provided, the default SSL client certificate will be used if
+ present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
+ set, no client certificate will be used.
+ client_info (google.api_core.gapic_v1.client_info.ClientInfo):
+ The client info used to send a user-agent string along with
+ API requests. If ``None``, then default info will be used.
+ Generally, you only need to set this if you're developing
+ your own client library.
+
+ Raises:
+ google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
+ creation failed for any reason.
+ """
+ if isinstance(client_options, dict):
+ client_options = client_options_lib.from_dict(client_options)
+ if client_options is None:
+ client_options = client_options_lib.ClientOptions()
+
+ # Create SSL credentials for mutual TLS if needed.
+ use_client_cert = bool(
+ util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"))
+ )
+
+ ssl_credentials = 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
+ else:
+ creds = SslCredentials()
+ is_mtls = creds.is_mtls
+ ssl_credentials = creds.ssl_credentials if is_mtls else None
+
+ # Figure out which api endpoint to use.
+ if client_options.api_endpoint is not None:
+ api_endpoint = client_options.api_endpoint
+ else:
+ use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
+ if use_mtls_env == "never":
+ api_endpoint = self.DEFAULT_ENDPOINT
+ elif use_mtls_env == "always":
+ api_endpoint = self.DEFAULT_MTLS_ENDPOINT
+ elif use_mtls_env == "auto":
+ api_endpoint = (
+ self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT
+ )
+ else:
+ raise MutualTLSChannelError(
+ "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always"
+ )
+
+ # Save or instantiate the transport.
+ # Ordinarily, we provide the transport, but allowing a custom transport
+ # instance provides an extensibility point for unusual situations.
+ if isinstance(transport, BudgetServiceTransport):
+ # transport is a BudgetServiceTransport instance.
+ if credentials or client_options.credentials_file:
+ raise ValueError(
+ "When providing a transport instance, "
+ "provide its credentials directly."
+ )
+ if client_options.scopes:
+ raise ValueError(
+ "When providing a transport instance, "
+ "provide its scopes directly."
+ )
+ self._transport = transport
+ else:
+ Transport = type(self).get_transport_class(transport)
+ self._transport = Transport(
+ credentials=credentials,
+ credentials_file=client_options.credentials_file,
+ host=api_endpoint,
+ scopes=client_options.scopes,
+ ssl_channel_credentials=ssl_credentials,
+ quota_project_id=client_options.quota_project_id,
+ client_info=client_info,
+ )
+
+ def create_budget(
+ self,
+ request: budget_service.CreateBudgetRequest = None,
+ *,
+ parent: str = None,
+ budget: budget_model.Budget = None,
+ retry: retries.Retry = gapic_v1.method.DEFAULT,
+ timeout: float = None,
+ metadata: Sequence[Tuple[str, str]] = (),
+ ) -> budget_model.Budget:
+ r"""Creates a new budget. See `Quotas and
+ limits `__ for more
+ information on the limits of the number of budgets you can
+ create.
+
+ Args:
+ request (:class:`~.budget_service.CreateBudgetRequest`):
+ The request object. Request for CreateBudget
+ parent (:class:`str`):
+ Required. The name of the billing account to create the
+ budget in. Values are of the form
+ ``billingAccounts/{billingAccountId}``.
+ This corresponds to the ``parent`` field
+ on the ``request`` instance; if ``request`` is provided, this
+ should not be set.
+ budget (:class:`~.budget_model.Budget`):
+ Required. Budget to create.
+ This corresponds to the ``budget`` field
+ on the ``request`` instance; if ``request`` is provided, this
+ should not be set.
+
+ retry (google.api_core.retry.Retry): Designation of what errors, if any,
+ should be retried.
+ timeout (float): The timeout for this request.
+ metadata (Sequence[Tuple[str, str]]): Strings which should be
+ sent along with the request as metadata.
+
+ Returns:
+ ~.budget_model.Budget:
+ A budget is a plan that describes
+ what you expect to spend on Cloud
+ projects, plus the rules to execute as
+ spend is tracked against that plan, (for
+ example, send an alert when 90% of the
+ target spend is met). Currently all
+ plans are monthly budgets so the usage
+ period(s) tracked are implied (calendar
+ months of usage back-to-back).
+
+ """
+ # Create or coerce a protobuf request object.
+ # Sanity check: If we got a request object, we should *not* have
+ # gotten any keyword arguments that map to the request.
+ has_flattened_params = any([parent, budget])
+ if request is not None and has_flattened_params:
+ raise ValueError(
+ "If the `request` argument is set, then none of "
+ "the individual field arguments should be set."
+ )
+
+ # Minor optimization to avoid making a copy if the user passes
+ # in a budget_service.CreateBudgetRequest.
+ # There's no risk of modifying the input as we've already verified
+ # there are no flattened fields.
+ if not isinstance(request, budget_service.CreateBudgetRequest):
+ request = budget_service.CreateBudgetRequest(request)
+
+ # If we have keyword arguments corresponding to fields on the
+ # request, apply these.
+
+ if parent is not None:
+ request.parent = parent
+ if budget is not None:
+ request.budget = budget
+
+ # Wrap the RPC method; this adds retry and timeout information,
+ # and friendly error handling.
+ rpc = self._transport._wrapped_methods[self._transport.create_budget]
+
+ # Certain fields should be provided within the metadata header;
+ # add these here.
+ metadata = tuple(metadata) + (
+ gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
+ )
+
+ # Send the request.
+ response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,)
+
+ # Done; return the response.
+ return response
+
+ def update_budget(
+ self,
+ request: budget_service.UpdateBudgetRequest = None,
+ *,
+ budget: budget_model.Budget = None,
+ update_mask: field_mask.FieldMask = None,
+ retry: retries.Retry = gapic_v1.method.DEFAULT,
+ timeout: float = None,
+ metadata: Sequence[Tuple[str, str]] = (),
+ ) -> budget_model.Budget:
+ r"""Updates a budget and returns the updated budget.
+ WARNING: There are some fields exposed on the Google
+ Cloud Console that aren't available on this API. Budget
+ fields that are not exposed in this API will not be
+ changed by this method.
+
+ Args:
+ request (:class:`~.budget_service.UpdateBudgetRequest`):
+ The request object. Request for UpdateBudget
+ budget (:class:`~.budget_model.Budget`):
+ Required. The updated budget object.
+ The budget to update is specified by the
+ budget name in the budget.
+ This corresponds to the ``budget`` field
+ on the ``request`` instance; if ``request`` is provided, this
+ should not be set.
+ update_mask (:class:`~.field_mask.FieldMask`):
+ Optional. Indicates which fields in the provided budget
+ to update. Read-only fields (such as ``name``) cannot be
+ changed. If this is not provided, then only fields with
+ non-default values from the request are updated. See
+ https://developers.google.com/protocol-buffers/docs/proto3#default
+ for more details about default values.
+ This corresponds to the ``update_mask`` field
+ on the ``request`` instance; if ``request`` is provided, this
+ should not be set.
+
+ retry (google.api_core.retry.Retry): Designation of what errors, if any,
+ should be retried.
+ timeout (float): The timeout for this request.
+ metadata (Sequence[Tuple[str, str]]): Strings which should be
+ sent along with the request as metadata.
+
+ Returns:
+ ~.budget_model.Budget:
+ A budget is a plan that describes
+ what you expect to spend on Cloud
+ projects, plus the rules to execute as
+ spend is tracked against that plan, (for
+ example, send an alert when 90% of the
+ target spend is met). Currently all
+ plans are monthly budgets so the usage
+ period(s) tracked are implied (calendar
+ months of usage back-to-back).
+
+ """
+ # Create or coerce a protobuf request object.
+ # Sanity check: If we got a request object, we should *not* have
+ # gotten any keyword arguments that map to the request.
+ has_flattened_params = any([budget, update_mask])
+ if request is not None and has_flattened_params:
+ raise ValueError(
+ "If the `request` argument is set, then none of "
+ "the individual field arguments should be set."
+ )
+
+ # Minor optimization to avoid making a copy if the user passes
+ # in a budget_service.UpdateBudgetRequest.
+ # There's no risk of modifying the input as we've already verified
+ # there are no flattened fields.
+ if not isinstance(request, budget_service.UpdateBudgetRequest):
+ request = budget_service.UpdateBudgetRequest(request)
+
+ # If we have keyword arguments corresponding to fields on the
+ # request, apply these.
+
+ if budget is not None:
+ request.budget = budget
+ if update_mask is not None:
+ request.update_mask = update_mask
+
+ # Wrap the RPC method; this adds retry and timeout information,
+ # and friendly error handling.
+ rpc = self._transport._wrapped_methods[self._transport.update_budget]
+
+ # Certain fields should be provided within the metadata header;
+ # add these here.
+ metadata = tuple(metadata) + (
+ gapic_v1.routing_header.to_grpc_metadata(
+ (("budget.name", request.budget.name),)
+ ),
+ )
+
+ # Send the request.
+ response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,)
+
+ # Done; return the response.
+ return response
+
+ def get_budget(
+ self,
+ request: budget_service.GetBudgetRequest = None,
+ *,
+ name: str = None,
+ retry: retries.Retry = gapic_v1.method.DEFAULT,
+ timeout: float = None,
+ metadata: Sequence[Tuple[str, str]] = (),
+ ) -> budget_model.Budget:
+ r"""Returns a budget.
+ WARNING: There are some fields exposed on the Google
+ Cloud Console that aren't available on this API. When
+ reading from the API, you will not see these fields in
+ the return value, though they may have been set in the
+ Cloud Console.
+
+ Args:
+ request (:class:`~.budget_service.GetBudgetRequest`):
+ The request object. Request for GetBudget
+ name (:class:`str`):
+ Required. Name of budget to get. Values are of the form
+ ``billingAccounts/{billingAccountId}/budgets/{budgetId}``.
+ This corresponds to the ``name`` field
+ on the ``request`` instance; if ``request`` is provided, this
+ should not be set.
+
+ retry (google.api_core.retry.Retry): Designation of what errors, if any,
+ should be retried.
+ timeout (float): The timeout for this request.
+ metadata (Sequence[Tuple[str, str]]): Strings which should be
+ sent along with the request as metadata.
+
+ Returns:
+ ~.budget_model.Budget:
+ A budget is a plan that describes
+ what you expect to spend on Cloud
+ projects, plus the rules to execute as
+ spend is tracked against that plan, (for
+ example, send an alert when 90% of the
+ target spend is met). Currently all
+ plans are monthly budgets so the usage
+ period(s) tracked are implied (calendar
+ months of usage back-to-back).
+
+ """
+ # Create or coerce a protobuf request object.
+ # Sanity check: If we got a request object, we should *not* have
+ # gotten any keyword arguments that map to the request.
+ has_flattened_params = any([name])
+ if request is not None and has_flattened_params:
+ raise ValueError(
+ "If the `request` argument is set, then none of "
+ "the individual field arguments should be set."
+ )
+
+ # Minor optimization to avoid making a copy if the user passes
+ # in a budget_service.GetBudgetRequest.
+ # There's no risk of modifying the input as we've already verified
+ # there are no flattened fields.
+ if not isinstance(request, budget_service.GetBudgetRequest):
+ request = budget_service.GetBudgetRequest(request)
+
+ # If we have keyword arguments corresponding to fields on the
+ # request, apply these.
+
+ if name is not None:
+ request.name = name
+
+ # Wrap the RPC method; this adds retry and timeout information,
+ # and friendly error handling.
+ rpc = self._transport._wrapped_methods[self._transport.get_budget]
+
+ # Certain fields should be provided within the metadata header;
+ # add these here.
+ metadata = tuple(metadata) + (
+ gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
+ )
+
+ # Send the request.
+ response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,)
+
+ # Done; return the response.
+ return response
+
+ def list_budgets(
+ self,
+ request: budget_service.ListBudgetsRequest = None,
+ *,
+ parent: str = None,
+ retry: retries.Retry = gapic_v1.method.DEFAULT,
+ timeout: float = None,
+ metadata: Sequence[Tuple[str, str]] = (),
+ ) -> pagers.ListBudgetsPager:
+ r"""Returns a list of budgets for a billing account.
+ WARNING: There are some fields exposed on the Google
+ Cloud Console that aren't available on this API. When
+ reading from the API, you will not see these fields in
+ the return value, though they may have been set in the
+ Cloud Console.
+
+ Args:
+ request (:class:`~.budget_service.ListBudgetsRequest`):
+ The request object. Request for ListBudgets
+ parent (:class:`str`):
+ Required. Name of billing account to list budgets under.
+ Values are of the form
+ ``billingAccounts/{billingAccountId}``.
+ This corresponds to the ``parent`` field
+ on the ``request`` instance; if ``request`` is provided, this
+ should not be set.
+
+ retry (google.api_core.retry.Retry): Designation of what errors, if any,
+ should be retried.
+ timeout (float): The timeout for this request.
+ metadata (Sequence[Tuple[str, str]]): Strings which should be
+ sent along with the request as metadata.
+
+ Returns:
+ ~.pagers.ListBudgetsPager:
+ Response for ListBudgets
+ Iterating over this object will yield
+ results and resolve additional pages
+ automatically.
+
+ """
+ # Create or coerce a protobuf request object.
+ # Sanity check: If we got a request object, we should *not* have
+ # gotten any keyword arguments that map to the request.
+ has_flattened_params = any([parent])
+ if request is not None and has_flattened_params:
+ raise ValueError(
+ "If the `request` argument is set, then none of "
+ "the individual field arguments should be set."
+ )
+
+ # Minor optimization to avoid making a copy if the user passes
+ # in a budget_service.ListBudgetsRequest.
+ # There's no risk of modifying the input as we've already verified
+ # there are no flattened fields.
+ if not isinstance(request, budget_service.ListBudgetsRequest):
+ request = budget_service.ListBudgetsRequest(request)
+
+ # If we have keyword arguments corresponding to fields on the
+ # request, apply these.
+
+ if parent is not None:
+ request.parent = parent
+
+ # Wrap the RPC method; this adds retry and timeout information,
+ # and friendly error handling.
+ rpc = self._transport._wrapped_methods[self._transport.list_budgets]
+
+ # Certain fields should be provided within the metadata header;
+ # add these here.
+ metadata = tuple(metadata) + (
+ gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)),
+ )
+
+ # Send the request.
+ response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,)
+
+ # This method is paged; wrap the response in a pager, which provides
+ # an `__iter__` convenience method.
+ response = pagers.ListBudgetsPager(
+ method=rpc, request=request, response=response, metadata=metadata,
+ )
+
+ # Done; return the response.
+ return response
+
+ def delete_budget(
+ self,
+ request: budget_service.DeleteBudgetRequest = None,
+ *,
+ name: str = None,
+ retry: retries.Retry = gapic_v1.method.DEFAULT,
+ timeout: float = None,
+ metadata: Sequence[Tuple[str, str]] = (),
+ ) -> None:
+ r"""Deletes a budget. Returns successfully if already
+ deleted.
+
+ Args:
+ request (:class:`~.budget_service.DeleteBudgetRequest`):
+ The request object. Request for DeleteBudget
+ name (:class:`str`):
+ Required. Name of the budget to delete. Values are of
+ the form
+ ``billingAccounts/{billingAccountId}/budgets/{budgetId}``.
+ This corresponds to the ``name`` field
+ on the ``request`` instance; if ``request`` is provided, this
+ should not be set.
+
+ retry (google.api_core.retry.Retry): Designation of what errors, if any,
+ should be retried.
+ timeout (float): The timeout for this request.
+ metadata (Sequence[Tuple[str, str]]): Strings which should be
+ sent along with the request as metadata.
+ """
+ # Create or coerce a protobuf request object.
+ # Sanity check: If we got a request object, we should *not* have
+ # gotten any keyword arguments that map to the request.
+ has_flattened_params = any([name])
+ if request is not None and has_flattened_params:
+ raise ValueError(
+ "If the `request` argument is set, then none of "
+ "the individual field arguments should be set."
+ )
+
+ # Minor optimization to avoid making a copy if the user passes
+ # in a budget_service.DeleteBudgetRequest.
+ # There's no risk of modifying the input as we've already verified
+ # there are no flattened fields.
+ if not isinstance(request, budget_service.DeleteBudgetRequest):
+ request = budget_service.DeleteBudgetRequest(request)
+
+ # If we have keyword arguments corresponding to fields on the
+ # request, apply these.
+
+ if name is not None:
+ request.name = name
+
+ # Wrap the RPC method; this adds retry and timeout information,
+ # and friendly error handling.
+ rpc = self._transport._wrapped_methods[self._transport.delete_budget]
+
+ # Certain fields should be provided within the metadata header;
+ # add these here.
+ metadata = tuple(metadata) + (
+ gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
+ )
+
+ # Send the request.
+ rpc(
+ request, retry=retry, timeout=timeout, metadata=metadata,
+ )
+
+
+try:
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
+ gapic_version=pkg_resources.get_distribution(
+ "google-cloud-billing-budgets",
+ ).version,
+ )
+except pkg_resources.DistributionNotFound:
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo()
+
+
+__all__ = ("BudgetServiceClient",)
diff --git a/google/cloud/billing/budgets_v1/services/budget_service/pagers.py b/google/cloud/billing/budgets_v1/services/budget_service/pagers.py
new file mode 100644
index 0000000..4e3a453
--- /dev/null
+++ b/google/cloud/billing/budgets_v1/services/budget_service/pagers.py
@@ -0,0 +1,149 @@
+# -*- 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.
+#
+
+from typing import Any, AsyncIterable, Awaitable, Callable, Iterable, Sequence, Tuple
+
+from google.cloud.billing.budgets_v1.types import budget_model
+from google.cloud.billing.budgets_v1.types import budget_service
+
+
+class ListBudgetsPager:
+ """A pager for iterating through ``list_budgets`` requests.
+
+ This class thinly wraps an initial
+ :class:`~.budget_service.ListBudgetsResponse` object, and
+ provides an ``__iter__`` method to iterate through its
+ ``budgets`` field.
+
+ If there are more pages, the ``__iter__`` method will make additional
+ ``ListBudgets`` requests and continue to iterate
+ through the ``budgets`` field on the
+ corresponding responses.
+
+ All the usual :class:`~.budget_service.ListBudgetsResponse`
+ attributes are available on the pager. If multiple requests are made, only
+ the most recent response is retained, and thus used for attribute lookup.
+ """
+
+ def __init__(
+ self,
+ method: Callable[..., budget_service.ListBudgetsResponse],
+ request: budget_service.ListBudgetsRequest,
+ response: budget_service.ListBudgetsResponse,
+ *,
+ metadata: Sequence[Tuple[str, str]] = ()
+ ):
+ """Instantiate the pager.
+
+ Args:
+ method (Callable): The method that was originally called, and
+ which instantiated this pager.
+ request (:class:`~.budget_service.ListBudgetsRequest`):
+ The initial request object.
+ response (:class:`~.budget_service.ListBudgetsResponse`):
+ The initial response object.
+ metadata (Sequence[Tuple[str, str]]): Strings which should be
+ sent along with the request as metadata.
+ """
+ self._method = method
+ self._request = budget_service.ListBudgetsRequest(request)
+ self._response = response
+ self._metadata = metadata
+
+ def __getattr__(self, name: str) -> Any:
+ return getattr(self._response, name)
+
+ @property
+ def pages(self) -> Iterable[budget_service.ListBudgetsResponse]:
+ yield self._response
+ while self._response.next_page_token:
+ self._request.page_token = self._response.next_page_token
+ self._response = self._method(self._request, metadata=self._metadata)
+ yield self._response
+
+ def __iter__(self) -> Iterable[budget_model.Budget]:
+ for page in self.pages:
+ yield from page.budgets
+
+ def __repr__(self) -> str:
+ return "{0}<{1!r}>".format(self.__class__.__name__, self._response)
+
+
+class ListBudgetsAsyncPager:
+ """A pager for iterating through ``list_budgets`` requests.
+
+ This class thinly wraps an initial
+ :class:`~.budget_service.ListBudgetsResponse` object, and
+ provides an ``__aiter__`` method to iterate through its
+ ``budgets`` field.
+
+ If there are more pages, the ``__aiter__`` method will make additional
+ ``ListBudgets`` requests and continue to iterate
+ through the ``budgets`` field on the
+ corresponding responses.
+
+ All the usual :class:`~.budget_service.ListBudgetsResponse`
+ attributes are available on the pager. If multiple requests are made, only
+ the most recent response is retained, and thus used for attribute lookup.
+ """
+
+ def __init__(
+ self,
+ method: Callable[..., Awaitable[budget_service.ListBudgetsResponse]],
+ request: budget_service.ListBudgetsRequest,
+ response: budget_service.ListBudgetsResponse,
+ *,
+ metadata: Sequence[Tuple[str, str]] = ()
+ ):
+ """Instantiate the pager.
+
+ Args:
+ method (Callable): The method that was originally called, and
+ which instantiated this pager.
+ request (:class:`~.budget_service.ListBudgetsRequest`):
+ The initial request object.
+ response (:class:`~.budget_service.ListBudgetsResponse`):
+ The initial response object.
+ metadata (Sequence[Tuple[str, str]]): Strings which should be
+ sent along with the request as metadata.
+ """
+ self._method = method
+ self._request = budget_service.ListBudgetsRequest(request)
+ self._response = response
+ self._metadata = metadata
+
+ def __getattr__(self, name: str) -> Any:
+ return getattr(self._response, name)
+
+ @property
+ async def pages(self) -> AsyncIterable[budget_service.ListBudgetsResponse]:
+ yield self._response
+ while self._response.next_page_token:
+ self._request.page_token = self._response.next_page_token
+ self._response = await self._method(self._request, metadata=self._metadata)
+ yield self._response
+
+ def __aiter__(self) -> AsyncIterable[budget_model.Budget]:
+ async def async_generator():
+ async for page in self.pages:
+ for response in page.budgets:
+ yield response
+
+ return async_generator()
+
+ def __repr__(self) -> str:
+ return "{0}<{1!r}>".format(self.__class__.__name__, self._response)
diff --git a/google/cloud/billing/budgets_v1/services/budget_service/transports/__init__.py b/google/cloud/billing/budgets_v1/services/budget_service/transports/__init__.py
new file mode 100644
index 0000000..8f54d9e
--- /dev/null
+++ b/google/cloud/billing/budgets_v1/services/budget_service/transports/__init__.py
@@ -0,0 +1,36 @@
+# -*- 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.
+#
+
+from collections import OrderedDict
+from typing import Dict, Type
+
+from .base import BudgetServiceTransport
+from .grpc import BudgetServiceGrpcTransport
+from .grpc_asyncio import BudgetServiceGrpcAsyncIOTransport
+
+
+# Compile a registry of transports.
+_transport_registry = OrderedDict() # type: Dict[str, Type[BudgetServiceTransport]]
+_transport_registry["grpc"] = BudgetServiceGrpcTransport
+_transport_registry["grpc_asyncio"] = BudgetServiceGrpcAsyncIOTransport
+
+
+__all__ = (
+ "BudgetServiceTransport",
+ "BudgetServiceGrpcTransport",
+ "BudgetServiceGrpcAsyncIOTransport",
+)
diff --git a/google/cloud/billing/budgets_v1/services/budget_service/transports/base.py b/google/cloud/billing/budgets_v1/services/budget_service/transports/base.py
new file mode 100644
index 0000000..f0460b6
--- /dev/null
+++ b/google/cloud/billing/budgets_v1/services/budget_service/transports/base.py
@@ -0,0 +1,220 @@
+# -*- 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.
+#
+
+import abc
+import typing
+import pkg_resources
+
+from google import auth # type: ignore
+from google.api_core import exceptions # type: ignore
+from google.api_core import gapic_v1 # type: ignore
+from google.api_core import retry as retries # type: ignore
+from google.auth import credentials # type: ignore
+
+from google.cloud.billing.budgets_v1.types import budget_model
+from google.cloud.billing.budgets_v1.types import budget_service
+from google.protobuf import empty_pb2 as empty # type: ignore
+
+
+try:
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
+ gapic_version=pkg_resources.get_distribution(
+ "google-cloud-billing-budgets",
+ ).version,
+ )
+except pkg_resources.DistributionNotFound:
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo()
+
+
+class BudgetServiceTransport(abc.ABC):
+ """Abstract transport class for BudgetService."""
+
+ AUTH_SCOPES = (
+ "https://www.googleapis.com/auth/cloud-billing",
+ "https://www.googleapis.com/auth/cloud-platform",
+ )
+
+ def __init__(
+ self,
+ *,
+ host: str = "billingbudgets.googleapis.com",
+ credentials: credentials.Credentials = None,
+ credentials_file: typing.Optional[str] = None,
+ scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES,
+ quota_project_id: typing.Optional[str] = None,
+ client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
+ **kwargs,
+ ) -> None:
+ """Instantiate the transport.
+
+ Args:
+ host (Optional[str]): The hostname to connect to.
+ credentials (Optional[google.auth.credentials.Credentials]): The
+ authorization credentials to attach to requests. These
+ credentials identify the application to the service; if none
+ are specified, the client will attempt to ascertain the
+ credentials from the environment.
+ credentials_file (Optional[str]): A file with credentials that can
+ be loaded with :func:`google.auth.load_credentials_from_file`.
+ This argument is mutually exclusive with credentials.
+ 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
+ your own client library.
+ """
+ # Save the hostname. Default to port 443 (HTTPS) if none is specified.
+ if ":" not in host:
+ host += ":443"
+ self._host = host
+
+ # If no credentials are provided, then determine the appropriate
+ # defaults.
+ if credentials and credentials_file:
+ raise exceptions.DuplicateCredentialArgs(
+ "'credentials_file' and 'credentials' are mutually exclusive"
+ )
+
+ if credentials_file is not None:
+ credentials, _ = auth.load_credentials_from_file(
+ credentials_file, scopes=scopes, quota_project_id=quota_project_id
+ )
+
+ elif credentials is None:
+ credentials, _ = auth.default(
+ scopes=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 = {
+ self.create_budget: gapic_v1.method.wrap_method(
+ self.create_budget, default_timeout=60.0, client_info=client_info,
+ ),
+ self.update_budget: gapic_v1.method.wrap_method(
+ self.update_budget,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.DeadlineExceeded, exceptions.ServiceUnavailable,
+ ),
+ ),
+ default_timeout=60.0,
+ client_info=client_info,
+ ),
+ self.get_budget: gapic_v1.method.wrap_method(
+ self.get_budget,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.DeadlineExceeded, exceptions.ServiceUnavailable,
+ ),
+ ),
+ default_timeout=60.0,
+ client_info=client_info,
+ ),
+ self.list_budgets: gapic_v1.method.wrap_method(
+ self.list_budgets,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.DeadlineExceeded, exceptions.ServiceUnavailable,
+ ),
+ ),
+ default_timeout=60.0,
+ client_info=client_info,
+ ),
+ self.delete_budget: gapic_v1.method.wrap_method(
+ self.delete_budget,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.DeadlineExceeded, exceptions.ServiceUnavailable,
+ ),
+ ),
+ default_timeout=60.0,
+ client_info=client_info,
+ ),
+ }
+
+ @property
+ def create_budget(
+ self,
+ ) -> typing.Callable[
+ [budget_service.CreateBudgetRequest],
+ typing.Union[budget_model.Budget, typing.Awaitable[budget_model.Budget]],
+ ]:
+ raise NotImplementedError()
+
+ @property
+ def update_budget(
+ self,
+ ) -> typing.Callable[
+ [budget_service.UpdateBudgetRequest],
+ typing.Union[budget_model.Budget, typing.Awaitable[budget_model.Budget]],
+ ]:
+ raise NotImplementedError()
+
+ @property
+ def get_budget(
+ self,
+ ) -> typing.Callable[
+ [budget_service.GetBudgetRequest],
+ typing.Union[budget_model.Budget, typing.Awaitable[budget_model.Budget]],
+ ]:
+ raise NotImplementedError()
+
+ @property
+ def list_budgets(
+ self,
+ ) -> typing.Callable[
+ [budget_service.ListBudgetsRequest],
+ typing.Union[
+ budget_service.ListBudgetsResponse,
+ typing.Awaitable[budget_service.ListBudgetsResponse],
+ ],
+ ]:
+ raise NotImplementedError()
+
+ @property
+ def delete_budget(
+ self,
+ ) -> typing.Callable[
+ [budget_service.DeleteBudgetRequest],
+ typing.Union[empty.Empty, typing.Awaitable[empty.Empty]],
+ ]:
+ raise NotImplementedError()
+
+
+__all__ = ("BudgetServiceTransport",)
diff --git a/google/cloud/billing/budgets_v1/services/budget_service/transports/grpc.py b/google/cloud/billing/budgets_v1/services/budget_service/transports/grpc.py
new file mode 100644
index 0000000..ec12f7c
--- /dev/null
+++ b/google/cloud/billing/budgets_v1/services/budget_service/transports/grpc.py
@@ -0,0 +1,387 @@
+# -*- 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.
+#
+
+import warnings
+from typing import Callable, Dict, Optional, Sequence, Tuple
+
+from google.api_core import grpc_helpers # type: ignore
+from google.api_core import gapic_v1 # type: ignore
+from google import auth # type: ignore
+from google.auth import credentials # type: ignore
+from google.auth.transport.grpc import SslCredentials # type: ignore
+
+import grpc # type: ignore
+
+from google.cloud.billing.budgets_v1.types import budget_model
+from google.cloud.billing.budgets_v1.types import budget_service
+from google.protobuf import empty_pb2 as empty # type: ignore
+
+from .base import BudgetServiceTransport, DEFAULT_CLIENT_INFO
+
+
+class BudgetServiceGrpcTransport(BudgetServiceTransport):
+ """gRPC backend transport for BudgetService.
+
+ BudgetService stores Cloud Billing budgets, which define a
+ budget plan and rules to execute as we track spend against that
+ plan.
+
+ This class defines the same methods as the primary client, so the
+ primary client can load the underlying transport implementation
+ and call it.
+
+ It sends protocol buffers over the wire using gRPC (which is built on
+ top of HTTP/2); the ``grpcio`` package must be installed.
+ """
+
+ _stubs: Dict[str, Callable]
+
+ def __init__(
+ self,
+ *,
+ host: str = "billingbudgets.googleapis.com",
+ credentials: credentials.Credentials = None,
+ credentials_file: str = None,
+ scopes: Sequence[str] = None,
+ channel: grpc.Channel = None,
+ api_mtls_endpoint: str = None,
+ client_cert_source: Callable[[], Tuple[bytes, bytes]] = None,
+ ssl_channel_credentials: grpc.ChannelCredentials = None,
+ quota_project_id: Optional[str] = None,
+ client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
+ ) -> None:
+ """Instantiate the transport.
+
+ Args:
+ host (Optional[str]): The hostname to connect to.
+ credentials (Optional[google.auth.credentials.Credentials]): The
+ authorization credentials to attach to requests. These
+ credentials identify the application to the service; if none
+ are specified, the client will attempt to ascertain the
+ credentials from the environment.
+ This argument is ignored if ``channel`` is provided.
+ credentials_file (Optional[str]): A file with credentials that can
+ be loaded with :func:`google.auth.load_credentials_from_file`.
+ This argument is ignored if ``channel`` is provided.
+ scopes (Optional(Sequence[str])): A list of scopes. This argument is
+ ignored if ``channel`` is provided.
+ channel (Optional[grpc.Channel]): A ``Channel`` instance through
+ which to make calls.
+ api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
+ If provided, it overrides the ``host`` argument and tries to create
+ a mutual TLS channel with client SSL credentials from
+ ``client_cert_source`` or applicatin default SSL credentials.
+ client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
+ Deprecated. A callback to provide client SSL certificate bytes and
+ private key bytes, both in PEM format. It is ignored if
+ ``api_mtls_endpoint`` is None.
+ ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
+ for grpc channel. It is ignored if ``channel`` is provided.
+ 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
+ your own client library.
+
+ Raises:
+ google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
+ creation failed for any reason.
+ google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
+ and ``credentials_file`` are passed.
+ """
+ self._ssl_channel_credentials = ssl_channel_credentials
+
+ if channel:
+ # Sanity check: Ensure that channel and credentials are not both
+ # provided.
+ 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"
+ )
+
+ if credentials is None:
+ credentials, _ = auth.default(
+ scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id
+ )
+
+ # Create SSL credentials with client_cert_source or application
+ # default SSL credentials.
+ if client_cert_source:
+ cert, key = client_cert_source()
+ ssl_credentials = grpc.ssl_channel_credentials(
+ certificate_chain=cert, private_key=key
+ )
+ else:
+ ssl_credentials = SslCredentials().ssl_credentials
+
+ # create a new channel. The provided one is ignored.
+ self._grpc_channel = type(self).create_channel(
+ host,
+ credentials=credentials,
+ credentials_file=credentials_file,
+ ssl_credentials=ssl_credentials,
+ scopes=scopes or self.AUTH_SCOPES,
+ quota_project_id=quota_project_id,
+ )
+ self._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
+ )
+
+ # 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_channel_credentials,
+ scopes=scopes or self.AUTH_SCOPES,
+ quota_project_id=quota_project_id,
+ )
+
+ 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,
+ )
+
+ @classmethod
+ def create_channel(
+ cls,
+ host: str = "billingbudgets.googleapis.com",
+ credentials: credentials.Credentials = None,
+ credentials_file: str = None,
+ scopes: Optional[Sequence[str]] = None,
+ quota_project_id: Optional[str] = None,
+ **kwargs,
+ ) -> grpc.Channel:
+ """Create and return a gRPC channel object.
+ Args:
+ address (Optionsl[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
+ none are specified, the client will attempt to ascertain
+ the credentials from the environment.
+ credentials_file (Optional[str]): A file with credentials that can
+ be loaded with :func:`google.auth.load_credentials_from_file`.
+ This argument is mutually exclusive with credentials.
+ scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
+ service. These are only used when credentials are not specified and
+ are passed to :func:`google.auth.default`.
+ quota_project_id (Optional[str]): An optional project to use for billing
+ and quota.
+ kwargs (Optional[dict]): Keyword arguments, which are passed to the
+ channel creation.
+ Returns:
+ grpc.Channel: A gRPC channel object.
+
+ Raises:
+ google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
+ and ``credentials_file`` are passed.
+ """
+ scopes = scopes or cls.AUTH_SCOPES
+ return grpc_helpers.create_channel(
+ host,
+ credentials=credentials,
+ credentials_file=credentials_file,
+ scopes=scopes,
+ quota_project_id=quota_project_id,
+ **kwargs,
+ )
+
+ @property
+ def grpc_channel(self) -> grpc.Channel:
+ """Return the channel designed to connect to this service.
+ """
+ return self._grpc_channel
+
+ @property
+ def create_budget(
+ self,
+ ) -> Callable[[budget_service.CreateBudgetRequest], budget_model.Budget]:
+ r"""Return a callable for the create budget method over gRPC.
+
+ Creates a new budget. See `Quotas and
+ limits `__ for more
+ information on the limits of the number of budgets you can
+ create.
+
+ Returns:
+ Callable[[~.CreateBudgetRequest],
+ ~.Budget]:
+ A function that, when called, will call the underlying RPC
+ on the server.
+ """
+ # Generate a "stub function" on-the-fly which will actually make
+ # the request.
+ # gRPC handles serialization and deserialization, so we just need
+ # to pass in the functions for each.
+ if "create_budget" not in self._stubs:
+ self._stubs["create_budget"] = self.grpc_channel.unary_unary(
+ "/google.cloud.billing.budgets.v1.BudgetService/CreateBudget",
+ request_serializer=budget_service.CreateBudgetRequest.serialize,
+ response_deserializer=budget_model.Budget.deserialize,
+ )
+ return self._stubs["create_budget"]
+
+ @property
+ def update_budget(
+ self,
+ ) -> Callable[[budget_service.UpdateBudgetRequest], budget_model.Budget]:
+ r"""Return a callable for the update budget method over gRPC.
+
+ Updates a budget and returns the updated budget.
+ WARNING: There are some fields exposed on the Google
+ Cloud Console that aren't available on this API. Budget
+ fields that are not exposed in this API will not be
+ changed by this method.
+
+ Returns:
+ Callable[[~.UpdateBudgetRequest],
+ ~.Budget]:
+ A function that, when called, will call the underlying RPC
+ on the server.
+ """
+ # Generate a "stub function" on-the-fly which will actually make
+ # the request.
+ # gRPC handles serialization and deserialization, so we just need
+ # to pass in the functions for each.
+ if "update_budget" not in self._stubs:
+ self._stubs["update_budget"] = self.grpc_channel.unary_unary(
+ "/google.cloud.billing.budgets.v1.BudgetService/UpdateBudget",
+ request_serializer=budget_service.UpdateBudgetRequest.serialize,
+ response_deserializer=budget_model.Budget.deserialize,
+ )
+ return self._stubs["update_budget"]
+
+ @property
+ def get_budget(
+ self,
+ ) -> Callable[[budget_service.GetBudgetRequest], budget_model.Budget]:
+ r"""Return a callable for the get budget method over gRPC.
+
+ Returns a budget.
+ WARNING: There are some fields exposed on the Google
+ Cloud Console that aren't available on this API. When
+ reading from the API, you will not see these fields in
+ the return value, though they may have been set in the
+ Cloud Console.
+
+ Returns:
+ Callable[[~.GetBudgetRequest],
+ ~.Budget]:
+ A function that, when called, will call the underlying RPC
+ on the server.
+ """
+ # Generate a "stub function" on-the-fly which will actually make
+ # the request.
+ # gRPC handles serialization and deserialization, so we just need
+ # to pass in the functions for each.
+ if "get_budget" not in self._stubs:
+ self._stubs["get_budget"] = self.grpc_channel.unary_unary(
+ "/google.cloud.billing.budgets.v1.BudgetService/GetBudget",
+ request_serializer=budget_service.GetBudgetRequest.serialize,
+ response_deserializer=budget_model.Budget.deserialize,
+ )
+ return self._stubs["get_budget"]
+
+ @property
+ def list_budgets(
+ self,
+ ) -> Callable[
+ [budget_service.ListBudgetsRequest], budget_service.ListBudgetsResponse
+ ]:
+ r"""Return a callable for the list budgets method over gRPC.
+
+ Returns a list of budgets for a billing account.
+ WARNING: There are some fields exposed on the Google
+ Cloud Console that aren't available on this API. When
+ reading from the API, you will not see these fields in
+ the return value, though they may have been set in the
+ Cloud Console.
+
+ Returns:
+ Callable[[~.ListBudgetsRequest],
+ ~.ListBudgetsResponse]:
+ A function that, when called, will call the underlying RPC
+ on the server.
+ """
+ # Generate a "stub function" on-the-fly which will actually make
+ # the request.
+ # gRPC handles serialization and deserialization, so we just need
+ # to pass in the functions for each.
+ if "list_budgets" not in self._stubs:
+ self._stubs["list_budgets"] = self.grpc_channel.unary_unary(
+ "/google.cloud.billing.budgets.v1.BudgetService/ListBudgets",
+ request_serializer=budget_service.ListBudgetsRequest.serialize,
+ response_deserializer=budget_service.ListBudgetsResponse.deserialize,
+ )
+ return self._stubs["list_budgets"]
+
+ @property
+ def delete_budget(
+ self,
+ ) -> Callable[[budget_service.DeleteBudgetRequest], empty.Empty]:
+ r"""Return a callable for the delete budget method over gRPC.
+
+ Deletes a budget. Returns successfully if already
+ deleted.
+
+ Returns:
+ Callable[[~.DeleteBudgetRequest],
+ ~.Empty]:
+ A function that, when called, will call the underlying RPC
+ on the server.
+ """
+ # Generate a "stub function" on-the-fly which will actually make
+ # the request.
+ # gRPC handles serialization and deserialization, so we just need
+ # to pass in the functions for each.
+ if "delete_budget" not in self._stubs:
+ self._stubs["delete_budget"] = self.grpc_channel.unary_unary(
+ "/google.cloud.billing.budgets.v1.BudgetService/DeleteBudget",
+ request_serializer=budget_service.DeleteBudgetRequest.serialize,
+ response_deserializer=empty.Empty.FromString,
+ )
+ return self._stubs["delete_budget"]
+
+
+__all__ = ("BudgetServiceGrpcTransport",)
diff --git a/google/cloud/billing/budgets_v1/services/budget_service/transports/grpc_asyncio.py b/google/cloud/billing/budgets_v1/services/budget_service/transports/grpc_asyncio.py
new file mode 100644
index 0000000..4a007b7
--- /dev/null
+++ b/google/cloud/billing/budgets_v1/services/budget_service/transports/grpc_asyncio.py
@@ -0,0 +1,392 @@
+# -*- 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.
+#
+
+import warnings
+from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple
+
+from google.api_core import gapic_v1 # type: ignore
+from google.api_core import grpc_helpers_async # type: ignore
+from google import auth # type: ignore
+from google.auth import credentials # type: ignore
+from google.auth.transport.grpc import SslCredentials # type: ignore
+
+import grpc # type: ignore
+from grpc.experimental import aio # type: ignore
+
+from google.cloud.billing.budgets_v1.types import budget_model
+from google.cloud.billing.budgets_v1.types import budget_service
+from google.protobuf import empty_pb2 as empty # type: ignore
+
+from .base import BudgetServiceTransport, DEFAULT_CLIENT_INFO
+from .grpc import BudgetServiceGrpcTransport
+
+
+class BudgetServiceGrpcAsyncIOTransport(BudgetServiceTransport):
+ """gRPC AsyncIO backend transport for BudgetService.
+
+ BudgetService stores Cloud Billing budgets, which define a
+ budget plan and rules to execute as we track spend against that
+ plan.
+
+ This class defines the same methods as the primary client, so the
+ primary client can load the underlying transport implementation
+ and call it.
+
+ It sends protocol buffers over the wire using gRPC (which is built on
+ top of HTTP/2); the ``grpcio`` package must be installed.
+ """
+
+ _grpc_channel: aio.Channel
+ _stubs: Dict[str, Callable] = {}
+
+ @classmethod
+ def create_channel(
+ cls,
+ host: str = "billingbudgets.googleapis.com",
+ credentials: credentials.Credentials = None,
+ credentials_file: Optional[str] = None,
+ scopes: Optional[Sequence[str]] = None,
+ quota_project_id: Optional[str] = None,
+ **kwargs,
+ ) -> aio.Channel:
+ """Create and return a gRPC AsyncIO channel object.
+ Args:
+ address (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
+ none are specified, the client will attempt to ascertain
+ the credentials from the environment.
+ credentials_file (Optional[str]): A file with credentials that can
+ be loaded with :func:`google.auth.load_credentials_from_file`.
+ This argument is ignored if ``channel`` is provided.
+ scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
+ service. These are only used when credentials are not specified and
+ are passed to :func:`google.auth.default`.
+ quota_project_id (Optional[str]): An optional project to use for billing
+ and quota.
+ kwargs (Optional[dict]): Keyword arguments, which are passed to the
+ channel creation.
+ Returns:
+ aio.Channel: A gRPC AsyncIO channel object.
+ """
+ scopes = scopes or cls.AUTH_SCOPES
+ return grpc_helpers_async.create_channel(
+ host,
+ credentials=credentials,
+ credentials_file=credentials_file,
+ scopes=scopes,
+ quota_project_id=quota_project_id,
+ **kwargs,
+ )
+
+ def __init__(
+ self,
+ *,
+ host: str = "billingbudgets.googleapis.com",
+ credentials: credentials.Credentials = None,
+ credentials_file: Optional[str] = None,
+ scopes: Optional[Sequence[str]] = None,
+ channel: aio.Channel = None,
+ api_mtls_endpoint: str = None,
+ client_cert_source: Callable[[], Tuple[bytes, bytes]] = None,
+ ssl_channel_credentials: grpc.ChannelCredentials = None,
+ quota_project_id=None,
+ client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
+ ) -> None:
+ """Instantiate the transport.
+
+ Args:
+ host (Optional[str]): The hostname to connect to.
+ credentials (Optional[google.auth.credentials.Credentials]): The
+ authorization credentials to attach to requests. These
+ credentials identify the application to the service; if none
+ are specified, the client will attempt to ascertain the
+ credentials from the environment.
+ This argument is ignored if ``channel`` is provided.
+ credentials_file (Optional[str]): A file with credentials that can
+ be loaded with :func:`google.auth.load_credentials_from_file`.
+ This argument is ignored if ``channel`` is provided.
+ scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
+ service. These are only used when credentials are not specified and
+ are passed to :func:`google.auth.default`.
+ channel (Optional[aio.Channel]): A ``Channel`` instance through
+ which to make calls.
+ api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
+ If provided, it overrides the ``host`` argument and tries to create
+ a mutual TLS channel with client SSL credentials from
+ ``client_cert_source`` or applicatin default SSL credentials.
+ client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
+ Deprecated. A callback to provide client SSL certificate bytes and
+ private key bytes, both in PEM format. It is ignored if
+ ``api_mtls_endpoint`` is None.
+ ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
+ for grpc channel. It is ignored if ``channel`` is provided.
+ 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
+ your own client library.
+
+ Raises:
+ google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
+ creation failed for any reason.
+ google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
+ and ``credentials_file`` are passed.
+ """
+ self._ssl_channel_credentials = ssl_channel_credentials
+
+ if channel:
+ # Sanity check: Ensure that channel and credentials are not both
+ # provided.
+ 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"
+ )
+
+ if credentials is None:
+ credentials, _ = auth.default(
+ scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id
+ )
+
+ # Create SSL credentials with client_cert_source or application
+ # default SSL credentials.
+ if client_cert_source:
+ cert, key = client_cert_source()
+ ssl_credentials = grpc.ssl_channel_credentials(
+ certificate_chain=cert, private_key=key
+ )
+ else:
+ ssl_credentials = SslCredentials().ssl_credentials
+
+ # create a new channel. The provided one is ignored.
+ self._grpc_channel = type(self).create_channel(
+ host,
+ credentials=credentials,
+ credentials_file=credentials_file,
+ ssl_credentials=ssl_credentials,
+ scopes=scopes or self.AUTH_SCOPES,
+ quota_project_id=quota_project_id,
+ )
+ self._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
+ )
+
+ # 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_channel_credentials,
+ scopes=scopes or self.AUTH_SCOPES,
+ quota_project_id=quota_project_id,
+ )
+
+ # 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 = {}
+
+ @property
+ def grpc_channel(self) -> aio.Channel:
+ """Create the channel designed to connect to this service.
+
+ This property caches on the instance; repeated calls return
+ the same channel.
+ """
+ # Return the channel from cache.
+ return self._grpc_channel
+
+ @property
+ def create_budget(
+ self,
+ ) -> Callable[[budget_service.CreateBudgetRequest], Awaitable[budget_model.Budget]]:
+ r"""Return a callable for the create budget method over gRPC.
+
+ Creates a new budget. See `Quotas and
+ limits `__ for more
+ information on the limits of the number of budgets you can
+ create.
+
+ Returns:
+ Callable[[~.CreateBudgetRequest],
+ Awaitable[~.Budget]]:
+ A function that, when called, will call the underlying RPC
+ on the server.
+ """
+ # Generate a "stub function" on-the-fly which will actually make
+ # the request.
+ # gRPC handles serialization and deserialization, so we just need
+ # to pass in the functions for each.
+ if "create_budget" not in self._stubs:
+ self._stubs["create_budget"] = self.grpc_channel.unary_unary(
+ "/google.cloud.billing.budgets.v1.BudgetService/CreateBudget",
+ request_serializer=budget_service.CreateBudgetRequest.serialize,
+ response_deserializer=budget_model.Budget.deserialize,
+ )
+ return self._stubs["create_budget"]
+
+ @property
+ def update_budget(
+ self,
+ ) -> Callable[[budget_service.UpdateBudgetRequest], Awaitable[budget_model.Budget]]:
+ r"""Return a callable for the update budget method over gRPC.
+
+ Updates a budget and returns the updated budget.
+ WARNING: There are some fields exposed on the Google
+ Cloud Console that aren't available on this API. Budget
+ fields that are not exposed in this API will not be
+ changed by this method.
+
+ Returns:
+ Callable[[~.UpdateBudgetRequest],
+ Awaitable[~.Budget]]:
+ A function that, when called, will call the underlying RPC
+ on the server.
+ """
+ # Generate a "stub function" on-the-fly which will actually make
+ # the request.
+ # gRPC handles serialization and deserialization, so we just need
+ # to pass in the functions for each.
+ if "update_budget" not in self._stubs:
+ self._stubs["update_budget"] = self.grpc_channel.unary_unary(
+ "/google.cloud.billing.budgets.v1.BudgetService/UpdateBudget",
+ request_serializer=budget_service.UpdateBudgetRequest.serialize,
+ response_deserializer=budget_model.Budget.deserialize,
+ )
+ return self._stubs["update_budget"]
+
+ @property
+ def get_budget(
+ self,
+ ) -> Callable[[budget_service.GetBudgetRequest], Awaitable[budget_model.Budget]]:
+ r"""Return a callable for the get budget method over gRPC.
+
+ Returns a budget.
+ WARNING: There are some fields exposed on the Google
+ Cloud Console that aren't available on this API. When
+ reading from the API, you will not see these fields in
+ the return value, though they may have been set in the
+ Cloud Console.
+
+ Returns:
+ Callable[[~.GetBudgetRequest],
+ Awaitable[~.Budget]]:
+ A function that, when called, will call the underlying RPC
+ on the server.
+ """
+ # Generate a "stub function" on-the-fly which will actually make
+ # the request.
+ # gRPC handles serialization and deserialization, so we just need
+ # to pass in the functions for each.
+ if "get_budget" not in self._stubs:
+ self._stubs["get_budget"] = self.grpc_channel.unary_unary(
+ "/google.cloud.billing.budgets.v1.BudgetService/GetBudget",
+ request_serializer=budget_service.GetBudgetRequest.serialize,
+ response_deserializer=budget_model.Budget.deserialize,
+ )
+ return self._stubs["get_budget"]
+
+ @property
+ def list_budgets(
+ self,
+ ) -> Callable[
+ [budget_service.ListBudgetsRequest],
+ Awaitable[budget_service.ListBudgetsResponse],
+ ]:
+ r"""Return a callable for the list budgets method over gRPC.
+
+ Returns a list of budgets for a billing account.
+ WARNING: There are some fields exposed on the Google
+ Cloud Console that aren't available on this API. When
+ reading from the API, you will not see these fields in
+ the return value, though they may have been set in the
+ Cloud Console.
+
+ Returns:
+ Callable[[~.ListBudgetsRequest],
+ Awaitable[~.ListBudgetsResponse]]:
+ A function that, when called, will call the underlying RPC
+ on the server.
+ """
+ # Generate a "stub function" on-the-fly which will actually make
+ # the request.
+ # gRPC handles serialization and deserialization, so we just need
+ # to pass in the functions for each.
+ if "list_budgets" not in self._stubs:
+ self._stubs["list_budgets"] = self.grpc_channel.unary_unary(
+ "/google.cloud.billing.budgets.v1.BudgetService/ListBudgets",
+ request_serializer=budget_service.ListBudgetsRequest.serialize,
+ response_deserializer=budget_service.ListBudgetsResponse.deserialize,
+ )
+ return self._stubs["list_budgets"]
+
+ @property
+ def delete_budget(
+ self,
+ ) -> Callable[[budget_service.DeleteBudgetRequest], Awaitable[empty.Empty]]:
+ r"""Return a callable for the delete budget method over gRPC.
+
+ Deletes a budget. Returns successfully if already
+ deleted.
+
+ Returns:
+ Callable[[~.DeleteBudgetRequest],
+ Awaitable[~.Empty]]:
+ A function that, when called, will call the underlying RPC
+ on the server.
+ """
+ # Generate a "stub function" on-the-fly which will actually make
+ # the request.
+ # gRPC handles serialization and deserialization, so we just need
+ # to pass in the functions for each.
+ if "delete_budget" not in self._stubs:
+ self._stubs["delete_budget"] = self.grpc_channel.unary_unary(
+ "/google.cloud.billing.budgets.v1.BudgetService/DeleteBudget",
+ request_serializer=budget_service.DeleteBudgetRequest.serialize,
+ response_deserializer=empty.Empty.FromString,
+ )
+ return self._stubs["delete_budget"]
+
+
+__all__ = ("BudgetServiceGrpcAsyncIOTransport",)
diff --git a/google/cloud/billing/budgets_v1/types/__init__.py b/google/cloud/billing/budgets_v1/types/__init__.py
new file mode 100644
index 0000000..6720cb4
--- /dev/null
+++ b/google/cloud/billing/budgets_v1/types/__init__.py
@@ -0,0 +1,49 @@
+# -*- 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.
+#
+
+from .budget_model import (
+ Budget,
+ BudgetAmount,
+ LastPeriodAmount,
+ ThresholdRule,
+ NotificationsRule,
+ Filter,
+)
+from .budget_service import (
+ CreateBudgetRequest,
+ UpdateBudgetRequest,
+ GetBudgetRequest,
+ ListBudgetsRequest,
+ ListBudgetsResponse,
+ DeleteBudgetRequest,
+)
+
+
+__all__ = (
+ "Budget",
+ "BudgetAmount",
+ "LastPeriodAmount",
+ "ThresholdRule",
+ "NotificationsRule",
+ "Filter",
+ "CreateBudgetRequest",
+ "UpdateBudgetRequest",
+ "GetBudgetRequest",
+ "ListBudgetsRequest",
+ "ListBudgetsResponse",
+ "DeleteBudgetRequest",
+)
diff --git a/google/cloud/billing/budgets_v1/types/budget_model.py b/google/cloud/billing/budgets_v1/types/budget_model.py
new file mode 100644
index 0000000..b434854
--- /dev/null
+++ b/google/cloud/billing/budgets_v1/types/budget_model.py
@@ -0,0 +1,287 @@
+# -*- 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.
+#
+
+import proto # type: ignore
+
+
+from google.protobuf import struct_pb2 as struct # type: ignore
+from google.type import money_pb2 as money # type: ignore
+
+
+__protobuf__ = proto.module(
+ package="google.cloud.billing.budgets.v1",
+ manifest={
+ "Budget",
+ "BudgetAmount",
+ "LastPeriodAmount",
+ "ThresholdRule",
+ "NotificationsRule",
+ "Filter",
+ },
+)
+
+
+class Budget(proto.Message):
+ r"""A budget is a plan that describes what you expect to spend on
+ Cloud projects, plus the rules to execute as spend is tracked
+ against that plan, (for example, send an alert when 90% of the
+ target spend is met). Currently all plans are monthly budgets so
+ the usage period(s) tracked are implied (calendar months of
+ usage back-to-back).
+
+ Attributes:
+ name (str):
+ Output only. Resource name of the budget. The resource name
+ implies the scope of a budget. Values are of the form
+ ``billingAccounts/{billingAccountId}/budgets/{budgetId}``.
+ display_name (str):
+ User data for display name in UI. The name
+ must be less than or equal to 60 characters.
+ budget_filter (~.budget_model.Filter):
+ Optional. Filters that define which resources
+ are used to compute the actual spend against the
+ budget.
+ amount (~.budget_model.BudgetAmount):
+ Required. Budgeted amount.
+ threshold_rules (Sequence[~.budget_model.ThresholdRule]):
+ Optional. Rules that trigger alerts
+ (notifications of thresholds being crossed) when
+ spend exceeds the specified percentages of the
+ budget.
+ notifications_rule (~.budget_model.NotificationsRule):
+ Optional. Rules to apply to notifications
+ sent based on budget spend and thresholds.
+ etag (str):
+ Optional. Etag to validate that the object is
+ unchanged for a read-modify-write operation.
+ An empty etag will cause an update to overwrite
+ other changes.
+ """
+
+ name = proto.Field(proto.STRING, number=1)
+
+ display_name = proto.Field(proto.STRING, number=2)
+
+ budget_filter = proto.Field(proto.MESSAGE, number=3, message="Filter",)
+
+ amount = proto.Field(proto.MESSAGE, number=4, message="BudgetAmount",)
+
+ threshold_rules = proto.RepeatedField(
+ proto.MESSAGE, number=5, message="ThresholdRule",
+ )
+
+ notifications_rule = proto.Field(
+ proto.MESSAGE, number=6, message="NotificationsRule",
+ )
+
+ etag = proto.Field(proto.STRING, number=7)
+
+
+class BudgetAmount(proto.Message):
+ r"""The budgeted amount for each usage period.
+
+ Attributes:
+ specified_amount (~.money.Money):
+ A specified amount to use as the budget. ``currency_code``
+ is optional. If specified, it must match the currency of the
+ billing account. The ``currency_code`` is provided on
+ output.
+ last_period_amount (~.budget_model.LastPeriodAmount):
+ Use the last period's actual spend as the
+ budget for the present period.
+ """
+
+ specified_amount = proto.Field(
+ proto.MESSAGE, number=1, oneof="budget_amount", message=money.Money,
+ )
+
+ last_period_amount = proto.Field(
+ proto.MESSAGE, number=2, oneof="budget_amount", message="LastPeriodAmount",
+ )
+
+
+class LastPeriodAmount(proto.Message):
+ r"""Describes a budget amount targeted to last period's spend.
+ At this time, the amount is automatically 100% of last period's
+ spend; that is, there are no other options yet.
+ Future configuration will be described here (for example,
+ configuring a percentage of last period's spend).
+ """
+
+
+class ThresholdRule(proto.Message):
+ r"""ThresholdRule contains a definition of a threshold which triggers an
+ alert (a notification of a threshold being crossed) to be sent when
+ spend goes above the specified amount. Alerts are automatically
+ e-mailed to users with the Billing Account Administrator role or the
+ Billing Account User role. The thresholds here have no effect on
+ notifications sent to anything configured under
+ ``Budget.all_updates_rule``.
+
+ Attributes:
+ threshold_percent (float):
+ Required. Send an alert when this threshold
+ is exceeded. This is a 1.0-based percentage, so
+ 0.5 = 50%. Validation: non-negative number.
+ spend_basis (~.budget_model.ThresholdRule.Basis):
+ Optional. The type of basis used to determine if spend has
+ passed the threshold. Behavior defaults to CURRENT_SPEND if
+ not set.
+ """
+
+ class Basis(proto.Enum):
+ r"""The type of basis used to determine if spend has passed the
+ threshold.
+ """
+ BASIS_UNSPECIFIED = 0
+ CURRENT_SPEND = 1
+ FORECASTED_SPEND = 2
+
+ threshold_percent = proto.Field(proto.DOUBLE, number=1)
+
+ spend_basis = proto.Field(proto.ENUM, number=2, enum=Basis,)
+
+
+class NotificationsRule(proto.Message):
+ r"""NotificationsRule defines notifications that are sent based
+ on budget spend and thresholds.
+
+ Attributes:
+ pubsub_topic (str):
+ Optional. The name of the Pub/Sub topic where budget related
+ messages will be published, in the form
+ ``projects/{project_id}/topics/{topic_id}``. Updates are
+ sent at regular intervals to the topic. The topic needs to
+ be created before the budget is created; see
+ https://cloud.google.com/billing/docs/how-to/budgets#manage-notifications
+ for more details. Caller is expected to have
+ ``pubsub.topics.setIamPolicy`` permission on the topic when
+ it's set for a budget, otherwise, the API call will fail
+ with PERMISSION_DENIED. See
+ https://cloud.google.com/billing/docs/how-to/budgets-programmatic-notifications
+ for more details on Pub/Sub roles and permissions.
+ schema_version (str):
+ Optional. The schema version of the notification sent to
+ ``pubsub_topic``. Only "1.0" is accepted. It represents the
+ JSON schema as defined in
+ https://cloud.google.com/billing/docs/how-to/budgets-programmatic-notifications#notification_format
+ monitoring_notification_channels (Sequence[str]):
+ Optional. Targets to send notifications to when a threshold
+ is exceeded. This is in addition to default recipients who
+ have billing account IAM roles. The value is the full REST
+ resource name of a monitoring notification channel with the
+ form
+ ``projects/{project_id}/notificationChannels/{channel_id}``.
+ A maximum of 5 channels are allowed. See
+ https://cloud.google.com/billing/docs/how-to/budgets-notification-recipients
+ for more details.
+ disable_default_iam_recipients (bool):
+ Optional. When set to true, disables default
+ notifications sent when a threshold is exceeded.
+ Default notifications are sent to those with
+ Billing Account Administrator and Billing
+ Account User IAM roles for the target account.
+ """
+
+ pubsub_topic = proto.Field(proto.STRING, number=1)
+
+ schema_version = proto.Field(proto.STRING, number=2)
+
+ monitoring_notification_channels = proto.RepeatedField(proto.STRING, number=3)
+
+ disable_default_iam_recipients = proto.Field(proto.BOOL, number=4)
+
+
+class Filter(proto.Message):
+ r"""A filter for a budget, limiting the scope of the cost to
+ calculate.
+
+ Attributes:
+ projects (Sequence[str]):
+ Optional. A set of projects of the form
+ ``projects/{project}``, specifying that usage from only this
+ set of projects should be included in the budget. If
+ omitted, the report will include all usage for the billing
+ account, regardless of which project the usage occurred on.
+ Only zero or one project can be specified currently.
+ credit_types (Sequence[str]):
+ Optional. If
+ [Filter.credit_types_treatment][google.cloud.billing.budgets.v1.Filter.credit_types_treatment]
+ is INCLUDE_SPECIFIED_CREDITS, this is a list of credit types
+ to be subtracted from gross cost to determine the spend for
+ threshold calculations.
+
+ If
+ [Filter.credit_types_treatment][google.cloud.billing.budgets.v1.Filter.credit_types_treatment]
+ is **not** INCLUDE_SPECIFIED_CREDITS, this field must be
+ empty. See `a list of acceptable credit type
+ values `__.
+ credit_types_treatment (~.budget_model.Filter.CreditTypesTreatment):
+ Optional. If not set, default behavior is
+ ``INCLUDE_ALL_CREDITS``.
+ services (Sequence[str]):
+ Optional. A set of services of the form
+ ``services/{service_id}``, specifying that usage from only
+ this set of services should be included in the budget. If
+ omitted, the report will include usage for all the services.
+ The service names are available through the Catalog API:
+ https://cloud.google.com/billing/v1/how-tos/catalog-api.
+ subaccounts (Sequence[str]):
+ Optional. A set of subaccounts of the form
+ ``billingAccounts/{account_id}``, specifying that usage from
+ only this set of subaccounts should be included in the
+ budget. If a subaccount is set to the name of the parent
+ account, usage from the parent account will be included. If
+ the field is omitted, the report will include usage from the
+ parent account and all subaccounts, if they exist.
+ labels (Sequence[~.budget_model.Filter.LabelsEntry]):
+ Optional. A single label and value pair
+ specifying that usage from only this set of
+ labeled resources should be included in the
+ budget. Currently, multiple entries or multiple
+ values per entry are not allowed. If omitted,
+ the report will include all labeled and
+ unlabeled usage.
+ """
+
+ class CreditTypesTreatment(proto.Enum):
+ r"""Specifies how credits should be treated when determining
+ spend for threshold calculations.
+ """
+ CREDIT_TYPES_TREATMENT_UNSPECIFIED = 0
+ INCLUDE_ALL_CREDITS = 1
+ EXCLUDE_ALL_CREDITS = 2
+ INCLUDE_SPECIFIED_CREDITS = 3
+
+ projects = proto.RepeatedField(proto.STRING, number=1)
+
+ credit_types = proto.RepeatedField(proto.STRING, number=7)
+
+ credit_types_treatment = proto.Field(
+ proto.ENUM, number=4, enum=CreditTypesTreatment,
+ )
+
+ services = proto.RepeatedField(proto.STRING, number=3)
+
+ subaccounts = proto.RepeatedField(proto.STRING, number=5)
+
+ labels = proto.MapField(
+ proto.STRING, proto.MESSAGE, number=6, message=struct.ListValue,
+ )
+
+
+__all__ = tuple(sorted(__protobuf__.manifest))
diff --git a/google/cloud/billing/budgets_v1/types/budget_service.py b/google/cloud/billing/budgets_v1/types/budget_service.py
new file mode 100644
index 0000000..6fd3ff0
--- /dev/null
+++ b/google/cloud/billing/budgets_v1/types/budget_service.py
@@ -0,0 +1,150 @@
+# -*- 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.
+#
+
+import proto # type: ignore
+
+
+from google.cloud.billing.budgets_v1.types import budget_model
+from google.protobuf import field_mask_pb2 as field_mask # type: ignore
+
+
+__protobuf__ = proto.module(
+ package="google.cloud.billing.budgets.v1",
+ manifest={
+ "CreateBudgetRequest",
+ "UpdateBudgetRequest",
+ "GetBudgetRequest",
+ "ListBudgetsRequest",
+ "ListBudgetsResponse",
+ "DeleteBudgetRequest",
+ },
+)
+
+
+class CreateBudgetRequest(proto.Message):
+ r"""Request for CreateBudget
+
+ Attributes:
+ parent (str):
+ Required. The name of the billing account to create the
+ budget in. Values are of the form
+ ``billingAccounts/{billingAccountId}``.
+ budget (~.budget_model.Budget):
+ Required. Budget to create.
+ """
+
+ parent = proto.Field(proto.STRING, number=1)
+
+ budget = proto.Field(proto.MESSAGE, number=2, message=budget_model.Budget,)
+
+
+class UpdateBudgetRequest(proto.Message):
+ r"""Request for UpdateBudget
+
+ Attributes:
+ budget (~.budget_model.Budget):
+ Required. The updated budget object.
+ The budget to update is specified by the budget
+ name in the budget.
+ update_mask (~.field_mask.FieldMask):
+ Optional. Indicates which fields in the provided budget to
+ update. Read-only fields (such as ``name``) cannot be
+ changed. If this is not provided, then only fields with
+ non-default values from the request are updated. See
+ https://developers.google.com/protocol-buffers/docs/proto3#default
+ for more details about default values.
+ """
+
+ budget = proto.Field(proto.MESSAGE, number=1, message=budget_model.Budget,)
+
+ update_mask = proto.Field(proto.MESSAGE, number=2, message=field_mask.FieldMask,)
+
+
+class GetBudgetRequest(proto.Message):
+ r"""Request for GetBudget
+
+ Attributes:
+ name (str):
+ Required. Name of budget to get. Values are of the form
+ ``billingAccounts/{billingAccountId}/budgets/{budgetId}``.
+ """
+
+ name = proto.Field(proto.STRING, number=1)
+
+
+class ListBudgetsRequest(proto.Message):
+ r"""Request for ListBudgets
+
+ Attributes:
+ parent (str):
+ Required. Name of billing account to list budgets under.
+ Values are of the form
+ ``billingAccounts/{billingAccountId}``.
+ page_size (int):
+ Optional. The maximum number of budgets to
+ return per page. The default and maximum value
+ are 100.
+ page_token (str):
+ Optional. The value returned by the last
+ ``ListBudgetsResponse`` which indicates that this is a
+ continuation of a prior ``ListBudgets`` call, and that the
+ system should return the next page of data.
+ """
+
+ parent = proto.Field(proto.STRING, number=1)
+
+ page_size = proto.Field(proto.INT32, number=2)
+
+ page_token = proto.Field(proto.STRING, number=3)
+
+
+class ListBudgetsResponse(proto.Message):
+ r"""Response for ListBudgets
+
+ Attributes:
+ budgets (Sequence[~.budget_model.Budget]):
+ List of the budgets owned by the requested
+ billing account.
+ next_page_token (str):
+ If not empty, indicates that there may be more budgets that
+ match the request; this value should be passed in a new
+ ``ListBudgetsRequest``.
+ """
+
+ @property
+ def raw_page(self):
+ return self
+
+ budgets = proto.RepeatedField(proto.MESSAGE, number=1, message=budget_model.Budget,)
+
+ next_page_token = proto.Field(proto.STRING, number=2)
+
+
+class DeleteBudgetRequest(proto.Message):
+ r"""Request for DeleteBudget
+
+ Attributes:
+ name (str):
+ Required. Name of the budget to delete. Values are of the
+ form
+ ``billingAccounts/{billingAccountId}/budgets/{budgetId}``.
+ """
+
+ name = proto.Field(proto.STRING, number=1)
+
+
+__all__ = tuple(sorted(__protobuf__.manifest))
diff --git a/google/cloud/billing/budgets_v1beta1/proto/budget_model.proto b/google/cloud/billing/budgets_v1beta1/proto/budget_model.proto
index fb2f1cf..10f149f 100644
--- a/google/cloud/billing/budgets_v1beta1/proto/budget_model.proto
+++ b/google/cloud/billing/budgets_v1beta1/proto/budget_model.proto
@@ -54,10 +54,11 @@ message Budget {
// Optional. Rules that trigger alerts (notifications of thresholds
// being crossed) when spend exceeds the specified percentages of the budget.
- repeated ThresholdRule threshold_rules = 5 [(google.api.field_behavior) = OPTIONAL];
+ repeated ThresholdRule threshold_rules = 5
+ [(google.api.field_behavior) = OPTIONAL];
- // Optional. Rules to apply to all updates to the actual spend, regardless
- // of the thresholds set in `threshold_rules`.
+ // Optional. Rules to apply to notifications sent based on budget spend and
+ // thresholds.
AllUpdatesRule all_updates_rule = 6 [(google.api.field_behavior) = OPTIONAL];
// Optional. Etag to validate that the object is unchanged for a
@@ -86,9 +87,7 @@ message BudgetAmount {
// that is, there are no other options yet.
// Future configuration will be described here (for example, configuring a
// percentage of last period's spend).
-message LastPeriodAmount {
-
-}
+message LastPeriodAmount {}
// ThresholdRule contains a definition of a threshold which triggers
// an alert (a notification of a threshold being crossed) to be sent when
@@ -121,27 +120,44 @@ message ThresholdRule {
Basis spend_basis = 2 [(google.api.field_behavior) = OPTIONAL];
}
-// AllUpdatesRule defines notifications that are sent on every update to the
-// billing account's spend, regardless of the thresholds defined using
-// threshold rules.
+// AllUpdatesRule defines notifications that are sent based on budget spend
+// and thresholds.
message AllUpdatesRule {
- // Required. The name of the Cloud Pub/Sub topic where budget related messages will be
- // published, in the form `projects/{project_id}/topics/{topic_id}`. Updates
- // are sent at regular intervals to the topic.
- // The topic needs to be created before the budget is created; see
+ // Optional. The name of the Pub/Sub topic where budget related messages will
+ // be published, in the form `projects/{project_id}/topics/{topic_id}`.
+ // Updates are sent at regular intervals to the topic. The topic needs to be
+ // created before the budget is created; see
// https://cloud.google.com/billing/docs/how-to/budgets#manage-notifications
// for more details.
// Caller is expected to have
// `pubsub.topics.setIamPolicy` permission on the topic when it's set for a
// budget, otherwise, the API call will fail with PERMISSION_DENIED. See
- // https://cloud.google.com/pubsub/docs/access-control for more details on
- // Pub/Sub roles and permissions.
- string pubsub_topic = 1 [(google.api.field_behavior) = REQUIRED];
+ // https://cloud.google.com/billing/docs/how-to/budgets-programmatic-notifications
+ // for more details on Pub/Sub roles and permissions.
+ string pubsub_topic = 1 [(google.api.field_behavior) = OPTIONAL];
- // Required. The schema version of the notification.
+ // Optional. The schema version of the notification sent to `pubsub_topic`.
// Only "1.0" is accepted. It represents the JSON schema as defined in
- // https://cloud.google.com/billing/docs/how-to/budgets#notification_format
- string schema_version = 2 [(google.api.field_behavior) = REQUIRED];
+ // https://cloud.google.com/billing/docs/how-to/budgets-programmatic-notifications#notification_format
+ string schema_version = 2 [(google.api.field_behavior) = OPTIONAL];
+
+ // Optional. Targets to send notifications to when a threshold is exceeded.
+ // This is in addition to default recipients who have billing account IAM
+ // roles. The value is the full REST resource name of a monitoring
+ // notification channel with the form
+ // `projects/{project_id}/notificationChannels/{channel_id}`. A maximum of 5
+ // channels are allowed. See
+ // https://cloud.google.com/billing/docs/how-to/budgets-notification-recipients
+ // for more details.
+ repeated string monitoring_notification_channels = 3
+ [(google.api.field_behavior) = OPTIONAL];
+
+ // Optional. When set to true, disables default notifications sent when a
+ // threshold is exceeded. Default notifications are sent to those with Billing
+ // Account Administrator and Billing Account User IAM roles for the target
+ // account.
+ bool disable_default_iam_recipients = 4
+ [(google.api.field_behavior) = OPTIONAL];
}
// A filter for a budget, limiting the scope of the cost to calculate.
@@ -158,6 +174,10 @@ message Filter {
// All types of credit are added to the net cost to determine the spend for
// threshold calculations.
EXCLUDE_ALL_CREDITS = 2;
+
+ // Credit types specified in the credit_types field are subtracted from the
+ // gross cost to determine the spend for threshold calculations.
+ INCLUDE_SPECIFIED_CREDITS = 3;
}
// Optional. A set of projects of the form `projects/{project}`,
@@ -167,8 +187,18 @@ message Filter {
// Only zero or one project can be specified currently.
repeated string projects = 1 [(google.api.field_behavior) = OPTIONAL];
+ // Optional. A list of credit types to be subtracted from gross cost to
+ // determine the spend for threshold calculations if and only if
+ // credit_types_treatment is INCLUDE_SPECIFIED_CREDITS. If
+ // credit_types_treatment is not INCLUDE_SPECIFIED_CREDITS, this field must be
+ // empty. See credits.type at
+ // https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables#data-schema
+ // for a list of acceptable credit type values in this field.
+ repeated string credit_types = 7 [(google.api.field_behavior) = OPTIONAL];
+
// Optional. If not set, default behavior is `INCLUDE_ALL_CREDITS`.
- CreditTypesTreatment credit_types_treatment = 4 [(google.api.field_behavior) = OPTIONAL];
+ CreditTypesTreatment credit_types_treatment = 4
+ [(google.api.field_behavior) = OPTIONAL];
// Optional. A set of services of the form `services/{service_id}`,
// specifying that usage from only this set of services should be
@@ -178,16 +208,18 @@ message Filter {
// https://cloud.google.com/billing/v1/how-tos/catalog-api.
repeated string services = 3 [(google.api.field_behavior) = OPTIONAL];
- // Optional. A set of subaccounts of the form `billingAccounts/{account_id}`, specifying
- // that usage from only this set of subaccounts should be included in the
- // budget. If a subaccount is set to the name of the master account, usage
- // from the master account will be included. If omitted, the report will
- // include usage from the master account and all subaccounts, if they exist.
+ // Optional. A set of subaccounts of the form `billingAccounts/{account_id}`,
+ // specifying that usage from only this set of subaccounts should be included
+ // in the budget. If a subaccount is set to the name of the parent account,
+ // usage from the parent account will be included. If omitted, the
+ // report will include usage from the parent account and all
+ // subaccounts, if they exist.
repeated string subaccounts = 5 [(google.api.field_behavior) = OPTIONAL];
- // Optional. A single label and value pair specifying that usage from only this set of
- // labeled resources should be included in the budget. Multiple entries or
- // multiple values per entry are not allowed. If omitted, the report will
- // include all labeled and unlabeled usage.
- map labels = 6 [(google.api.field_behavior) = OPTIONAL];
+ // Optional. A single label and value pair specifying that usage from only
+ // this set of labeled resources should be included in the budget. Currently,
+ // multiple entries or multiple values per entry are not allowed. If omitted,
+ // the report will include all labeled and unlabeled usage.
+ map labels = 6
+ [(google.api.field_behavior) = OPTIONAL];
}
diff --git a/google/cloud/billing/budgets_v1beta1/proto/budget_service.proto b/google/cloud/billing/budgets_v1beta1/proto/budget_service.proto
index e15ecf5..fa52395 100644
--- a/google/cloud/billing/budgets_v1beta1/proto/budget_service.proto
+++ b/google/cloud/billing/budgets_v1beta1/proto/budget_service.proto
@@ -32,7 +32,9 @@ option java_package = "com.google.cloud.billing.budgets.v1beta1";
// budget plan and rules to execute as we track spend against that plan.
service BudgetService {
option (google.api.default_host) = "billingbudgets.googleapis.com";
- option (google.api.oauth_scopes) = "https://www.googleapis.com/auth/cloud-platform";
+ option (google.api.oauth_scopes) =
+ "https://www.googleapis.com/auth/cloud-billing,"
+ "https://www.googleapis.com/auth/cloud-platform";
// Creates a new budget. See
// Quotas and limits
@@ -115,7 +117,8 @@ message UpdateBudgetRequest {
// updated. See
// https://developers.google.com/protocol-buffers/docs/proto3#default for more
// details about default values.
- google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = OPTIONAL];
+ google.protobuf.FieldMask update_mask = 2
+ [(google.api.field_behavior) = OPTIONAL];
}
// Request for GetBudget
diff --git a/google/cloud/billing/budgets_v1beta1/services/budget_service/async_client.py b/google/cloud/billing/budgets_v1beta1/services/budget_service/async_client.py
index c39f4f8..641321d 100644
--- a/google/cloud/billing/budgets_v1beta1/services/budget_service/async_client.py
+++ b/google/cloud/billing/budgets_v1beta1/services/budget_service/async_client.py
@@ -32,7 +32,7 @@
from google.cloud.billing.budgets_v1beta1.types import budget_model
from google.cloud.billing.budgets_v1beta1.types import budget_service
-from .transports.base import BudgetServiceTransport
+from .transports.base import BudgetServiceTransport, DEFAULT_CLIENT_INFO
from .transports.grpc_asyncio import BudgetServiceGrpcAsyncIOTransport
from .client import BudgetServiceClient
@@ -49,10 +49,49 @@ class BudgetServiceAsyncClient:
DEFAULT_MTLS_ENDPOINT = BudgetServiceClient.DEFAULT_MTLS_ENDPOINT
budget_path = staticmethod(BudgetServiceClient.budget_path)
+ parse_budget_path = staticmethod(BudgetServiceClient.parse_budget_path)
+
+ common_billing_account_path = staticmethod(
+ BudgetServiceClient.common_billing_account_path
+ )
+ parse_common_billing_account_path = staticmethod(
+ BudgetServiceClient.parse_common_billing_account_path
+ )
+
+ common_folder_path = staticmethod(BudgetServiceClient.common_folder_path)
+ parse_common_folder_path = staticmethod(
+ BudgetServiceClient.parse_common_folder_path
+ )
+
+ common_organization_path = staticmethod(
+ BudgetServiceClient.common_organization_path
+ )
+ parse_common_organization_path = staticmethod(
+ BudgetServiceClient.parse_common_organization_path
+ )
+
+ common_project_path = staticmethod(BudgetServiceClient.common_project_path)
+ parse_common_project_path = staticmethod(
+ BudgetServiceClient.parse_common_project_path
+ )
+
+ common_location_path = staticmethod(BudgetServiceClient.common_location_path)
+ parse_common_location_path = staticmethod(
+ BudgetServiceClient.parse_common_location_path
+ )
from_service_account_file = BudgetServiceClient.from_service_account_file
from_service_account_json = from_service_account_file
+ @property
+ def transport(self) -> BudgetServiceTransport:
+ """Return the transport used by the client instance.
+
+ Returns:
+ BudgetServiceTransport: The transport used by the client instance.
+ """
+ return self._client.transport
+
get_transport_class = functools.partial(
type(BudgetServiceClient).get_transport_class, type(BudgetServiceClient)
)
@@ -63,6 +102,7 @@ def __init__(
credentials: credentials.Credentials = None,
transport: Union[str, BudgetServiceTransport] = "grpc_asyncio",
client_options: ClientOptions = None,
+ client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
) -> None:
"""Instantiate the budget service client.
@@ -78,16 +118,19 @@ def __init__(
client_options (ClientOptions): Custom options for the client. It
won't take effect if a ``transport`` instance is provided.
(1) The ``api_endpoint`` property can be used to override the
- default endpoint provided by the client. GOOGLE_API_USE_MTLS
+ default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT
environment variable can also be used to override the endpoint:
"always" (always use the default mTLS endpoint), "never" (always
- use the default regular endpoint, this is the default value for
- the environment variable) and "auto" (auto switch to the default
- mTLS endpoint if client SSL credentials is present). However,
- the ``api_endpoint`` property takes precedence if provided.
- (2) The ``client_cert_source`` property is used to provide client
- SSL credentials for mutual TLS transport. If not provided, the
- default SSL credentials will be used if present.
+ use the default regular endpoint) and "auto" (auto switch to the
+ default mTLS endpoint if client certificate is present, this is
+ the default value). However, the ``api_endpoint`` property takes
+ precedence if provided.
+ (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
+ is "true", then the ``client_cert_source`` property can be used
+ to provide client certificate for mutual TLS transport. If
+ not provided, the default SSL client certificate will be used if
+ present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
+ set, no client certificate will be used.
Raises:
google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
@@ -95,7 +138,10 @@ def __init__(
"""
self._client = BudgetServiceClient(
- credentials=credentials, transport=transport, client_options=client_options,
+ credentials=credentials,
+ transport=transport,
+ client_options=client_options,
+ client_info=client_info,
)
async def create_budget(
@@ -142,8 +188,8 @@ async def create_budget(
# and friendly error handling.
rpc = gapic_v1.method_async.wrap_method(
self._client._transport.create_budget,
- default_timeout=None,
- client_info=_client_info,
+ default_timeout=60.0,
+ client_info=DEFAULT_CLIENT_INFO,
)
# Certain fields should be provided within the metadata header;
@@ -203,8 +249,16 @@ async def update_budget(
# and friendly error handling.
rpc = gapic_v1.method_async.wrap_method(
self._client._transport.update_budget,
- default_timeout=None,
- client_info=_client_info,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.DeadlineExceeded, exceptions.ServiceUnavailable,
+ ),
+ ),
+ default_timeout=60.0,
+ client_info=DEFAULT_CLIENT_INFO,
)
# Certain fields should be provided within the metadata header;
@@ -267,8 +321,16 @@ async def get_budget(
# and friendly error handling.
rpc = gapic_v1.method_async.wrap_method(
self._client._transport.get_budget,
- default_timeout=None,
- client_info=_client_info,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.DeadlineExceeded, exceptions.ServiceUnavailable,
+ ),
+ ),
+ default_timeout=60.0,
+ client_info=DEFAULT_CLIENT_INFO,
)
# Certain fields should be provided within the metadata header;
@@ -324,8 +386,16 @@ async def list_budgets(
# and friendly error handling.
rpc = gapic_v1.method_async.wrap_method(
self._client._transport.list_budgets,
- default_timeout=None,
- client_info=_client_info,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.DeadlineExceeded, exceptions.ServiceUnavailable,
+ ),
+ ),
+ default_timeout=60.0,
+ client_info=DEFAULT_CLIENT_INFO,
)
# Certain fields should be provided within the metadata header;
@@ -375,8 +445,16 @@ async def delete_budget(
# and friendly error handling.
rpc = gapic_v1.method_async.wrap_method(
self._client._transport.delete_budget,
- default_timeout=None,
- client_info=_client_info,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.DeadlineExceeded, exceptions.ServiceUnavailable,
+ ),
+ ),
+ default_timeout=60.0,
+ client_info=DEFAULT_CLIENT_INFO,
)
# Certain fields should be provided within the metadata header;
@@ -392,13 +470,13 @@ async def delete_budget(
try:
- _client_info = gapic_v1.client_info.ClientInfo(
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
gapic_version=pkg_resources.get_distribution(
"google-cloud-billing-budgets",
).version,
)
except pkg_resources.DistributionNotFound:
- _client_info = gapic_v1.client_info.ClientInfo()
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo()
__all__ = ("BudgetServiceAsyncClient",)
diff --git a/google/cloud/billing/budgets_v1beta1/services/budget_service/client.py b/google/cloud/billing/budgets_v1beta1/services/budget_service/client.py
index 8db8e78..93223ad 100644
--- a/google/cloud/billing/budgets_v1beta1/services/budget_service/client.py
+++ b/google/cloud/billing/budgets_v1beta1/services/budget_service/client.py
@@ -16,17 +16,19 @@
#
from collections import OrderedDict
+from distutils import util
import os
import re
-from typing import Callable, Dict, Sequence, Tuple, Type, Union
+from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union
import pkg_resources
-import google.api_core.client_options as ClientOptions # type: ignore
+from google.api_core import client_options as client_options_lib # type: ignore
from google.api_core import exceptions # type: ignore
from google.api_core import gapic_v1 # type: ignore
from google.api_core import retry as retries # type: ignore
from google.auth import credentials # type: ignore
from google.auth.transport import mtls # type: ignore
+from google.auth.transport.grpc import SslCredentials # type: ignore
from google.auth.exceptions import MutualTLSChannelError # type: ignore
from google.oauth2 import service_account # type: ignore
@@ -34,7 +36,7 @@
from google.cloud.billing.budgets_v1beta1.types import budget_model
from google.cloud.billing.budgets_v1beta1.types import budget_service
-from .transports.base import BudgetServiceTransport
+from .transports.base import BudgetServiceTransport, DEFAULT_CLIENT_INFO
from .transports.grpc import BudgetServiceGrpcTransport
from .transports.grpc_asyncio import BudgetServiceGrpcAsyncIOTransport
@@ -130,6 +132,15 @@ def from_service_account_file(cls, filename: str, *args, **kwargs):
from_service_account_json = from_service_account_file
+ @property
+ def transport(self) -> BudgetServiceTransport:
+ """Return the transport used by the client instance.
+
+ Returns:
+ BudgetServiceTransport: The transport used by the client instance.
+ """
+ return self._transport
+
@staticmethod
def budget_path(billing_account: str, budget: str,) -> str:
"""Return a fully-qualified budget string."""
@@ -145,12 +156,72 @@ def parse_budget_path(path: str) -> Dict[str, str]:
)
return m.groupdict() if m else {}
+ @staticmethod
+ def common_billing_account_path(billing_account: str,) -> str:
+ """Return a fully-qualified billing_account string."""
+ return "billingAccounts/{billing_account}".format(
+ billing_account=billing_account,
+ )
+
+ @staticmethod
+ def parse_common_billing_account_path(path: str) -> Dict[str, str]:
+ """Parse a billing_account path into its component segments."""
+ m = re.match(r"^billingAccounts/(?P.+?)$", path)
+ return m.groupdict() if m else {}
+
+ @staticmethod
+ def common_folder_path(folder: str,) -> str:
+ """Return a fully-qualified folder string."""
+ return "folders/{folder}".format(folder=folder,)
+
+ @staticmethod
+ def parse_common_folder_path(path: str) -> Dict[str, str]:
+ """Parse a folder path into its component segments."""
+ m = re.match(r"^folders/(?P.+?)$", path)
+ return m.groupdict() if m else {}
+
+ @staticmethod
+ def common_organization_path(organization: str,) -> str:
+ """Return a fully-qualified organization string."""
+ return "organizations/{organization}".format(organization=organization,)
+
+ @staticmethod
+ def parse_common_organization_path(path: str) -> Dict[str, str]:
+ """Parse a organization path into its component segments."""
+ m = re.match(r"^organizations/(?P.+?)$", path)
+ return m.groupdict() if m else {}
+
+ @staticmethod
+ def common_project_path(project: str,) -> str:
+ """Return a fully-qualified project string."""
+ return "projects/{project}".format(project=project,)
+
+ @staticmethod
+ def parse_common_project_path(path: str) -> Dict[str, str]:
+ """Parse a project path into its component segments."""
+ m = re.match(r"^projects/(?P.+?)$", path)
+ return m.groupdict() if m else {}
+
+ @staticmethod
+ def common_location_path(project: str, location: str,) -> str:
+ """Return a fully-qualified location string."""
+ return "projects/{project}/locations/{location}".format(
+ project=project, location=location,
+ )
+
+ @staticmethod
+ def parse_common_location_path(path: str) -> Dict[str, str]:
+ """Parse a location path into its component segments."""
+ m = re.match(r"^projects/(?P.+?)/locations/(?P.+?)$", path)
+ return m.groupdict() if m else {}
+
def __init__(
self,
*,
- credentials: credentials.Credentials = None,
- transport: Union[str, BudgetServiceTransport] = None,
- client_options: ClientOptions = None,
+ credentials: Optional[credentials.Credentials] = None,
+ transport: Union[str, BudgetServiceTransport, None] = None,
+ client_options: Optional[client_options_lib.ClientOptions] = None,
+ client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
) -> None:
"""Instantiate the budget service client.
@@ -163,48 +234,74 @@ def __init__(
transport (Union[str, ~.BudgetServiceTransport]): The
transport to use. If set to None, a transport is chosen
automatically.
- client_options (ClientOptions): Custom options for the client. It
- won't take effect if a ``transport`` instance is provided.
+ client_options (client_options_lib.ClientOptions): Custom options for the
+ client. It won't take effect if a ``transport`` instance is provided.
(1) The ``api_endpoint`` property can be used to override the
- default endpoint provided by the client. GOOGLE_API_USE_MTLS
+ default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT
environment variable can also be used to override the endpoint:
"always" (always use the default mTLS endpoint), "never" (always
- use the default regular endpoint, this is the default value for
- the environment variable) and "auto" (auto switch to the default
- mTLS endpoint if client SSL credentials is present). However,
- the ``api_endpoint`` property takes precedence if provided.
- (2) The ``client_cert_source`` property is used to provide client
- SSL credentials for mutual TLS transport. If not provided, the
- default SSL credentials will be used if present.
+ use the default regular endpoint) and "auto" (auto switch to the
+ default mTLS endpoint if client certificate is present, this is
+ the default value). However, the ``api_endpoint`` property takes
+ precedence if provided.
+ (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
+ is "true", then the ``client_cert_source`` property can be used
+ to provide client certificate for mutual TLS transport. If
+ not provided, the default SSL client certificate will be used if
+ present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
+ set, no client certificate will be used.
+ 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:
google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
creation failed for any reason.
"""
if isinstance(client_options, dict):
- client_options = ClientOptions.from_dict(client_options)
+ client_options = client_options_lib.from_dict(client_options)
if client_options is None:
- client_options = ClientOptions.ClientOptions()
+ client_options = client_options_lib.ClientOptions()
+
+ # Create SSL credentials for mutual TLS if needed.
+ use_client_cert = bool(
+ util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"))
+ )
- if client_options.api_endpoint is None:
- use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "never")
+ ssl_credentials = 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
+ else:
+ creds = SslCredentials()
+ is_mtls = creds.is_mtls
+ ssl_credentials = creds.ssl_credentials if is_mtls else None
+
+ # Figure out which api endpoint to use.
+ if client_options.api_endpoint is not None:
+ api_endpoint = client_options.api_endpoint
+ else:
+ use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
if use_mtls_env == "never":
- client_options.api_endpoint = self.DEFAULT_ENDPOINT
+ api_endpoint = self.DEFAULT_ENDPOINT
elif use_mtls_env == "always":
- client_options.api_endpoint = self.DEFAULT_MTLS_ENDPOINT
+ api_endpoint = self.DEFAULT_MTLS_ENDPOINT
elif use_mtls_env == "auto":
- has_client_cert_source = (
- client_options.client_cert_source is not None
- or mtls.has_default_client_cert_source()
- )
- client_options.api_endpoint = (
- self.DEFAULT_MTLS_ENDPOINT
- if has_client_cert_source
- else self.DEFAULT_ENDPOINT
+ api_endpoint = (
+ self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT
)
else:
raise MutualTLSChannelError(
- "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: never, auto, always"
+ "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always"
)
# Save or instantiate the transport.
@@ -228,10 +325,11 @@ def __init__(
self._transport = Transport(
credentials=credentials,
credentials_file=client_options.credentials_file,
- host=client_options.api_endpoint,
+ host=api_endpoint,
scopes=client_options.scopes,
- api_mtls_endpoint=client_options.api_endpoint,
- client_cert_source=client_options.client_cert_source,
+ ssl_channel_credentials=ssl_credentials,
+ quota_project_id=client_options.quota_project_id,
+ client_info=client_info,
)
def create_budget(
@@ -272,15 +370,16 @@ def create_budget(
"""
# Create or coerce a protobuf request object.
- request = budget_service.CreateBudgetRequest(request)
+ # Minor optimization to avoid making a copy if the user passes
+ # in a budget_service.CreateBudgetRequest.
+ # There's no risk of modifying the input as we've already verified
+ # there are no flattened fields.
+ if not isinstance(request, budget_service.CreateBudgetRequest):
+ request = budget_service.CreateBudgetRequest(request)
# Wrap the RPC method; this adds retry and timeout information,
# and friendly error handling.
- rpc = gapic_v1.method.wrap_method(
- self._transport.create_budget,
- default_timeout=None,
- client_info=_client_info,
- )
+ rpc = self._transport._wrapped_methods[self._transport.create_budget]
# Certain fields should be provided within the metadata header;
# add these here.
@@ -333,15 +432,16 @@ def update_budget(
"""
# Create or coerce a protobuf request object.
- request = budget_service.UpdateBudgetRequest(request)
+ # Minor optimization to avoid making a copy if the user passes
+ # in a budget_service.UpdateBudgetRequest.
+ # There's no risk of modifying the input as we've already verified
+ # there are no flattened fields.
+ if not isinstance(request, budget_service.UpdateBudgetRequest):
+ request = budget_service.UpdateBudgetRequest(request)
# Wrap the RPC method; this adds retry and timeout information,
# and friendly error handling.
- rpc = gapic_v1.method.wrap_method(
- self._transport.update_budget,
- default_timeout=None,
- client_info=_client_info,
- )
+ rpc = self._transport._wrapped_methods[self._transport.update_budget]
# Certain fields should be provided within the metadata header;
# add these here.
@@ -397,13 +497,16 @@ def get_budget(
"""
# Create or coerce a protobuf request object.
- request = budget_service.GetBudgetRequest(request)
+ # Minor optimization to avoid making a copy if the user passes
+ # in a budget_service.GetBudgetRequest.
+ # There's no risk of modifying the input as we've already verified
+ # there are no flattened fields.
+ if not isinstance(request, budget_service.GetBudgetRequest):
+ request = budget_service.GetBudgetRequest(request)
# Wrap the RPC method; this adds retry and timeout information,
# and friendly error handling.
- rpc = gapic_v1.method.wrap_method(
- self._transport.get_budget, default_timeout=None, client_info=_client_info,
- )
+ rpc = self._transport._wrapped_methods[self._transport.get_budget]
# Certain fields should be provided within the metadata header;
# add these here.
@@ -452,15 +555,16 @@ def list_budgets(
"""
# Create or coerce a protobuf request object.
- request = budget_service.ListBudgetsRequest(request)
+ # Minor optimization to avoid making a copy if the user passes
+ # in a budget_service.ListBudgetsRequest.
+ # There's no risk of modifying the input as we've already verified
+ # there are no flattened fields.
+ if not isinstance(request, budget_service.ListBudgetsRequest):
+ request = budget_service.ListBudgetsRequest(request)
# Wrap the RPC method; this adds retry and timeout information,
# and friendly error handling.
- rpc = gapic_v1.method.wrap_method(
- self._transport.list_budgets,
- default_timeout=None,
- client_info=_client_info,
- )
+ rpc = self._transport._wrapped_methods[self._transport.list_budgets]
# Certain fields should be provided within the metadata header;
# add these here.
@@ -503,15 +607,16 @@ def delete_budget(
"""
# Create or coerce a protobuf request object.
- request = budget_service.DeleteBudgetRequest(request)
+ # Minor optimization to avoid making a copy if the user passes
+ # in a budget_service.DeleteBudgetRequest.
+ # There's no risk of modifying the input as we've already verified
+ # there are no flattened fields.
+ if not isinstance(request, budget_service.DeleteBudgetRequest):
+ request = budget_service.DeleteBudgetRequest(request)
# Wrap the RPC method; this adds retry and timeout information,
# and friendly error handling.
- rpc = gapic_v1.method.wrap_method(
- self._transport.delete_budget,
- default_timeout=None,
- client_info=_client_info,
- )
+ rpc = self._transport._wrapped_methods[self._transport.delete_budget]
# Certain fields should be provided within the metadata header;
# add these here.
@@ -526,13 +631,13 @@ def delete_budget(
try:
- _client_info = gapic_v1.client_info.ClientInfo(
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
gapic_version=pkg_resources.get_distribution(
"google-cloud-billing-budgets",
).version,
)
except pkg_resources.DistributionNotFound:
- _client_info = gapic_v1.client_info.ClientInfo()
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo()
__all__ = ("BudgetServiceClient",)
diff --git a/google/cloud/billing/budgets_v1beta1/services/budget_service/transports/base.py b/google/cloud/billing/budgets_v1beta1/services/budget_service/transports/base.py
index 7927b36..5901b3f 100644
--- a/google/cloud/billing/budgets_v1beta1/services/budget_service/transports/base.py
+++ b/google/cloud/billing/budgets_v1beta1/services/budget_service/transports/base.py
@@ -17,9 +17,12 @@
import abc
import typing
+import pkg_resources
-from google import auth
+from google import auth # type: ignore
from google.api_core import exceptions # type: ignore
+from google.api_core import gapic_v1 # type: ignore
+from google.api_core import retry as retries # type: ignore
from google.auth import credentials # type: ignore
from google.cloud.billing.budgets_v1beta1.types import budget_model
@@ -27,10 +30,23 @@
from google.protobuf import empty_pb2 as empty # type: ignore
+try:
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
+ gapic_version=pkg_resources.get_distribution(
+ "google-cloud-billing-budgets",
+ ).version,
+ )
+except pkg_resources.DistributionNotFound:
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo()
+
+
class BudgetServiceTransport(abc.ABC):
"""Abstract transport class for BudgetService."""
- AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)
+ AUTH_SCOPES = (
+ "https://www.googleapis.com/auth/cloud-billing",
+ "https://www.googleapis.com/auth/cloud-platform",
+ )
def __init__(
self,
@@ -39,6 +55,8 @@ def __init__(
credentials: credentials.Credentials = None,
credentials_file: typing.Optional[str] = None,
scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES,
+ quota_project_id: typing.Optional[str] = None,
+ client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
**kwargs,
) -> None:
"""Instantiate the transport.
@@ -54,6 +72,13 @@ def __init__(
be loaded with :func:`google.auth.load_credentials_from_file`.
This argument is mutually exclusive with credentials.
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
+ your own client library.
"""
# Save the hostname. Default to port 443 (HTTPS) if none is specified.
if ":" not in host:
@@ -69,14 +94,80 @@ def __init__(
if credentials_file is not None:
credentials, _ = auth.load_credentials_from_file(
- credentials_file, scopes=scopes
+ credentials_file, scopes=scopes, quota_project_id=quota_project_id
)
+
elif credentials is None:
- credentials, _ = auth.default(scopes=scopes)
+ credentials, _ = auth.default(
+ scopes=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 = {
+ self.create_budget: gapic_v1.method.wrap_method(
+ self.create_budget, default_timeout=60.0, client_info=client_info,
+ ),
+ self.update_budget: gapic_v1.method.wrap_method(
+ self.update_budget,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.DeadlineExceeded, exceptions.ServiceUnavailable,
+ ),
+ ),
+ default_timeout=60.0,
+ client_info=client_info,
+ ),
+ self.get_budget: gapic_v1.method.wrap_method(
+ self.get_budget,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.DeadlineExceeded, exceptions.ServiceUnavailable,
+ ),
+ ),
+ default_timeout=60.0,
+ client_info=client_info,
+ ),
+ self.list_budgets: gapic_v1.method.wrap_method(
+ self.list_budgets,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.DeadlineExceeded, exceptions.ServiceUnavailable,
+ ),
+ ),
+ default_timeout=60.0,
+ client_info=client_info,
+ ),
+ self.delete_budget: gapic_v1.method.wrap_method(
+ self.delete_budget,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.DeadlineExceeded, exceptions.ServiceUnavailable,
+ ),
+ ),
+ default_timeout=60.0,
+ client_info=client_info,
+ ),
+ }
+
@property
def create_budget(
self,
diff --git a/google/cloud/billing/budgets_v1beta1/services/budget_service/transports/grpc.py b/google/cloud/billing/budgets_v1beta1/services/budget_service/transports/grpc.py
index 84e78cc..d926f9a 100644
--- a/google/cloud/billing/budgets_v1beta1/services/budget_service/transports/grpc.py
+++ b/google/cloud/billing/budgets_v1beta1/services/budget_service/transports/grpc.py
@@ -15,21 +15,22 @@
# limitations under the License.
#
+import warnings
from typing import Callable, Dict, Optional, Sequence, Tuple
from google.api_core import grpc_helpers # type: ignore
+from google.api_core import gapic_v1 # type: ignore
from google import auth # type: ignore
from google.auth import credentials # type: ignore
from google.auth.transport.grpc import SslCredentials # type: ignore
-
import grpc # type: ignore
from google.cloud.billing.budgets_v1beta1.types import budget_model
from google.cloud.billing.budgets_v1beta1.types import budget_service
from google.protobuf import empty_pb2 as empty # type: ignore
-from .base import BudgetServiceTransport
+from .base import BudgetServiceTransport, DEFAULT_CLIENT_INFO
class BudgetServiceGrpcTransport(BudgetServiceTransport):
@@ -58,7 +59,10 @@ def __init__(
scopes: Sequence[str] = None,
channel: grpc.Channel = None,
api_mtls_endpoint: str = None,
- client_cert_source: Callable[[], Tuple[bytes, bytes]] = None
+ client_cert_source: Callable[[], Tuple[bytes, bytes]] = None,
+ ssl_channel_credentials: grpc.ChannelCredentials = None,
+ quota_project_id: Optional[str] = None,
+ client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
) -> None:
"""Instantiate the transport.
@@ -77,14 +81,23 @@ def __init__(
ignored if ``channel`` is provided.
channel (Optional[grpc.Channel]): A ``Channel`` instance through
which to make calls.
- api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If
- provided, it overrides the ``host`` argument and tries to create
+ api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
+ If provided, it overrides the ``host`` argument and tries to create
a mutual TLS channel with client SSL credentials from
``client_cert_source`` or applicatin default SSL credentials.
- client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A
- callback to provide client SSL certificate bytes and private key
- bytes, both in PEM format. It is ignored if ``api_mtls_endpoint``
- is None.
+ client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
+ Deprecated. A callback to provide client SSL certificate bytes and
+ private key bytes, both in PEM format. It is ignored if
+ ``api_mtls_endpoint`` is None.
+ ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
+ for grpc channel. It is ignored if ``channel`` is provided.
+ 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
+ your own client library.
Raises:
google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
@@ -92,6 +105,8 @@ def __init__(
google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
and ``credentials_file`` are passed.
"""
+ self._ssl_channel_credentials = ssl_channel_credentials
+
if channel:
# Sanity check: Ensure that channel and credentials are not both
# provided.
@@ -99,7 +114,13 @@ def __init__(
# 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
@@ -107,7 +128,9 @@ def __init__(
)
if credentials is None:
- credentials, _ = auth.default(scopes=self.AUTH_SCOPES)
+ 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.
@@ -126,7 +149,28 @@ def __init__(
credentials_file=credentials_file,
ssl_credentials=ssl_credentials,
scopes=scopes or self.AUTH_SCOPES,
+ quota_project_id=quota_project_id,
)
+ 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
+ )
+
+ # 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_channel_credentials,
+ scopes=scopes or self.AUTH_SCOPES,
+ quota_project_id=quota_project_id,
+ )
+
+ self._stubs = {} # type: Dict[str, Callable]
# Run the base constructor.
super().__init__(
@@ -134,10 +178,10 @@ def __init__(
credentials=credentials,
credentials_file=credentials_file,
scopes=scopes or self.AUTH_SCOPES,
+ quota_project_id=quota_project_id,
+ client_info=client_info,
)
- self._stubs = {} # type: Dict[str, Callable]
-
@classmethod
def create_channel(
cls,
@@ -145,7 +189,8 @@ def create_channel(
credentials: credentials.Credentials = None,
credentials_file: str = None,
scopes: Optional[Sequence[str]] = None,
- **kwargs
+ quota_project_id: Optional[str] = None,
+ **kwargs,
) -> grpc.Channel:
"""Create and return a gRPC channel object.
Args:
@@ -161,6 +206,8 @@ def create_channel(
scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
service. These are only used when credentials are not specified and
are passed to :func:`google.auth.default`.
+ quota_project_id (Optional[str]): An optional project to use for billing
+ and quota.
kwargs (Optional[dict]): Keyword arguments, which are passed to the
channel creation.
Returns:
@@ -176,24 +223,14 @@ def create_channel(
credentials=credentials,
credentials_file=credentials_file,
scopes=scopes,
- **kwargs
+ quota_project_id=quota_project_id,
+ **kwargs,
)
@property
def grpc_channel(self) -> grpc.Channel:
- """Create the channel designed to connect to this service.
-
- This property caches on the instance; repeated calls return
- the same channel.
+ """Return the channel designed to connect to this service.
"""
- # Sanity check: Only create a new channel if we do not already
- # have one.
- if not hasattr(self, "_grpc_channel"):
- self._grpc_channel = self.create_channel(
- self._host, credentials=self._credentials,
- )
-
- # Return the channel from cache.
return self._grpc_channel
@property
diff --git a/google/cloud/billing/budgets_v1beta1/services/budget_service/transports/grpc_asyncio.py b/google/cloud/billing/budgets_v1beta1/services/budget_service/transports/grpc_asyncio.py
index 602a735..d0f6a21 100644
--- a/google/cloud/billing/budgets_v1beta1/services/budget_service/transports/grpc_asyncio.py
+++ b/google/cloud/billing/budgets_v1beta1/services/budget_service/transports/grpc_asyncio.py
@@ -15,9 +15,12 @@
# limitations under the License.
#
+import warnings
from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple
+from google.api_core import gapic_v1 # type: ignore
from google.api_core import grpc_helpers_async # type: ignore
+from google import auth # type: ignore
from google.auth import credentials # type: ignore
from google.auth.transport.grpc import SslCredentials # type: ignore
@@ -28,7 +31,7 @@
from google.cloud.billing.budgets_v1beta1.types import budget_service
from google.protobuf import empty_pb2 as empty # type: ignore
-from .base import BudgetServiceTransport
+from .base import BudgetServiceTransport, DEFAULT_CLIENT_INFO
from .grpc import BudgetServiceGrpcTransport
@@ -57,7 +60,8 @@ def create_channel(
credentials: credentials.Credentials = None,
credentials_file: Optional[str] = None,
scopes: Optional[Sequence[str]] = None,
- **kwargs
+ quota_project_id: Optional[str] = None,
+ **kwargs,
) -> aio.Channel:
"""Create and return a gRPC AsyncIO channel object.
Args:
@@ -73,6 +77,8 @@ def create_channel(
scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
service. These are only used when credentials are not specified and
are passed to :func:`google.auth.default`.
+ quota_project_id (Optional[str]): An optional project to use for billing
+ and quota.
kwargs (Optional[dict]): Keyword arguments, which are passed to the
channel creation.
Returns:
@@ -84,7 +90,8 @@ def create_channel(
credentials=credentials,
credentials_file=credentials_file,
scopes=scopes,
- **kwargs
+ quota_project_id=quota_project_id,
+ **kwargs,
)
def __init__(
@@ -96,7 +103,10 @@ def __init__(
scopes: Optional[Sequence[str]] = None,
channel: aio.Channel = None,
api_mtls_endpoint: str = None,
- client_cert_source: Callable[[], Tuple[bytes, bytes]] = None
+ client_cert_source: Callable[[], Tuple[bytes, bytes]] = None,
+ ssl_channel_credentials: grpc.ChannelCredentials = None,
+ quota_project_id=None,
+ client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
) -> None:
"""Instantiate the transport.
@@ -116,14 +126,23 @@ def __init__(
are passed to :func:`google.auth.default`.
channel (Optional[aio.Channel]): A ``Channel`` instance through
which to make calls.
- api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If
- provided, it overrides the ``host`` argument and tries to create
+ api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint.
+ If provided, it overrides the ``host`` argument and tries to create
a mutual TLS channel with client SSL credentials from
``client_cert_source`` or applicatin default SSL credentials.
- client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A
- callback to provide client SSL certificate bytes and private key
- bytes, both in PEM format. It is ignored if ``api_mtls_endpoint``
- is None.
+ client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]):
+ Deprecated. A callback to provide client SSL certificate bytes and
+ private key bytes, both in PEM format. It is ignored if
+ ``api_mtls_endpoint`` is None.
+ ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials
+ for grpc channel. It is ignored if ``channel`` is provided.
+ 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
+ your own client library.
Raises:
google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
@@ -131,6 +150,8 @@ def __init__(
google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials``
and ``credentials_file`` are passed.
"""
+ self._ssl_channel_credentials = ssl_channel_credentials
+
if channel:
# Sanity check: Ensure that channel and credentials are not both
# provided.
@@ -138,13 +159,24 @@ def __init__(
# 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"
)
+ 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:
@@ -162,6 +194,25 @@ def __init__(
credentials_file=credentials_file,
ssl_credentials=ssl_credentials,
scopes=scopes or self.AUTH_SCOPES,
+ quota_project_id=quota_project_id,
+ )
+ 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
+ )
+
+ # 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_channel_credentials,
+ scopes=scopes or self.AUTH_SCOPES,
+ quota_project_id=quota_project_id,
)
# Run the base constructor.
@@ -170,6 +221,8 @@ def __init__(
credentials=credentials,
credentials_file=credentials_file,
scopes=scopes or self.AUTH_SCOPES,
+ quota_project_id=quota_project_id,
+ client_info=client_info,
)
self._stubs = {}
@@ -181,13 +234,6 @@ def grpc_channel(self) -> aio.Channel:
This property caches on the instance; repeated calls return
the same channel.
"""
- # Sanity check: Only create a new channel if we do not already
- # have one.
- if not hasattr(self, "_grpc_channel"):
- self._grpc_channel = self.create_channel(
- self._host, credentials=self._credentials,
- )
-
# Return the channel from cache.
return self._grpc_channel
diff --git a/google/cloud/billing/budgets_v1beta1/types/budget_model.py b/google/cloud/billing/budgets_v1beta1/types/budget_model.py
index 733068a..c5b7056 100644
--- a/google/cloud/billing/budgets_v1beta1/types/budget_model.py
+++ b/google/cloud/billing/budgets_v1beta1/types/budget_model.py
@@ -63,8 +63,8 @@ class Budget(proto.Message):
spend exceeds the specified percentages of the
budget.
all_updates_rule (~.budget_model.AllUpdatesRule):
- Optional. Rules to apply to all updates to the actual spend,
- regardless of the thresholds set in ``threshold_rules``.
+ Optional. Rules to apply to notifications
+ sent based on budget spend and thresholds.
etag (str):
Optional. Etag to validate that the object is
unchanged for a read-modify-write operation.
@@ -103,10 +103,12 @@ class BudgetAmount(proto.Message):
budget for the present period.
"""
- specified_amount = proto.Field(proto.MESSAGE, number=1, message=money.Money,)
+ specified_amount = proto.Field(
+ proto.MESSAGE, number=1, oneof="budget_amount", message=money.Money,
+ )
last_period_amount = proto.Field(
- proto.MESSAGE, number=2, message="LastPeriodAmount",
+ proto.MESSAGE, number=2, oneof="budget_amount", message="LastPeriodAmount",
)
@@ -153,14 +155,13 @@ class Basis(proto.Enum):
class AllUpdatesRule(proto.Message):
- r"""AllUpdatesRule defines notifications that are sent on every
- update to the billing account's spend, regardless of the
- thresholds defined using threshold rules.
+ r"""AllUpdatesRule defines notifications that are sent based on
+ budget spend and thresholds.
Attributes:
pubsub_topic (str):
- Required. The name of the Cloud Pub/Sub topic where budget
- related messages will be published, in the form
+ Optional. The name of the Pub/Sub topic where budget related
+ messages will be published, in the form
``projects/{project_id}/topics/{topic_id}``. Updates are
sent at regular intervals to the topic. The topic needs to
be created before the budget is created; see
@@ -169,18 +170,39 @@ class AllUpdatesRule(proto.Message):
``pubsub.topics.setIamPolicy`` permission on the topic when
it's set for a budget, otherwise, the API call will fail
with PERMISSION_DENIED. See
- https://cloud.google.com/pubsub/docs/access-control for more
- details on Pub/Sub roles and permissions.
+ https://cloud.google.com/billing/docs/how-to/budgets-programmatic-notifications
+ for more details on Pub/Sub roles and permissions.
schema_version (str):
- Required. The schema version of the notification. Only "1.0"
- is accepted. It represents the JSON schema as defined in
- https://cloud.google.com/billing/docs/how-to/budgets#notification_format
+ Optional. The schema version of the notification sent to
+ ``pubsub_topic``. Only "1.0" is accepted. It represents the
+ JSON schema as defined in
+ https://cloud.google.com/billing/docs/how-to/budgets-programmatic-notifications#notification_format
+ monitoring_notification_channels (Sequence[str]):
+ Optional. Targets to send notifications to when a threshold
+ is exceeded. This is in addition to default recipients who
+ have billing account IAM roles. The value is the full REST
+ resource name of a monitoring notification channel with the
+ form
+ ``projects/{project_id}/notificationChannels/{channel_id}``.
+ A maximum of 5 channels are allowed. See
+ https://cloud.google.com/billing/docs/how-to/budgets-notification-recipients
+ for more details.
+ disable_default_iam_recipients (bool):
+ Optional. When set to true, disables default
+ notifications sent when a threshold is exceeded.
+ Default notifications are sent to those with
+ Billing Account Administrator and Billing
+ Account User IAM roles for the target account.
"""
pubsub_topic = proto.Field(proto.STRING, number=1)
schema_version = proto.Field(proto.STRING, number=2)
+ monitoring_notification_channels = proto.RepeatedField(proto.STRING, number=3)
+
+ disable_default_iam_recipients = proto.Field(proto.BOOL, number=4)
+
class Filter(proto.Message):
r"""A filter for a budget, limiting the scope of the cost to
@@ -194,6 +216,15 @@ class Filter(proto.Message):
omitted, the report will include all usage for the billing
account, regardless of which project the usage occurred on.
Only zero or one project can be specified currently.
+ credit_types (Sequence[str]):
+ Optional. A list of credit types to be subtracted from gross
+ cost to determine the spend for threshold calculations if
+ and only if credit_types_treatment is
+ INCLUDE_SPECIFIED_CREDITS. If credit_types_treatment is not
+ INCLUDE_SPECIFIED_CREDITS, this field must be empty. See
+ credits.type at
+ https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables#data-schema
+ for a list of acceptable credit type values in this field.
credit_types_treatment (~.budget_model.Filter.CreditTypesTreatment):
Optional. If not set, default behavior is
``INCLUDE_ALL_CREDITS``.
@@ -208,17 +239,18 @@ class Filter(proto.Message):
Optional. A set of subaccounts of the form
``billingAccounts/{account_id}``, specifying that usage from
only this set of subaccounts should be included in the
- budget. If a subaccount is set to the name of the master
- account, usage from the master account will be included. If
- omitted, the report will include usage from the master
+ budget. If a subaccount is set to the name of the parent
+ account, usage from the parent account will be included. If
+ omitted, the report will include usage from the parent
account and all subaccounts, if they exist.
labels (Sequence[~.budget_model.Filter.LabelsEntry]):
Optional. A single label and value pair
specifying that usage from only this set of
labeled resources should be included in the
- budget. Multiple entries or multiple values per
- entry are not allowed. If omitted, the report
- will include all labeled and unlabeled usage.
+ budget. Currently, multiple entries or multiple
+ values per entry are not allowed. If omitted,
+ the report will include all labeled and
+ unlabeled usage.
"""
class CreditTypesTreatment(proto.Enum):
@@ -228,9 +260,12 @@ class CreditTypesTreatment(proto.Enum):
CREDIT_TYPES_TREATMENT_UNSPECIFIED = 0
INCLUDE_ALL_CREDITS = 1
EXCLUDE_ALL_CREDITS = 2
+ INCLUDE_SPECIFIED_CREDITS = 3
projects = proto.RepeatedField(proto.STRING, number=1)
+ credit_types = proto.RepeatedField(proto.STRING, number=7)
+
credit_types_treatment = proto.Field(
proto.ENUM, number=4, enum=CreditTypesTreatment,
)
diff --git a/noxfile.py b/noxfile.py
index 18abaac..45efcaa 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -72,7 +72,9 @@ def default(session):
# Install all test dependencies, then install this package in-place.
session.install("asyncmock", "pytest-asyncio")
- session.install("mock", "pytest", "pytest-cov")
+ session.install(
+ "mock", "pytest", "pytest-cov",
+ )
session.install("-e", ".")
# Run py.test against the unit tests.
@@ -102,6 +104,10 @@ def system(session):
"""Run the system test suite."""
system_test_path = os.path.join("tests", "system.py")
system_test_folder_path = os.path.join("tests", "system")
+
+ # Check the value of `RUN_SYSTEM_TESTS` env var. It defaults to true.
+ if os.environ.get("RUN_SYSTEM_TESTS", "true") == "false":
+ session.skip("RUN_SYSTEM_TESTS is set to false, skipping")
# 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")
@@ -147,7 +153,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(
@@ -162,3 +168,38 @@ def docs(session):
os.path.join("docs", ""),
os.path.join("docs", "_build", "html", ""),
)
+
+
+@nox.session(python=DEFAULT_PYTHON_VERSION)
+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")
+
+ shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True)
+ session.run(
+ "sphinx-build",
+ "-T", # show full traceback on exception
+ "-N", # no colors
+ "-D",
+ (
+ "extensions=sphinx.ext.autodoc,"
+ "sphinx.ext.autosummary,"
+ "docfx_yaml.extension,"
+ "sphinx.ext.intersphinx,"
+ "sphinx.ext.coverage,"
+ "sphinx.ext.napoleon,"
+ "sphinx.ext.todo,"
+ "sphinx.ext.viewcode,"
+ "recommonmark"
+ ),
+ "-b",
+ "html",
+ "-d",
+ os.path.join("docs", "_build", "doctrees", ""),
+ os.path.join("docs", ""),
+ os.path.join("docs", "_build", "html", ""),
+ )
diff --git a/scripts/decrypt-secrets.sh b/scripts/decrypt-secrets.sh
index ff599eb..21f6d2a 100755
--- a/scripts/decrypt-secrets.sh
+++ b/scripts/decrypt-secrets.sh
@@ -20,14 +20,27 @@ ROOT=$( dirname "$DIR" )
# Work from the project root.
cd $ROOT
+# Prevent it from overriding files.
+# We recommend that sample authors use their own service account files and cloud project.
+# In that case, they are supposed to prepare these files by themselves.
+if [[ -f "testing/test-env.sh" ]] || \
+ [[ -f "testing/service-account.json" ]] || \
+ [[ -f "testing/client-secrets.json" ]]; then
+ echo "One or more target files exist, aborting."
+ exit 1
+fi
+
# Use SECRET_MANAGER_PROJECT if set, fallback to cloud-devrel-kokoro-resources.
PROJECT_ID="${SECRET_MANAGER_PROJECT:-cloud-devrel-kokoro-resources}"
gcloud secrets versions access latest --secret="python-docs-samples-test-env" \
+ --project="${PROJECT_ID}" \
> testing/test-env.sh
gcloud secrets versions access latest \
--secret="python-docs-samples-service-account" \
+ --project="${PROJECT_ID}" \
> testing/service-account.json
gcloud secrets versions access latest \
--secret="python-docs-samples-client-secrets" \
- > testing/client-secrets.json
\ No newline at end of file
+ --project="${PROJECT_ID}" \
+ > testing/client-secrets.json
diff --git a/scripts/fixup_budgets_v1_keywords.py b/scripts/fixup_budgets_v1_keywords.py
new file mode 100644
index 0000000..ebf535e
--- /dev/null
+++ b/scripts/fixup_budgets_v1_keywords.py
@@ -0,0 +1,183 @@
+#! /usr/bin/env python3
+# -*- 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.
+#
+
+import argparse
+import os
+import libcst as cst
+import pathlib
+import sys
+from typing import (Any, Callable, Dict, List, Sequence, Tuple)
+
+
+def partition(
+ predicate: Callable[[Any], bool],
+ iterator: Sequence[Any]
+) -> Tuple[List[Any], List[Any]]:
+ """A stable, out-of-place partition."""
+ results = ([], [])
+
+ for i in iterator:
+ results[int(predicate(i))].append(i)
+
+ # Returns trueList, falseList
+ return results[1], results[0]
+
+
+class budgetsCallTransformer(cst.CSTTransformer):
+ CTRL_PARAMS: Tuple[str] = ('retry', 'timeout', 'metadata')
+ METHOD_TO_PARAMS: Dict[str, Tuple[str]] = {
+ 'create_budget': ('parent', 'budget', ),
+ 'delete_budget': ('name', ),
+ 'get_budget': ('name', ),
+ 'list_budgets': ('parent', 'page_size', 'page_token', ),
+ 'update_budget': ('budget', 'update_mask', ),
+
+ }
+
+ def leave_Call(self, original: cst.Call, updated: cst.Call) -> cst.CSTNode:
+ try:
+ key = original.func.attr.value
+ kword_params = self.METHOD_TO_PARAMS[key]
+ except (AttributeError, KeyError):
+ # Either not a method from the API or too convoluted to be sure.
+ return updated
+
+ # If the existing code is valid, keyword args come after positional args.
+ # Therefore, all positional args must map to the first parameters.
+ args, kwargs = partition(lambda a: not bool(a.keyword), updated.args)
+ if any(k.keyword.value == "request" for k in kwargs):
+ # We've already fixed this file, don't fix it again.
+ return updated
+
+ kwargs, ctrl_kwargs = partition(
+ lambda a: not a.keyword.value in self.CTRL_PARAMS,
+ kwargs
+ )
+
+ args, ctrl_args = args[:len(kword_params)], args[len(kword_params):]
+ ctrl_kwargs.extend(cst.Arg(value=a.value, keyword=cst.Name(value=ctrl))
+ for a, ctrl in zip(ctrl_args, self.CTRL_PARAMS))
+
+ request_arg = cst.Arg(
+ value=cst.Dict([
+ cst.DictElement(
+ cst.SimpleString("'{}'".format(name)),
+ cst.Element(value=arg.value)
+ )
+ # Note: the args + kwargs looks silly, but keep in mind that
+ # the control parameters had to be stripped out, and that
+ # those could have been passed positionally or by keyword.
+ for name, arg in zip(kword_params, args + kwargs)]),
+ keyword=cst.Name("request")
+ )
+
+ return updated.with_changes(
+ args=[request_arg] + ctrl_kwargs
+ )
+
+
+def fix_files(
+ in_dir: pathlib.Path,
+ out_dir: pathlib.Path,
+ *,
+ transformer=budgetsCallTransformer(),
+):
+ """Duplicate the input dir to the output dir, fixing file method calls.
+
+ Preconditions:
+ * in_dir is a real directory
+ * out_dir is a real, empty directory
+ """
+ pyfile_gen = (
+ pathlib.Path(os.path.join(root, f))
+ for root, _, files in os.walk(in_dir)
+ for f in files if os.path.splitext(f)[1] == ".py"
+ )
+
+ for fpath in pyfile_gen:
+ with open(fpath, 'r') as f:
+ src = f.read()
+
+ # Parse the code and insert method call fixes.
+ tree = cst.parse_module(src)
+ updated = tree.visit(transformer)
+
+ # Create the path and directory structure for the new file.
+ updated_path = out_dir.joinpath(fpath.relative_to(in_dir))
+ updated_path.parent.mkdir(parents=True, exist_ok=True)
+
+ # Generate the updated source file at the corresponding path.
+ with open(updated_path, 'w') as f:
+ f.write(updated.code)
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(
+ description="""Fix up source that uses the budgets client library.
+
+The existing sources are NOT overwritten but are copied to output_dir with changes made.
+
+Note: This tool operates at a best-effort level at converting positional
+ parameters in client method calls to keyword based parameters.
+ Cases where it WILL FAIL include
+ A) * or ** expansion in a method call.
+ B) Calls via function or method alias (includes free function calls)
+ C) Indirect or dispatched calls (e.g. the method is looked up dynamically)
+
+ These all constitute false negatives. The tool will also detect false
+ positives when an API method shares a name with another method.
+""")
+ parser.add_argument(
+ '-d',
+ '--input-directory',
+ required=True,
+ dest='input_dir',
+ help='the input directory to walk for python files to fix up',
+ )
+ parser.add_argument(
+ '-o',
+ '--output-directory',
+ required=True,
+ dest='output_dir',
+ help='the directory to output files fixed via un-flattening',
+ )
+ args = parser.parse_args()
+ input_dir = pathlib.Path(args.input_dir)
+ output_dir = pathlib.Path(args.output_dir)
+ if not input_dir.is_dir():
+ print(
+ f"input directory '{input_dir}' does not exist or is not a directory",
+ file=sys.stderr,
+ )
+ sys.exit(-1)
+
+ if not output_dir.is_dir():
+ print(
+ f"output directory '{output_dir}' does not exist or is not a directory",
+ file=sys.stderr,
+ )
+ sys.exit(-1)
+
+ if os.listdir(output_dir):
+ print(
+ f"output directory '{output_dir}' is not empty",
+ file=sys.stderr,
+ )
+ sys.exit(-1)
+
+ fix_files(input_dir, output_dir)
diff --git a/scripts/fixup_budgets_v1beta1_keywords.py b/scripts/fixup_budgets_v1beta1_keywords.py
index 68c6999..ebf535e 100644
--- a/scripts/fixup_budgets_v1beta1_keywords.py
+++ b/scripts/fixup_budgets_v1beta1_keywords.py
@@ -1,3 +1,4 @@
+#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2020 Google LLC
@@ -45,6 +46,7 @@ class budgetsCallTransformer(cst.CSTTransformer):
'get_budget': ('name', ),
'list_budgets': ('parent', 'page_size', 'page_token', ),
'update_budget': ('budget', 'update_mask', ),
+
}
def leave_Call(self, original: cst.Call, updated: cst.Call) -> cst.CSTNode:
diff --git a/setup.py b/setup.py
index 5ab1523..f288ea0 100644
--- a/setup.py
+++ b/setup.py
@@ -25,8 +25,8 @@
version = "1.0.1"
release_status = "Development Status :: 4 - Beta"
dependencies = [
- "google-api-core[grpc] >= 1.21.0, < 2.0.0dev",
- "proto-plus >= 0.4.0",
+ "google-api-core[grpc] >= 1.22.0, < 2.0.0dev",
+ "proto-plus >= 1.10.0",
"libcst >= 0.2.5",
]
diff --git a/synth.metadata b/synth.metadata
index 205947a..8dd8c4c 100644
--- a/synth.metadata
+++ b/synth.metadata
@@ -3,15 +3,15 @@
{
"git": {
"name": ".",
- "remote": "https://github.com/googleapis/python-billingbudgets.git",
- "sha": "d2165d55e63ebb803bf18b9597e17bee8792f8a1"
+ "remote": "git@github.com:googleapis/python-billingbudgets",
+ "sha": "b3f7a148ff069f5eede18f0f9d04c7c6174caae4"
}
},
{
"git": {
"name": "synthtool",
"remote": "https://github.com/googleapis/synthtool.git",
- "sha": "d82deccf657a66e31bd5da9efdb96c6fa322fc7e"
+ "sha": "c7824ea48ff6d4d42dfae0849aec8a85acd90bd9"
}
}
],
@@ -24,6 +24,15 @@
"language": "python",
"generator": "bazel"
}
+ },
+ {
+ "client": {
+ "source": "googleapis",
+ "apiName": "billing_budgets",
+ "apiVersion": "v1",
+ "language": "python",
+ "generator": "bazel"
+ }
}
]
}
\ No newline at end of file
diff --git a/synth.py b/synth.py
index 558a3a8..dc98aec 100644
--- a/synth.py
+++ b/synth.py
@@ -26,22 +26,24 @@
# ----------------------------------------------------------------------------
# Generate billing budgets GAPIC layer
# ----------------------------------------------------------------------------
-library = gapic.py_library(
- service="billing_budgets",
- version="v1beta1",
- bazel_target="//google/cloud/billing/budgets/v1beta1:billing-budgets-v1beta1-py",
- include_protos=True,
- proto_output_path=f"google/cloud/billing/budgets_v1beta1/proto"
-)
+versions = ["v1beta1", "v1"]
+for version in versions:
+ library = gapic.py_library(
+ service="billing_budgets",
+ version=f"{version}",
+ bazel_target=f"//google/cloud/billing/budgets/{version}:billing-budgets-{version}-py",
+ include_protos=True,
+ proto_output_path=f"google/cloud/billing/budgets_{version}/proto"
+ )
-excludes = [
- "nox.py",
- "setup.py",
- "README.rst",
- library / "docs/index.rst",
-]
+ excludes = [
+ "nox.py",
+ "setup.py",
+ "README.rst",
+ library / "docs/index.rst",
+ ]
-s.move(library, excludes=excludes)
+ s.move(library, excludes=excludes)
# ----------------------------------------------------------------------------
# Add templated files
@@ -53,9 +55,6 @@
)
s.move(templated_files, excludes=[".coveragerc"]) # microgenerator has a good .coveragerc file
-# TODO(busunkim): Use latest sphinx after microgenerator transition
-s.replace("noxfile.py", """['"]sphinx['"]""", '"sphinx<3.0.0"')
-
# Update the namespace in noxfile.py
s.replace(
"noxfile.py",
diff --git a/tests/unit/gapic/budgets_v1/__init__.py b/tests/unit/gapic/budgets_v1/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tests/unit/gapic/budgets_v1/__init__.py
@@ -0,0 +1 @@
+
diff --git a/tests/unit/gapic/budgets_v1/test_budget_service.py b/tests/unit/gapic/budgets_v1/test_budget_service.py
new file mode 100644
index 0000000..324fe39
--- /dev/null
+++ b/tests/unit/gapic/budgets_v1/test_budget_service.py
@@ -0,0 +1,2030 @@
+# -*- 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.
+#
+
+import os
+import mock
+
+import grpc
+from grpc.experimental import aio
+import math
+import pytest
+from proto.marshal.rules.dates import DurationRule, TimestampRule
+
+from google import auth
+from google.api_core import client_options
+from google.api_core import exceptions
+from google.api_core import gapic_v1
+from google.api_core import grpc_helpers
+from google.api_core import grpc_helpers_async
+from google.auth import credentials
+from google.auth.exceptions import MutualTLSChannelError
+from google.cloud.billing.budgets_v1.services.budget_service import (
+ BudgetServiceAsyncClient,
+)
+from google.cloud.billing.budgets_v1.services.budget_service import BudgetServiceClient
+from google.cloud.billing.budgets_v1.services.budget_service import pagers
+from google.cloud.billing.budgets_v1.services.budget_service import transports
+from google.cloud.billing.budgets_v1.types import budget_model
+from google.cloud.billing.budgets_v1.types import budget_service
+from google.oauth2 import service_account
+from google.protobuf import field_mask_pb2 as field_mask # type: ignore
+from google.protobuf import struct_pb2 as struct # type: ignore
+from google.type import money_pb2 as money # type: ignore
+
+
+def client_cert_source_callback():
+ return b"cert bytes", b"key bytes"
+
+
+# If default endpoint is localhost, then default mtls endpoint will be the same.
+# This method modifies the default endpoint so the client can produce a different
+# mtls endpoint for endpoint testing purposes.
+def modify_default_endpoint(client):
+ return (
+ "foo.googleapis.com"
+ if ("localhost" in client.DEFAULT_ENDPOINT)
+ else client.DEFAULT_ENDPOINT
+ )
+
+
+def test__get_default_mtls_endpoint():
+ api_endpoint = "example.googleapis.com"
+ api_mtls_endpoint = "example.mtls.googleapis.com"
+ sandbox_endpoint = "example.sandbox.googleapis.com"
+ sandbox_mtls_endpoint = "example.mtls.sandbox.googleapis.com"
+ non_googleapi = "api.example.com"
+
+ assert BudgetServiceClient._get_default_mtls_endpoint(None) is None
+ assert (
+ BudgetServiceClient._get_default_mtls_endpoint(api_endpoint)
+ == api_mtls_endpoint
+ )
+ assert (
+ BudgetServiceClient._get_default_mtls_endpoint(api_mtls_endpoint)
+ == api_mtls_endpoint
+ )
+ assert (
+ BudgetServiceClient._get_default_mtls_endpoint(sandbox_endpoint)
+ == sandbox_mtls_endpoint
+ )
+ assert (
+ BudgetServiceClient._get_default_mtls_endpoint(sandbox_mtls_endpoint)
+ == sandbox_mtls_endpoint
+ )
+ assert (
+ BudgetServiceClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi
+ )
+
+
+@pytest.mark.parametrize(
+ "client_class", [BudgetServiceClient, BudgetServiceAsyncClient]
+)
+def test_budget_service_client_from_service_account_file(client_class):
+ creds = credentials.AnonymousCredentials()
+ with mock.patch.object(
+ service_account.Credentials, "from_service_account_file"
+ ) as factory:
+ factory.return_value = creds
+ client = client_class.from_service_account_file("dummy/file/path.json")
+ assert client.transport._credentials == creds
+
+ client = client_class.from_service_account_json("dummy/file/path.json")
+ assert client.transport._credentials == creds
+
+ assert client.transport._host == "billingbudgets.googleapis.com:443"
+
+
+def test_budget_service_client_get_transport_class():
+ transport = BudgetServiceClient.get_transport_class()
+ assert transport == transports.BudgetServiceGrpcTransport
+
+ transport = BudgetServiceClient.get_transport_class("grpc")
+ assert transport == transports.BudgetServiceGrpcTransport
+
+
+@pytest.mark.parametrize(
+ "client_class,transport_class,transport_name",
+ [
+ (BudgetServiceClient, transports.BudgetServiceGrpcTransport, "grpc"),
+ (
+ BudgetServiceAsyncClient,
+ transports.BudgetServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ ),
+ ],
+)
+@mock.patch.object(
+ BudgetServiceClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(BudgetServiceClient),
+)
+@mock.patch.object(
+ BudgetServiceAsyncClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(BudgetServiceAsyncClient),
+)
+def test_budget_service_client_client_options(
+ client_class, transport_class, transport_name
+):
+ # Check that if channel is provided we won't create a new one.
+ with mock.patch.object(BudgetServiceClient, "get_transport_class") as gtc:
+ transport = transport_class(credentials=credentials.AnonymousCredentials())
+ client = client_class(transport=transport)
+ gtc.assert_not_called()
+
+ # Check that if channel is provided via str we will create a new one.
+ with mock.patch.object(BudgetServiceClient, "get_transport_class") as gtc:
+ client = client_class(transport=transport_name)
+ gtc.assert_called()
+
+ # Check the case api_endpoint is provided.
+ options = client_options.ClientOptions(api_endpoint="squid.clam.whelk")
+ with mock.patch.object(transport_class, "__init__") as patched:
+ patched.return_value = None
+ client = client_class(client_options=options)
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host="squid.clam.whelk",
+ scopes=None,
+ ssl_channel_credentials=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+ # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is
+ # "never".
+ with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}):
+ with mock.patch.object(transport_class, "__init__") as patched:
+ patched.return_value = None
+ client = client_class()
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host=client.DEFAULT_ENDPOINT,
+ scopes=None,
+ ssl_channel_credentials=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+ # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is
+ # "always".
+ with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}):
+ with mock.patch.object(transport_class, "__init__") as patched:
+ patched.return_value = None
+ client = client_class()
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host=client.DEFAULT_MTLS_ENDPOINT,
+ scopes=None,
+ ssl_channel_credentials=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+ # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has
+ # unsupported value.
+ with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}):
+ with pytest.raises(MutualTLSChannelError):
+ client = client_class()
+
+ # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value.
+ with mock.patch.dict(
+ os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"}
+ ):
+ with pytest.raises(ValueError):
+ client = client_class()
+
+ # Check the case quota_project_id is provided
+ options = client_options.ClientOptions(quota_project_id="octopus")
+ with mock.patch.object(transport_class, "__init__") as patched:
+ patched.return_value = None
+ client = client_class(client_options=options)
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host=client.DEFAULT_ENDPOINT,
+ scopes=None,
+ ssl_channel_credentials=None,
+ quota_project_id="octopus",
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+
+@pytest.mark.parametrize(
+ "client_class,transport_class,transport_name,use_client_cert_env",
+ [
+ (BudgetServiceClient, transports.BudgetServiceGrpcTransport, "grpc", "true"),
+ (
+ BudgetServiceAsyncClient,
+ transports.BudgetServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ "true",
+ ),
+ (BudgetServiceClient, transports.BudgetServiceGrpcTransport, "grpc", "false"),
+ (
+ BudgetServiceAsyncClient,
+ transports.BudgetServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ "false",
+ ),
+ ],
+)
+@mock.patch.object(
+ BudgetServiceClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(BudgetServiceClient),
+)
+@mock.patch.object(
+ BudgetServiceAsyncClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(BudgetServiceAsyncClient),
+)
+@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"})
+def test_budget_service_client_mtls_env_auto(
+ client_class, transport_class, transport_name, use_client_cert_env
+):
+ # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default
+ # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists.
+
+ # Check the case client_cert_source is provided. Whether client cert is used depends on
+ # GOOGLE_API_USE_CLIENT_CERTIFICATE value.
+ with mock.patch.dict(
+ os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env}
+ ):
+ options = client_options.ClientOptions(
+ client_cert_source=client_cert_source_callback
+ )
+ with mock.patch.object(transport_class, "__init__") as patched:
+ 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)
+
+ 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
+
+ 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,
+ )
+
+ # Check the case ADC client cert is provided. Whether client cert is used depends on
+ # GOOGLE_API_USE_CLIENT_CERTIFICATE value.
+ with mock.patch.dict(
+ os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env}
+ ):
+ with mock.patch.object(transport_class, "__init__") as patched:
+ with mock.patch(
+ "google.auth.transport.grpc.SslCredentials.__init__", return_value=None
+ ):
+ 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,
+ )
+
+ # 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,
+ scopes=None,
+ ssl_channel_credentials=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+
+@pytest.mark.parametrize(
+ "client_class,transport_class,transport_name",
+ [
+ (BudgetServiceClient, transports.BudgetServiceGrpcTransport, "grpc"),
+ (
+ BudgetServiceAsyncClient,
+ transports.BudgetServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ ),
+ ],
+)
+def test_budget_service_client_client_options_scopes(
+ client_class, transport_class, transport_name
+):
+ # Check the case scopes are provided.
+ options = client_options.ClientOptions(scopes=["1", "2"],)
+ with mock.patch.object(transport_class, "__init__") as patched:
+ patched.return_value = None
+ client = client_class(client_options=options)
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host=client.DEFAULT_ENDPOINT,
+ scopes=["1", "2"],
+ ssl_channel_credentials=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+
+@pytest.mark.parametrize(
+ "client_class,transport_class,transport_name",
+ [
+ (BudgetServiceClient, transports.BudgetServiceGrpcTransport, "grpc"),
+ (
+ BudgetServiceAsyncClient,
+ transports.BudgetServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ ),
+ ],
+)
+def test_budget_service_client_client_options_credentials_file(
+ client_class, transport_class, transport_name
+):
+ # Check the case credentials file is provided.
+ options = client_options.ClientOptions(credentials_file="credentials.json")
+ with mock.patch.object(transport_class, "__init__") as patched:
+ patched.return_value = None
+ client = client_class(client_options=options)
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file="credentials.json",
+ host=client.DEFAULT_ENDPOINT,
+ scopes=None,
+ ssl_channel_credentials=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+
+def test_budget_service_client_client_options_from_dict():
+ with mock.patch(
+ "google.cloud.billing.budgets_v1.services.budget_service.transports.BudgetServiceGrpcTransport.__init__"
+ ) as grpc_transport:
+ grpc_transport.return_value = None
+ client = BudgetServiceClient(
+ client_options={"api_endpoint": "squid.clam.whelk"}
+ )
+ grpc_transport.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host="squid.clam.whelk",
+ scopes=None,
+ ssl_channel_credentials=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+
+def test_create_budget(
+ transport: str = "grpc", request_type=budget_service.CreateBudgetRequest
+):
+ client = BudgetServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # Everything is optional in proto3 as far as the runtime is concerned,
+ # and we are mocking out the actual API, so just send an empty request.
+ request = request_type()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.create_budget), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = budget_model.Budget(
+ name="name_value", display_name="display_name_value", etag="etag_value",
+ )
+
+ response = client.create_budget(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == budget_service.CreateBudgetRequest()
+
+ # Establish that the response is the type that we expect.
+
+ assert isinstance(response, budget_model.Budget)
+
+ assert response.name == "name_value"
+
+ assert response.display_name == "display_name_value"
+
+ assert response.etag == "etag_value"
+
+
+def test_create_budget_from_dict():
+ test_create_budget(request_type=dict)
+
+
+@pytest.mark.asyncio
+async def test_create_budget_async(
+ transport: str = "grpc_asyncio", request_type=budget_service.CreateBudgetRequest
+):
+ client = BudgetServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # Everything is optional in proto3 as far as the runtime is concerned,
+ # and we are mocking out the actual API, so just send an empty request.
+ request = request_type()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.create_budget), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ budget_model.Budget(
+ name="name_value", display_name="display_name_value", etag="etag_value",
+ )
+ )
+
+ response = await client.create_budget(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == budget_service.CreateBudgetRequest()
+
+ # Establish that the response is the type that we expect.
+ assert isinstance(response, budget_model.Budget)
+
+ assert response.name == "name_value"
+
+ assert response.display_name == "display_name_value"
+
+ assert response.etag == "etag_value"
+
+
+@pytest.mark.asyncio
+async def test_create_budget_async_from_dict():
+ await test_create_budget_async(request_type=dict)
+
+
+def test_create_budget_field_headers():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Any value that is part of the HTTP/1.1 URI should be sent as
+ # a field header. Set these to a non-empty value.
+ request = budget_service.CreateBudgetRequest()
+ request.parent = "parent/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.create_budget), "__call__") as call:
+ call.return_value = budget_model.Budget()
+
+ client.create_budget(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+ assert args[0] == request
+
+ # Establish that the field header was sent.
+ _, _, kw = call.mock_calls[0]
+ assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"]
+
+
+@pytest.mark.asyncio
+async def test_create_budget_field_headers_async():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Any value that is part of the HTTP/1.1 URI should be sent as
+ # a field header. Set these to a non-empty value.
+ request = budget_service.CreateBudgetRequest()
+ request.parent = "parent/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.create_budget), "__call__") as call:
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(budget_model.Budget())
+
+ await client.create_budget(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+ assert args[0] == request
+
+ # Establish that the field header was sent.
+ _, _, kw = call.mock_calls[0]
+ assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"]
+
+
+def test_create_budget_flattened():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.create_budget), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = budget_model.Budget()
+
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ client.create_budget(
+ parent="parent_value", budget=budget_model.Budget(name="name_value"),
+ )
+
+ # Establish that the underlying call was made with the expected
+ # request object values.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0].parent == "parent_value"
+
+ assert args[0].budget == budget_model.Budget(name="name_value")
+
+
+def test_create_budget_flattened_error():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ client.create_budget(
+ budget_service.CreateBudgetRequest(),
+ parent="parent_value",
+ budget=budget_model.Budget(name="name_value"),
+ )
+
+
+@pytest.mark.asyncio
+async def test_create_budget_flattened_async():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.create_budget), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = budget_model.Budget()
+
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(budget_model.Budget())
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ response = await client.create_budget(
+ parent="parent_value", budget=budget_model.Budget(name="name_value"),
+ )
+
+ # Establish that the underlying call was made with the expected
+ # request object values.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0].parent == "parent_value"
+
+ assert args[0].budget == budget_model.Budget(name="name_value")
+
+
+@pytest.mark.asyncio
+async def test_create_budget_flattened_error_async():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ await client.create_budget(
+ budget_service.CreateBudgetRequest(),
+ parent="parent_value",
+ budget=budget_model.Budget(name="name_value"),
+ )
+
+
+def test_update_budget(
+ transport: str = "grpc", request_type=budget_service.UpdateBudgetRequest
+):
+ client = BudgetServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # Everything is optional in proto3 as far as the runtime is concerned,
+ # and we are mocking out the actual API, so just send an empty request.
+ request = request_type()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.update_budget), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = budget_model.Budget(
+ name="name_value", display_name="display_name_value", etag="etag_value",
+ )
+
+ response = client.update_budget(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == budget_service.UpdateBudgetRequest()
+
+ # Establish that the response is the type that we expect.
+
+ assert isinstance(response, budget_model.Budget)
+
+ assert response.name == "name_value"
+
+ assert response.display_name == "display_name_value"
+
+ assert response.etag == "etag_value"
+
+
+def test_update_budget_from_dict():
+ test_update_budget(request_type=dict)
+
+
+@pytest.mark.asyncio
+async def test_update_budget_async(
+ transport: str = "grpc_asyncio", request_type=budget_service.UpdateBudgetRequest
+):
+ client = BudgetServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # Everything is optional in proto3 as far as the runtime is concerned,
+ # and we are mocking out the actual API, so just send an empty request.
+ request = request_type()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.update_budget), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ budget_model.Budget(
+ name="name_value", display_name="display_name_value", etag="etag_value",
+ )
+ )
+
+ response = await client.update_budget(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == budget_service.UpdateBudgetRequest()
+
+ # Establish that the response is the type that we expect.
+ assert isinstance(response, budget_model.Budget)
+
+ assert response.name == "name_value"
+
+ assert response.display_name == "display_name_value"
+
+ assert response.etag == "etag_value"
+
+
+@pytest.mark.asyncio
+async def test_update_budget_async_from_dict():
+ await test_update_budget_async(request_type=dict)
+
+
+def test_update_budget_field_headers():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Any value that is part of the HTTP/1.1 URI should be sent as
+ # a field header. Set these to a non-empty value.
+ request = budget_service.UpdateBudgetRequest()
+ request.budget.name = "budget.name/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.update_budget), "__call__") as call:
+ call.return_value = budget_model.Budget()
+
+ client.update_budget(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+ assert args[0] == request
+
+ # Establish that the field header was sent.
+ _, _, kw = call.mock_calls[0]
+ assert ("x-goog-request-params", "budget.name=budget.name/value",) in kw["metadata"]
+
+
+@pytest.mark.asyncio
+async def test_update_budget_field_headers_async():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Any value that is part of the HTTP/1.1 URI should be sent as
+ # a field header. Set these to a non-empty value.
+ request = budget_service.UpdateBudgetRequest()
+ request.budget.name = "budget.name/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.update_budget), "__call__") as call:
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(budget_model.Budget())
+
+ await client.update_budget(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+ assert args[0] == request
+
+ # Establish that the field header was sent.
+ _, _, kw = call.mock_calls[0]
+ assert ("x-goog-request-params", "budget.name=budget.name/value",) in kw["metadata"]
+
+
+def test_update_budget_flattened():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.update_budget), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = budget_model.Budget()
+
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ client.update_budget(
+ budget=budget_model.Budget(name="name_value"),
+ update_mask=field_mask.FieldMask(paths=["paths_value"]),
+ )
+
+ # Establish that the underlying call was made with the expected
+ # request object values.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0].budget == budget_model.Budget(name="name_value")
+
+ assert args[0].update_mask == field_mask.FieldMask(paths=["paths_value"])
+
+
+def test_update_budget_flattened_error():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ client.update_budget(
+ budget_service.UpdateBudgetRequest(),
+ budget=budget_model.Budget(name="name_value"),
+ update_mask=field_mask.FieldMask(paths=["paths_value"]),
+ )
+
+
+@pytest.mark.asyncio
+async def test_update_budget_flattened_async():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.update_budget), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = budget_model.Budget()
+
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(budget_model.Budget())
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ response = await client.update_budget(
+ budget=budget_model.Budget(name="name_value"),
+ update_mask=field_mask.FieldMask(paths=["paths_value"]),
+ )
+
+ # Establish that the underlying call was made with the expected
+ # request object values.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0].budget == budget_model.Budget(name="name_value")
+
+ assert args[0].update_mask == field_mask.FieldMask(paths=["paths_value"])
+
+
+@pytest.mark.asyncio
+async def test_update_budget_flattened_error_async():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ await client.update_budget(
+ budget_service.UpdateBudgetRequest(),
+ budget=budget_model.Budget(name="name_value"),
+ update_mask=field_mask.FieldMask(paths=["paths_value"]),
+ )
+
+
+def test_get_budget(
+ transport: str = "grpc", request_type=budget_service.GetBudgetRequest
+):
+ client = BudgetServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # Everything is optional in proto3 as far as the runtime is concerned,
+ # and we are mocking out the actual API, so just send an empty request.
+ request = request_type()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.get_budget), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = budget_model.Budget(
+ name="name_value", display_name="display_name_value", etag="etag_value",
+ )
+
+ response = client.get_budget(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == budget_service.GetBudgetRequest()
+
+ # Establish that the response is the type that we expect.
+
+ assert isinstance(response, budget_model.Budget)
+
+ assert response.name == "name_value"
+
+ assert response.display_name == "display_name_value"
+
+ assert response.etag == "etag_value"
+
+
+def test_get_budget_from_dict():
+ test_get_budget(request_type=dict)
+
+
+@pytest.mark.asyncio
+async def test_get_budget_async(
+ transport: str = "grpc_asyncio", request_type=budget_service.GetBudgetRequest
+):
+ client = BudgetServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # Everything is optional in proto3 as far as the runtime is concerned,
+ # and we are mocking out the actual API, so just send an empty request.
+ request = request_type()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.get_budget), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ budget_model.Budget(
+ name="name_value", display_name="display_name_value", etag="etag_value",
+ )
+ )
+
+ response = await client.get_budget(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == budget_service.GetBudgetRequest()
+
+ # Establish that the response is the type that we expect.
+ assert isinstance(response, budget_model.Budget)
+
+ assert response.name == "name_value"
+
+ assert response.display_name == "display_name_value"
+
+ assert response.etag == "etag_value"
+
+
+@pytest.mark.asyncio
+async def test_get_budget_async_from_dict():
+ await test_get_budget_async(request_type=dict)
+
+
+def test_get_budget_field_headers():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Any value that is part of the HTTP/1.1 URI should be sent as
+ # a field header. Set these to a non-empty value.
+ request = budget_service.GetBudgetRequest()
+ request.name = "name/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.get_budget), "__call__") as call:
+ call.return_value = budget_model.Budget()
+
+ client.get_budget(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+ assert args[0] == request
+
+ # Establish that the field header was sent.
+ _, _, kw = call.mock_calls[0]
+ assert ("x-goog-request-params", "name=name/value",) in kw["metadata"]
+
+
+@pytest.mark.asyncio
+async def test_get_budget_field_headers_async():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Any value that is part of the HTTP/1.1 URI should be sent as
+ # a field header. Set these to a non-empty value.
+ request = budget_service.GetBudgetRequest()
+ request.name = "name/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.get_budget), "__call__") as call:
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(budget_model.Budget())
+
+ await client.get_budget(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+ assert args[0] == request
+
+ # Establish that the field header was sent.
+ _, _, kw = call.mock_calls[0]
+ assert ("x-goog-request-params", "name=name/value",) in kw["metadata"]
+
+
+def test_get_budget_flattened():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.get_budget), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = budget_model.Budget()
+
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ client.get_budget(name="name_value",)
+
+ # Establish that the underlying call was made with the expected
+ # request object values.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0].name == "name_value"
+
+
+def test_get_budget_flattened_error():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ client.get_budget(
+ budget_service.GetBudgetRequest(), name="name_value",
+ )
+
+
+@pytest.mark.asyncio
+async def test_get_budget_flattened_async():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.get_budget), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = budget_model.Budget()
+
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(budget_model.Budget())
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ response = await client.get_budget(name="name_value",)
+
+ # Establish that the underlying call was made with the expected
+ # request object values.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0].name == "name_value"
+
+
+@pytest.mark.asyncio
+async def test_get_budget_flattened_error_async():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ await client.get_budget(
+ budget_service.GetBudgetRequest(), name="name_value",
+ )
+
+
+def test_list_budgets(
+ transport: str = "grpc", request_type=budget_service.ListBudgetsRequest
+):
+ client = BudgetServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # Everything is optional in proto3 as far as the runtime is concerned,
+ # and we are mocking out the actual API, so just send an empty request.
+ request = request_type()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.list_budgets), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = budget_service.ListBudgetsResponse(
+ next_page_token="next_page_token_value",
+ )
+
+ response = client.list_budgets(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == budget_service.ListBudgetsRequest()
+
+ # Establish that the response is the type that we expect.
+
+ assert isinstance(response, pagers.ListBudgetsPager)
+
+ assert response.next_page_token == "next_page_token_value"
+
+
+def test_list_budgets_from_dict():
+ test_list_budgets(request_type=dict)
+
+
+@pytest.mark.asyncio
+async def test_list_budgets_async(
+ transport: str = "grpc_asyncio", request_type=budget_service.ListBudgetsRequest
+):
+ client = BudgetServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # Everything is optional in proto3 as far as the runtime is concerned,
+ # and we are mocking out the actual API, so just send an empty request.
+ request = request_type()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.list_budgets), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ budget_service.ListBudgetsResponse(next_page_token="next_page_token_value",)
+ )
+
+ response = await client.list_budgets(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == budget_service.ListBudgetsRequest()
+
+ # Establish that the response is the type that we expect.
+ assert isinstance(response, pagers.ListBudgetsAsyncPager)
+
+ assert response.next_page_token == "next_page_token_value"
+
+
+@pytest.mark.asyncio
+async def test_list_budgets_async_from_dict():
+ await test_list_budgets_async(request_type=dict)
+
+
+def test_list_budgets_field_headers():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Any value that is part of the HTTP/1.1 URI should be sent as
+ # a field header. Set these to a non-empty value.
+ request = budget_service.ListBudgetsRequest()
+ request.parent = "parent/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.list_budgets), "__call__") as call:
+ call.return_value = budget_service.ListBudgetsResponse()
+
+ client.list_budgets(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+ assert args[0] == request
+
+ # Establish that the field header was sent.
+ _, _, kw = call.mock_calls[0]
+ assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"]
+
+
+@pytest.mark.asyncio
+async def test_list_budgets_field_headers_async():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Any value that is part of the HTTP/1.1 URI should be sent as
+ # a field header. Set these to a non-empty value.
+ request = budget_service.ListBudgetsRequest()
+ request.parent = "parent/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.list_budgets), "__call__") as call:
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ budget_service.ListBudgetsResponse()
+ )
+
+ await client.list_budgets(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+ assert args[0] == request
+
+ # Establish that the field header was sent.
+ _, _, kw = call.mock_calls[0]
+ assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"]
+
+
+def test_list_budgets_flattened():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.list_budgets), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = budget_service.ListBudgetsResponse()
+
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ client.list_budgets(parent="parent_value",)
+
+ # Establish that the underlying call was made with the expected
+ # request object values.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0].parent == "parent_value"
+
+
+def test_list_budgets_flattened_error():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ client.list_budgets(
+ budget_service.ListBudgetsRequest(), parent="parent_value",
+ )
+
+
+@pytest.mark.asyncio
+async def test_list_budgets_flattened_async():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.list_budgets), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = budget_service.ListBudgetsResponse()
+
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ budget_service.ListBudgetsResponse()
+ )
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ response = await client.list_budgets(parent="parent_value",)
+
+ # Establish that the underlying call was made with the expected
+ # request object values.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0].parent == "parent_value"
+
+
+@pytest.mark.asyncio
+async def test_list_budgets_flattened_error_async():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ await client.list_budgets(
+ budget_service.ListBudgetsRequest(), parent="parent_value",
+ )
+
+
+def test_list_budgets_pager():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials,)
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.list_budgets), "__call__") as call:
+ # Set the response to a series of pages.
+ call.side_effect = (
+ budget_service.ListBudgetsResponse(
+ budgets=[
+ budget_model.Budget(),
+ budget_model.Budget(),
+ budget_model.Budget(),
+ ],
+ next_page_token="abc",
+ ),
+ budget_service.ListBudgetsResponse(budgets=[], next_page_token="def",),
+ budget_service.ListBudgetsResponse(
+ budgets=[budget_model.Budget(),], next_page_token="ghi",
+ ),
+ budget_service.ListBudgetsResponse(
+ budgets=[budget_model.Budget(), budget_model.Budget(),],
+ ),
+ RuntimeError,
+ )
+
+ metadata = ()
+ metadata = tuple(metadata) + (
+ gapic_v1.routing_header.to_grpc_metadata((("parent", ""),)),
+ )
+ pager = client.list_budgets(request={})
+
+ assert pager._metadata == metadata
+
+ results = [i for i in pager]
+ assert len(results) == 6
+ assert all(isinstance(i, budget_model.Budget) for i in results)
+
+
+def test_list_budgets_pages():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials,)
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.list_budgets), "__call__") as call:
+ # Set the response to a series of pages.
+ call.side_effect = (
+ budget_service.ListBudgetsResponse(
+ budgets=[
+ budget_model.Budget(),
+ budget_model.Budget(),
+ budget_model.Budget(),
+ ],
+ next_page_token="abc",
+ ),
+ budget_service.ListBudgetsResponse(budgets=[], next_page_token="def",),
+ budget_service.ListBudgetsResponse(
+ budgets=[budget_model.Budget(),], next_page_token="ghi",
+ ),
+ budget_service.ListBudgetsResponse(
+ budgets=[budget_model.Budget(), budget_model.Budget(),],
+ ),
+ RuntimeError,
+ )
+ pages = list(client.list_budgets(request={}).pages)
+ for page_, token in zip(pages, ["abc", "def", "ghi", ""]):
+ assert page_.raw_page.next_page_token == token
+
+
+@pytest.mark.asyncio
+async def test_list_budgets_async_pager():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials,)
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client.transport.list_budgets), "__call__", new_callable=mock.AsyncMock
+ ) as call:
+ # Set the response to a series of pages.
+ call.side_effect = (
+ budget_service.ListBudgetsResponse(
+ budgets=[
+ budget_model.Budget(),
+ budget_model.Budget(),
+ budget_model.Budget(),
+ ],
+ next_page_token="abc",
+ ),
+ budget_service.ListBudgetsResponse(budgets=[], next_page_token="def",),
+ budget_service.ListBudgetsResponse(
+ budgets=[budget_model.Budget(),], next_page_token="ghi",
+ ),
+ budget_service.ListBudgetsResponse(
+ budgets=[budget_model.Budget(), budget_model.Budget(),],
+ ),
+ RuntimeError,
+ )
+ async_pager = await client.list_budgets(request={},)
+ assert async_pager.next_page_token == "abc"
+ responses = []
+ async for response in async_pager:
+ responses.append(response)
+
+ assert len(responses) == 6
+ assert all(isinstance(i, budget_model.Budget) for i in responses)
+
+
+@pytest.mark.asyncio
+async def test_list_budgets_async_pages():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials,)
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client.transport.list_budgets), "__call__", new_callable=mock.AsyncMock
+ ) as call:
+ # Set the response to a series of pages.
+ call.side_effect = (
+ budget_service.ListBudgetsResponse(
+ budgets=[
+ budget_model.Budget(),
+ budget_model.Budget(),
+ budget_model.Budget(),
+ ],
+ next_page_token="abc",
+ ),
+ budget_service.ListBudgetsResponse(budgets=[], next_page_token="def",),
+ budget_service.ListBudgetsResponse(
+ budgets=[budget_model.Budget(),], next_page_token="ghi",
+ ),
+ budget_service.ListBudgetsResponse(
+ budgets=[budget_model.Budget(), budget_model.Budget(),],
+ ),
+ RuntimeError,
+ )
+ pages = []
+ async for page_ in (await client.list_budgets(request={})).pages:
+ pages.append(page_)
+ for page_, token in zip(pages, ["abc", "def", "ghi", ""]):
+ assert page_.raw_page.next_page_token == token
+
+
+def test_delete_budget(
+ transport: str = "grpc", request_type=budget_service.DeleteBudgetRequest
+):
+ client = BudgetServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # Everything is optional in proto3 as far as the runtime is concerned,
+ # and we are mocking out the actual API, so just send an empty request.
+ request = request_type()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.delete_budget), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = None
+
+ response = client.delete_budget(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == budget_service.DeleteBudgetRequest()
+
+ # Establish that the response is the type that we expect.
+ assert response is None
+
+
+def test_delete_budget_from_dict():
+ test_delete_budget(request_type=dict)
+
+
+@pytest.mark.asyncio
+async def test_delete_budget_async(
+ transport: str = "grpc_asyncio", request_type=budget_service.DeleteBudgetRequest
+):
+ client = BudgetServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # Everything is optional in proto3 as far as the runtime is concerned,
+ # and we are mocking out the actual API, so just send an empty request.
+ request = request_type()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.delete_budget), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None)
+
+ response = await client.delete_budget(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == budget_service.DeleteBudgetRequest()
+
+ # Establish that the response is the type that we expect.
+ assert response is None
+
+
+@pytest.mark.asyncio
+async def test_delete_budget_async_from_dict():
+ await test_delete_budget_async(request_type=dict)
+
+
+def test_delete_budget_field_headers():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Any value that is part of the HTTP/1.1 URI should be sent as
+ # a field header. Set these to a non-empty value.
+ request = budget_service.DeleteBudgetRequest()
+ request.name = "name/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.delete_budget), "__call__") as call:
+ call.return_value = None
+
+ client.delete_budget(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+ assert args[0] == request
+
+ # Establish that the field header was sent.
+ _, _, kw = call.mock_calls[0]
+ assert ("x-goog-request-params", "name=name/value",) in kw["metadata"]
+
+
+@pytest.mark.asyncio
+async def test_delete_budget_field_headers_async():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Any value that is part of the HTTP/1.1 URI should be sent as
+ # a field header. Set these to a non-empty value.
+ request = budget_service.DeleteBudgetRequest()
+ request.name = "name/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.delete_budget), "__call__") as call:
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None)
+
+ await client.delete_budget(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+ assert args[0] == request
+
+ # Establish that the field header was sent.
+ _, _, kw = call.mock_calls[0]
+ assert ("x-goog-request-params", "name=name/value",) in kw["metadata"]
+
+
+def test_delete_budget_flattened():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.delete_budget), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = None
+
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ client.delete_budget(name="name_value",)
+
+ # Establish that the underlying call was made with the expected
+ # request object values.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0].name == "name_value"
+
+
+def test_delete_budget_flattened_error():
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ client.delete_budget(
+ budget_service.DeleteBudgetRequest(), name="name_value",
+ )
+
+
+@pytest.mark.asyncio
+async def test_delete_budget_flattened_async():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client.transport.delete_budget), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = None
+
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None)
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ response = await client.delete_budget(name="name_value",)
+
+ # Establish that the underlying call was made with the expected
+ # request object values.
+ assert len(call.mock_calls)
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0].name == "name_value"
+
+
+@pytest.mark.asyncio
+async def test_delete_budget_flattened_error_async():
+ client = BudgetServiceAsyncClient(credentials=credentials.AnonymousCredentials(),)
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ await client.delete_budget(
+ budget_service.DeleteBudgetRequest(), name="name_value",
+ )
+
+
+def test_credentials_transport_error():
+ # It is an error to provide credentials and a transport instance.
+ transport = transports.BudgetServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ with pytest.raises(ValueError):
+ client = BudgetServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # It is an error to provide a credentials file and a transport instance.
+ transport = transports.BudgetServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ with pytest.raises(ValueError):
+ client = BudgetServiceClient(
+ client_options={"credentials_file": "credentials.json"},
+ transport=transport,
+ )
+
+ # It is an error to provide scopes and a transport instance.
+ transport = transports.BudgetServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ with pytest.raises(ValueError):
+ client = BudgetServiceClient(
+ client_options={"scopes": ["1", "2"]}, transport=transport,
+ )
+
+
+def test_transport_instance():
+ # A client may be instantiated with a custom transport instance.
+ transport = transports.BudgetServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ client = BudgetServiceClient(transport=transport)
+ assert client.transport is transport
+
+
+def test_transport_get_channel():
+ # A client may be instantiated with a custom transport instance.
+ transport = transports.BudgetServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ channel = transport.grpc_channel
+ assert channel
+
+ transport = transports.BudgetServiceGrpcAsyncIOTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ channel = transport.grpc_channel
+ assert channel
+
+
+@pytest.mark.parametrize(
+ "transport_class",
+ [
+ transports.BudgetServiceGrpcTransport,
+ transports.BudgetServiceGrpcAsyncIOTransport,
+ ],
+)
+def test_transport_adc(transport_class):
+ # Test default credentials are used if not provided.
+ with mock.patch.object(auth, "default") as adc:
+ adc.return_value = (credentials.AnonymousCredentials(), None)
+ transport_class()
+ adc.assert_called_once()
+
+
+def test_transport_grpc_default():
+ # A client should use the gRPC transport by default.
+ client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
+ assert isinstance(client.transport, transports.BudgetServiceGrpcTransport,)
+
+
+def test_budget_service_base_transport_error():
+ # Passing both a credentials object and credentials_file should raise an error
+ with pytest.raises(exceptions.DuplicateCredentialArgs):
+ transport = transports.BudgetServiceTransport(
+ credentials=credentials.AnonymousCredentials(),
+ credentials_file="credentials.json",
+ )
+
+
+def test_budget_service_base_transport():
+ # Instantiate the base transport.
+ with mock.patch(
+ "google.cloud.billing.budgets_v1.services.budget_service.transports.BudgetServiceTransport.__init__"
+ ) as Transport:
+ Transport.return_value = None
+ transport = transports.BudgetServiceTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Every method on the transport should just blindly
+ # raise NotImplementedError.
+ methods = (
+ "create_budget",
+ "update_budget",
+ "get_budget",
+ "list_budgets",
+ "delete_budget",
+ )
+ for method in methods:
+ with pytest.raises(NotImplementedError):
+ getattr(transport, method)(request=object())
+
+
+def test_budget_service_base_transport_with_credentials_file():
+ # Instantiate the base transport with a credentials file
+ with mock.patch.object(
+ auth, "load_credentials_from_file"
+ ) as load_creds, mock.patch(
+ "google.cloud.billing.budgets_v1.services.budget_service.transports.BudgetServiceTransport._prep_wrapped_messages"
+ ) as Transport:
+ Transport.return_value = None
+ load_creds.return_value = (credentials.AnonymousCredentials(), None)
+ transport = transports.BudgetServiceTransport(
+ credentials_file="credentials.json", quota_project_id="octopus",
+ )
+ load_creds.assert_called_once_with(
+ "credentials.json",
+ scopes=(
+ "https://www.googleapis.com/auth/cloud-billing",
+ "https://www.googleapis.com/auth/cloud-platform",
+ ),
+ quota_project_id="octopus",
+ )
+
+
+def test_budget_service_base_transport_with_adc():
+ # Test the default credentials are used if credentials and credentials_file are None.
+ with mock.patch.object(auth, "default") as adc, mock.patch(
+ "google.cloud.billing.budgets_v1.services.budget_service.transports.BudgetServiceTransport._prep_wrapped_messages"
+ ) as Transport:
+ Transport.return_value = None
+ adc.return_value = (credentials.AnonymousCredentials(), None)
+ transport = transports.BudgetServiceTransport()
+ adc.assert_called_once()
+
+
+def test_budget_service_auth_adc():
+ # If no credentials are provided, we should use ADC credentials.
+ with mock.patch.object(auth, "default") as adc:
+ adc.return_value = (credentials.AnonymousCredentials(), None)
+ BudgetServiceClient()
+ adc.assert_called_once_with(
+ scopes=(
+ "https://www.googleapis.com/auth/cloud-billing",
+ "https://www.googleapis.com/auth/cloud-platform",
+ ),
+ quota_project_id=None,
+ )
+
+
+def test_budget_service_transport_auth_adc():
+ # If credentials and host are not provided, the transport class should use
+ # ADC credentials.
+ with mock.patch.object(auth, "default") as adc:
+ adc.return_value = (credentials.AnonymousCredentials(), None)
+ transports.BudgetServiceGrpcTransport(
+ host="squid.clam.whelk", quota_project_id="octopus"
+ )
+ adc.assert_called_once_with(
+ scopes=(
+ "https://www.googleapis.com/auth/cloud-billing",
+ "https://www.googleapis.com/auth/cloud-platform",
+ ),
+ quota_project_id="octopus",
+ )
+
+
+def test_budget_service_host_no_port():
+ client = BudgetServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ client_options=client_options.ClientOptions(
+ api_endpoint="billingbudgets.googleapis.com"
+ ),
+ )
+ assert client.transport._host == "billingbudgets.googleapis.com:443"
+
+
+def test_budget_service_host_with_port():
+ client = BudgetServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ client_options=client_options.ClientOptions(
+ api_endpoint="billingbudgets.googleapis.com:8000"
+ ),
+ )
+ assert client.transport._host == "billingbudgets.googleapis.com:8000"
+
+
+def test_budget_service_grpc_transport_channel():
+ channel = grpc.insecure_channel("http://localhost/")
+
+ # Check that channel is used if provided.
+ transport = transports.BudgetServiceGrpcTransport(
+ host="squid.clam.whelk", channel=channel,
+ )
+ assert transport.grpc_channel == channel
+ assert transport._host == "squid.clam.whelk:443"
+ assert transport._ssl_channel_credentials == None
+
+
+def test_budget_service_grpc_asyncio_transport_channel():
+ channel = aio.insecure_channel("http://localhost/")
+
+ # Check that channel is used if provided.
+ transport = transports.BudgetServiceGrpcAsyncIOTransport(
+ host="squid.clam.whelk", channel=channel,
+ )
+ assert transport.grpc_channel == channel
+ assert transport._host == "squid.clam.whelk:443"
+ assert transport._ssl_channel_credentials == None
+
+
+@pytest.mark.parametrize(
+ "transport_class",
+ [
+ transports.BudgetServiceGrpcTransport,
+ transports.BudgetServiceGrpcAsyncIOTransport,
+ ],
+)
+def test_budget_service_transport_channel_mtls_with_client_cert_source(transport_class):
+ with mock.patch(
+ "grpc.ssl_channel_credentials", autospec=True
+ ) as grpc_ssl_channel_cred:
+ with mock.patch.object(
+ transport_class, "create_channel", autospec=True
+ ) as grpc_create_channel:
+ mock_ssl_cred = mock.Mock()
+ grpc_ssl_channel_cred.return_value = mock_ssl_cred
+
+ mock_grpc_channel = mock.Mock()
+ grpc_create_channel.return_value = mock_grpc_channel
+
+ cred = credentials.AnonymousCredentials()
+ with pytest.warns(DeprecationWarning):
+ with mock.patch.object(auth, "default") as adc:
+ adc.return_value = (cred, None)
+ transport = transport_class(
+ host="squid.clam.whelk",
+ api_mtls_endpoint="mtls.squid.clam.whelk",
+ client_cert_source=client_cert_source_callback,
+ )
+ adc.assert_called_once()
+
+ grpc_ssl_channel_cred.assert_called_once_with(
+ certificate_chain=b"cert bytes", private_key=b"key bytes"
+ )
+ grpc_create_channel.assert_called_once_with(
+ "mtls.squid.clam.whelk:443",
+ credentials=cred,
+ credentials_file=None,
+ scopes=(
+ "https://www.googleapis.com/auth/cloud-billing",
+ "https://www.googleapis.com/auth/cloud-platform",
+ ),
+ ssl_credentials=mock_ssl_cred,
+ quota_project_id=None,
+ )
+ assert transport.grpc_channel == mock_grpc_channel
+ assert transport._ssl_channel_credentials == mock_ssl_cred
+
+
+@pytest.mark.parametrize(
+ "transport_class",
+ [
+ transports.BudgetServiceGrpcTransport,
+ transports.BudgetServiceGrpcAsyncIOTransport,
+ ],
+)
+def test_budget_service_transport_channel_mtls_with_adc(transport_class):
+ mock_ssl_cred = mock.Mock()
+ with mock.patch.multiple(
+ "google.auth.transport.grpc.SslCredentials",
+ __init__=mock.Mock(return_value=None),
+ ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred),
+ ):
+ with mock.patch.object(
+ transport_class, "create_channel", autospec=True
+ ) as grpc_create_channel:
+ mock_grpc_channel = mock.Mock()
+ grpc_create_channel.return_value = mock_grpc_channel
+ mock_cred = mock.Mock()
+
+ with pytest.warns(DeprecationWarning):
+ transport = transport_class(
+ host="squid.clam.whelk",
+ credentials=mock_cred,
+ api_mtls_endpoint="mtls.squid.clam.whelk",
+ client_cert_source=None,
+ )
+
+ grpc_create_channel.assert_called_once_with(
+ "mtls.squid.clam.whelk:443",
+ credentials=mock_cred,
+ credentials_file=None,
+ scopes=(
+ "https://www.googleapis.com/auth/cloud-billing",
+ "https://www.googleapis.com/auth/cloud-platform",
+ ),
+ ssl_credentials=mock_ssl_cred,
+ quota_project_id=None,
+ )
+ assert transport.grpc_channel == mock_grpc_channel
+
+
+def test_budget_path():
+ billing_account = "squid"
+ budget = "clam"
+
+ expected = "billingAccounts/{billing_account}/budgets/{budget}".format(
+ billing_account=billing_account, budget=budget,
+ )
+ actual = BudgetServiceClient.budget_path(billing_account, budget)
+ assert expected == actual
+
+
+def test_parse_budget_path():
+ expected = {
+ "billing_account": "whelk",
+ "budget": "octopus",
+ }
+ path = BudgetServiceClient.budget_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = BudgetServiceClient.parse_budget_path(path)
+ assert expected == actual
+
+
+def test_common_billing_account_path():
+ billing_account = "oyster"
+
+ expected = "billingAccounts/{billing_account}".format(
+ billing_account=billing_account,
+ )
+ actual = BudgetServiceClient.common_billing_account_path(billing_account)
+ assert expected == actual
+
+
+def test_parse_common_billing_account_path():
+ expected = {
+ "billing_account": "nudibranch",
+ }
+ path = BudgetServiceClient.common_billing_account_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = BudgetServiceClient.parse_common_billing_account_path(path)
+ assert expected == actual
+
+
+def test_common_folder_path():
+ folder = "cuttlefish"
+
+ expected = "folders/{folder}".format(folder=folder,)
+ actual = BudgetServiceClient.common_folder_path(folder)
+ assert expected == actual
+
+
+def test_parse_common_folder_path():
+ expected = {
+ "folder": "mussel",
+ }
+ path = BudgetServiceClient.common_folder_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = BudgetServiceClient.parse_common_folder_path(path)
+ assert expected == actual
+
+
+def test_common_organization_path():
+ organization = "winkle"
+
+ expected = "organizations/{organization}".format(organization=organization,)
+ actual = BudgetServiceClient.common_organization_path(organization)
+ assert expected == actual
+
+
+def test_parse_common_organization_path():
+ expected = {
+ "organization": "nautilus",
+ }
+ path = BudgetServiceClient.common_organization_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = BudgetServiceClient.parse_common_organization_path(path)
+ assert expected == actual
+
+
+def test_common_project_path():
+ project = "scallop"
+
+ expected = "projects/{project}".format(project=project,)
+ actual = BudgetServiceClient.common_project_path(project)
+ assert expected == actual
+
+
+def test_parse_common_project_path():
+ expected = {
+ "project": "abalone",
+ }
+ path = BudgetServiceClient.common_project_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = BudgetServiceClient.parse_common_project_path(path)
+ assert expected == actual
+
+
+def test_common_location_path():
+ project = "squid"
+ location = "clam"
+
+ expected = "projects/{project}/locations/{location}".format(
+ project=project, location=location,
+ )
+ actual = BudgetServiceClient.common_location_path(project, location)
+ assert expected == actual
+
+
+def test_parse_common_location_path():
+ expected = {
+ "project": "whelk",
+ "location": "octopus",
+ }
+ path = BudgetServiceClient.common_location_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = BudgetServiceClient.parse_common_location_path(path)
+ assert expected == actual
+
+
+def test_client_withDEFAULT_CLIENT_INFO():
+ client_info = gapic_v1.client_info.ClientInfo()
+
+ with mock.patch.object(
+ transports.BudgetServiceTransport, "_prep_wrapped_messages"
+ ) as prep:
+ client = BudgetServiceClient(
+ credentials=credentials.AnonymousCredentials(), client_info=client_info,
+ )
+ prep.assert_called_once_with(client_info)
+
+ with mock.patch.object(
+ transports.BudgetServiceTransport, "_prep_wrapped_messages"
+ ) as prep:
+ transport_class = BudgetServiceClient.get_transport_class()
+ transport = transport_class(
+ credentials=credentials.AnonymousCredentials(), client_info=client_info,
+ )
+ prep.assert_called_once_with(client_info)
diff --git a/tests/unit/gapic/budgets_v1beta1/__init__.py b/tests/unit/gapic/budgets_v1beta1/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tests/unit/gapic/budgets_v1beta1/__init__.py
@@ -0,0 +1 @@
+
diff --git a/tests/unit/gapic/budgets_v1beta1/test_budget_service.py b/tests/unit/gapic/budgets_v1beta1/test_budget_service.py
index 14953e2..79ffd3b 100644
--- a/tests/unit/gapic/budgets_v1beta1/test_budget_service.py
+++ b/tests/unit/gapic/budgets_v1beta1/test_budget_service.py
@@ -22,6 +22,7 @@
from grpc.experimental import aio
import math
import pytest
+from proto.marshal.rules.dates import DurationRule, TimestampRule
from google import auth
from google.api_core import client_options
@@ -51,6 +52,17 @@ def client_cert_source_callback():
return b"cert bytes", b"key bytes"
+# If default endpoint is localhost, then default mtls endpoint will be the same.
+# This method modifies the default endpoint so the client can produce a different
+# mtls endpoint for endpoint testing purposes.
+def modify_default_endpoint(client):
+ return (
+ "foo.googleapis.com"
+ if ("localhost" in client.DEFAULT_ENDPOINT)
+ else client.DEFAULT_ENDPOINT
+ )
+
+
def test__get_default_mtls_endpoint():
api_endpoint = "example.googleapis.com"
api_mtls_endpoint = "example.mtls.googleapis.com"
@@ -90,12 +102,12 @@ def test_budget_service_client_from_service_account_file(client_class):
) as factory:
factory.return_value = creds
client = client_class.from_service_account_file("dummy/file/path.json")
- assert client._transport._credentials == creds
+ assert client.transport._credentials == creds
client = client_class.from_service_account_json("dummy/file/path.json")
- assert client._transport._credentials == creds
+ assert client.transport._credentials == creds
- assert client._transport._host == "billingbudgets.googleapis.com:443"
+ assert client.transport._host == "billingbudgets.googleapis.com:443"
def test_budget_service_client_get_transport_class():
@@ -117,6 +129,16 @@ def test_budget_service_client_get_transport_class():
),
],
)
+@mock.patch.object(
+ BudgetServiceClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(BudgetServiceClient),
+)
+@mock.patch.object(
+ BudgetServiceAsyncClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(BudgetServiceAsyncClient),
+)
def test_budget_service_client_client_options(
client_class, transport_class, transport_name
):
@@ -141,103 +163,207 @@ def test_budget_service_client_client_options(
credentials_file=None,
host="squid.clam.whelk",
scopes=None,
- api_mtls_endpoint="squid.clam.whelk",
- client_cert_source=None,
+ ssl_channel_credentials=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
)
- # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is
+ # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is
# "never".
- os.environ["GOOGLE_API_USE_MTLS"] = "never"
- with mock.patch.object(transport_class, "__init__") as patched:
- patched.return_value = None
- client = client_class()
- patched.assert_called_once_with(
- credentials=None,
- credentials_file=None,
- host=client.DEFAULT_ENDPOINT,
- scopes=None,
- api_mtls_endpoint=client.DEFAULT_ENDPOINT,
- client_cert_source=None,
- )
-
- # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is
- # "always".
- os.environ["GOOGLE_API_USE_MTLS"] = "always"
- with mock.patch.object(transport_class, "__init__") as patched:
- patched.return_value = None
- client = client_class()
- patched.assert_called_once_with(
- credentials=None,
- credentials_file=None,
- host=client.DEFAULT_MTLS_ENDPOINT,
- scopes=None,
- api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT,
- client_cert_source=None,
- )
-
- # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is
- # "auto", and client_cert_source is provided.
- os.environ["GOOGLE_API_USE_MTLS"] = "auto"
- options = client_options.ClientOptions(
- client_cert_source=client_cert_source_callback
- )
- with mock.patch.object(transport_class, "__init__") as patched:
- patched.return_value = None
- client = client_class(client_options=options)
- patched.assert_called_once_with(
- credentials=None,
- credentials_file=None,
- host=client.DEFAULT_MTLS_ENDPOINT,
- scopes=None,
- api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT,
- client_cert_source=client_cert_source_callback,
- )
-
- # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is
- # "auto", and default_client_cert_source is provided.
- os.environ["GOOGLE_API_USE_MTLS"] = "auto"
- with mock.patch.object(transport_class, "__init__") as patched:
- with mock.patch(
- "google.auth.transport.mtls.has_default_client_cert_source",
- return_value=True,
- ):
+ with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}):
+ with mock.patch.object(transport_class, "__init__") as patched:
patched.return_value = None
client = client_class()
patched.assert_called_once_with(
credentials=None,
credentials_file=None,
- host=client.DEFAULT_MTLS_ENDPOINT,
+ host=client.DEFAULT_ENDPOINT,
scopes=None,
- api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT,
- client_cert_source=None,
+ ssl_channel_credentials=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
)
- # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is
- # "auto", but client_cert_source and default_client_cert_source are None.
- os.environ["GOOGLE_API_USE_MTLS"] = "auto"
- with mock.patch.object(transport_class, "__init__") as patched:
- with mock.patch(
- "google.auth.transport.mtls.has_default_client_cert_source",
- return_value=False,
- ):
+ # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is
+ # "always".
+ with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}):
+ with mock.patch.object(transport_class, "__init__") as patched:
patched.return_value = None
client = client_class()
patched.assert_called_once_with(
credentials=None,
credentials_file=None,
- host=client.DEFAULT_ENDPOINT,
+ host=client.DEFAULT_MTLS_ENDPOINT,
scopes=None,
- api_mtls_endpoint=client.DEFAULT_ENDPOINT,
- client_cert_source=None,
+ ssl_channel_credentials=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
)
- # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has
+ # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has
# unsupported value.
- os.environ["GOOGLE_API_USE_MTLS"] = "Unsupported"
- with pytest.raises(MutualTLSChannelError):
- client = client_class()
+ with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}):
+ with pytest.raises(MutualTLSChannelError):
+ client = client_class()
+
+ # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value.
+ with mock.patch.dict(
+ os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"}
+ ):
+ with pytest.raises(ValueError):
+ client = client_class()
+
+ # Check the case quota_project_id is provided
+ options = client_options.ClientOptions(quota_project_id="octopus")
+ with mock.patch.object(transport_class, "__init__") as patched:
+ patched.return_value = None
+ client = client_class(client_options=options)
+ patched.assert_called_once_with(
+ credentials=None,
+ credentials_file=None,
+ host=client.DEFAULT_ENDPOINT,
+ scopes=None,
+ ssl_channel_credentials=None,
+ quota_project_id="octopus",
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
+
+
+@pytest.mark.parametrize(
+ "client_class,transport_class,transport_name,use_client_cert_env",
+ [
+ (BudgetServiceClient, transports.BudgetServiceGrpcTransport, "grpc", "true"),
+ (
+ BudgetServiceAsyncClient,
+ transports.BudgetServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ "true",
+ ),
+ (BudgetServiceClient, transports.BudgetServiceGrpcTransport, "grpc", "false"),
+ (
+ BudgetServiceAsyncClient,
+ transports.BudgetServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ "false",
+ ),
+ ],
+)
+@mock.patch.object(
+ BudgetServiceClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(BudgetServiceClient),
+)
+@mock.patch.object(
+ BudgetServiceAsyncClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(BudgetServiceAsyncClient),
+)
+@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"})
+def test_budget_service_client_mtls_env_auto(
+ client_class, transport_class, transport_name, use_client_cert_env
+):
+ # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default
+ # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists.
- del os.environ["GOOGLE_API_USE_MTLS"]
+ # Check the case client_cert_source is provided. Whether client cert is used depends on
+ # GOOGLE_API_USE_CLIENT_CERTIFICATE value.
+ with mock.patch.dict(
+ os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env}
+ ):
+ options = client_options.ClientOptions(
+ client_cert_source=client_cert_source_callback
+ )
+ with mock.patch.object(transport_class, "__init__") as patched:
+ 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)
+
+ 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
+
+ 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,
+ )
+
+ # Check the case ADC client cert is provided. Whether client cert is used depends on
+ # GOOGLE_API_USE_CLIENT_CERTIFICATE value.
+ with mock.patch.dict(
+ os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env}
+ ):
+ with mock.patch.object(transport_class, "__init__") as patched:
+ with mock.patch(
+ "google.auth.transport.grpc.SslCredentials.__init__", return_value=None
+ ):
+ 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,
+ )
+
+ # 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,
+ scopes=None,
+ ssl_channel_credentials=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
+ )
@pytest.mark.parametrize(
@@ -264,8 +390,9 @@ def test_budget_service_client_client_options_scopes(
credentials_file=None,
host=client.DEFAULT_ENDPOINT,
scopes=["1", "2"],
- api_mtls_endpoint=client.DEFAULT_ENDPOINT,
- client_cert_source=None,
+ ssl_channel_credentials=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
)
@@ -293,8 +420,9 @@ def test_budget_service_client_client_options_credentials_file(
credentials_file="credentials.json",
host=client.DEFAULT_ENDPOINT,
scopes=None,
- api_mtls_endpoint=client.DEFAULT_ENDPOINT,
- client_cert_source=None,
+ ssl_channel_credentials=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
)
@@ -311,22 +439,25 @@ def test_budget_service_client_client_options_from_dict():
credentials_file=None,
host="squid.clam.whelk",
scopes=None,
- api_mtls_endpoint="squid.clam.whelk",
- client_cert_source=None,
+ ssl_channel_credentials=None,
+ quota_project_id=None,
+ client_info=transports.base.DEFAULT_CLIENT_INFO,
)
-def test_create_budget(transport: str = "grpc"):
+def test_create_budget(
+ transport: str = "grpc", request_type=budget_service.CreateBudgetRequest
+):
client = BudgetServiceClient(
credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
# and we are mocking out the actual API, so just send an empty request.
- request = budget_service.CreateBudgetRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(type(client._transport.create_budget), "__call__") as call:
+ with mock.patch.object(type(client.transport.create_budget), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = budget_model.Budget(
name="name_value", display_name="display_name_value", etag="etag_value",
@@ -338,9 +469,10 @@ def test_create_budget(transport: str = "grpc"):
assert len(call.mock_calls) == 1
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == budget_service.CreateBudgetRequest()
# Establish that the response is the type that we expect.
+
assert isinstance(response, budget_model.Budget)
assert response.name == "name_value"
@@ -350,20 +482,24 @@ def test_create_budget(transport: str = "grpc"):
assert response.etag == "etag_value"
+def test_create_budget_from_dict():
+ test_create_budget(request_type=dict)
+
+
@pytest.mark.asyncio
-async def test_create_budget_async(transport: str = "grpc_asyncio"):
+async def test_create_budget_async(
+ transport: str = "grpc_asyncio", request_type=budget_service.CreateBudgetRequest
+):
client = BudgetServiceAsyncClient(
credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
# and we are mocking out the actual API, so just send an empty request.
- request = budget_service.CreateBudgetRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.create_budget), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.create_budget), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
budget_model.Budget(
@@ -377,7 +513,7 @@ async def test_create_budget_async(transport: str = "grpc_asyncio"):
assert len(call.mock_calls)
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == budget_service.CreateBudgetRequest()
# Establish that the response is the type that we expect.
assert isinstance(response, budget_model.Budget)
@@ -389,6 +525,11 @@ async def test_create_budget_async(transport: str = "grpc_asyncio"):
assert response.etag == "etag_value"
+@pytest.mark.asyncio
+async def test_create_budget_async_from_dict():
+ await test_create_budget_async(request_type=dict)
+
+
def test_create_budget_field_headers():
client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
@@ -398,7 +539,7 @@ def test_create_budget_field_headers():
request.parent = "parent/value"
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(type(client._transport.create_budget), "__call__") as call:
+ with mock.patch.object(type(client.transport.create_budget), "__call__") as call:
call.return_value = budget_model.Budget()
client.create_budget(request)
@@ -423,9 +564,7 @@ async def test_create_budget_field_headers_async():
request.parent = "parent/value"
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.create_budget), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.create_budget), "__call__") as call:
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(budget_model.Budget())
await client.create_budget(request)
@@ -440,17 +579,19 @@ async def test_create_budget_field_headers_async():
assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"]
-def test_update_budget(transport: str = "grpc"):
+def test_update_budget(
+ transport: str = "grpc", request_type=budget_service.UpdateBudgetRequest
+):
client = BudgetServiceClient(
credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
# and we are mocking out the actual API, so just send an empty request.
- request = budget_service.UpdateBudgetRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(type(client._transport.update_budget), "__call__") as call:
+ with mock.patch.object(type(client.transport.update_budget), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = budget_model.Budget(
name="name_value", display_name="display_name_value", etag="etag_value",
@@ -462,9 +603,10 @@ def test_update_budget(transport: str = "grpc"):
assert len(call.mock_calls) == 1
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == budget_service.UpdateBudgetRequest()
# Establish that the response is the type that we expect.
+
assert isinstance(response, budget_model.Budget)
assert response.name == "name_value"
@@ -474,20 +616,24 @@ def test_update_budget(transport: str = "grpc"):
assert response.etag == "etag_value"
+def test_update_budget_from_dict():
+ test_update_budget(request_type=dict)
+
+
@pytest.mark.asyncio
-async def test_update_budget_async(transport: str = "grpc_asyncio"):
+async def test_update_budget_async(
+ transport: str = "grpc_asyncio", request_type=budget_service.UpdateBudgetRequest
+):
client = BudgetServiceAsyncClient(
credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
# and we are mocking out the actual API, so just send an empty request.
- request = budget_service.UpdateBudgetRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.update_budget), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.update_budget), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
budget_model.Budget(
@@ -501,7 +647,7 @@ async def test_update_budget_async(transport: str = "grpc_asyncio"):
assert len(call.mock_calls)
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == budget_service.UpdateBudgetRequest()
# Establish that the response is the type that we expect.
assert isinstance(response, budget_model.Budget)
@@ -513,6 +659,11 @@ async def test_update_budget_async(transport: str = "grpc_asyncio"):
assert response.etag == "etag_value"
+@pytest.mark.asyncio
+async def test_update_budget_async_from_dict():
+ await test_update_budget_async(request_type=dict)
+
+
def test_update_budget_field_headers():
client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
@@ -522,7 +673,7 @@ def test_update_budget_field_headers():
request.budget.name = "budget.name/value"
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(type(client._transport.update_budget), "__call__") as call:
+ with mock.patch.object(type(client.transport.update_budget), "__call__") as call:
call.return_value = budget_model.Budget()
client.update_budget(request)
@@ -547,9 +698,7 @@ async def test_update_budget_field_headers_async():
request.budget.name = "budget.name/value"
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.update_budget), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.update_budget), "__call__") as call:
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(budget_model.Budget())
await client.update_budget(request)
@@ -564,17 +713,19 @@ async def test_update_budget_field_headers_async():
assert ("x-goog-request-params", "budget.name=budget.name/value",) in kw["metadata"]
-def test_get_budget(transport: str = "grpc"):
+def test_get_budget(
+ transport: str = "grpc", request_type=budget_service.GetBudgetRequest
+):
client = BudgetServiceClient(
credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
# and we are mocking out the actual API, so just send an empty request.
- request = budget_service.GetBudgetRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(type(client._transport.get_budget), "__call__") as call:
+ with mock.patch.object(type(client.transport.get_budget), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = budget_model.Budget(
name="name_value", display_name="display_name_value", etag="etag_value",
@@ -586,9 +737,10 @@ def test_get_budget(transport: str = "grpc"):
assert len(call.mock_calls) == 1
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == budget_service.GetBudgetRequest()
# Establish that the response is the type that we expect.
+
assert isinstance(response, budget_model.Budget)
assert response.name == "name_value"
@@ -598,20 +750,24 @@ def test_get_budget(transport: str = "grpc"):
assert response.etag == "etag_value"
+def test_get_budget_from_dict():
+ test_get_budget(request_type=dict)
+
+
@pytest.mark.asyncio
-async def test_get_budget_async(transport: str = "grpc_asyncio"):
+async def test_get_budget_async(
+ transport: str = "grpc_asyncio", request_type=budget_service.GetBudgetRequest
+):
client = BudgetServiceAsyncClient(
credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
# and we are mocking out the actual API, so just send an empty request.
- request = budget_service.GetBudgetRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.get_budget), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.get_budget), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
budget_model.Budget(
@@ -625,7 +781,7 @@ async def test_get_budget_async(transport: str = "grpc_asyncio"):
assert len(call.mock_calls)
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == budget_service.GetBudgetRequest()
# Establish that the response is the type that we expect.
assert isinstance(response, budget_model.Budget)
@@ -637,6 +793,11 @@ async def test_get_budget_async(transport: str = "grpc_asyncio"):
assert response.etag == "etag_value"
+@pytest.mark.asyncio
+async def test_get_budget_async_from_dict():
+ await test_get_budget_async(request_type=dict)
+
+
def test_get_budget_field_headers():
client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
@@ -646,7 +807,7 @@ def test_get_budget_field_headers():
request.name = "name/value"
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(type(client._transport.get_budget), "__call__") as call:
+ with mock.patch.object(type(client.transport.get_budget), "__call__") as call:
call.return_value = budget_model.Budget()
client.get_budget(request)
@@ -671,9 +832,7 @@ async def test_get_budget_field_headers_async():
request.name = "name/value"
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.get_budget), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.get_budget), "__call__") as call:
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(budget_model.Budget())
await client.get_budget(request)
@@ -688,17 +847,19 @@ async def test_get_budget_field_headers_async():
assert ("x-goog-request-params", "name=name/value",) in kw["metadata"]
-def test_list_budgets(transport: str = "grpc"):
+def test_list_budgets(
+ transport: str = "grpc", request_type=budget_service.ListBudgetsRequest
+):
client = BudgetServiceClient(
credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
# and we are mocking out the actual API, so just send an empty request.
- request = budget_service.ListBudgetsRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(type(client._transport.list_budgets), "__call__") as call:
+ with mock.patch.object(type(client.transport.list_budgets), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = budget_service.ListBudgetsResponse(
next_page_token="next_page_token_value",
@@ -710,28 +871,33 @@ def test_list_budgets(transport: str = "grpc"):
assert len(call.mock_calls) == 1
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == budget_service.ListBudgetsRequest()
# Establish that the response is the type that we expect.
+
assert isinstance(response, pagers.ListBudgetsPager)
assert response.next_page_token == "next_page_token_value"
+def test_list_budgets_from_dict():
+ test_list_budgets(request_type=dict)
+
+
@pytest.mark.asyncio
-async def test_list_budgets_async(transport: str = "grpc_asyncio"):
+async def test_list_budgets_async(
+ transport: str = "grpc_asyncio", request_type=budget_service.ListBudgetsRequest
+):
client = BudgetServiceAsyncClient(
credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
# and we are mocking out the actual API, so just send an empty request.
- request = budget_service.ListBudgetsRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.list_budgets), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.list_budgets), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
budget_service.ListBudgetsResponse(next_page_token="next_page_token_value",)
@@ -743,7 +909,7 @@ async def test_list_budgets_async(transport: str = "grpc_asyncio"):
assert len(call.mock_calls)
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == budget_service.ListBudgetsRequest()
# Establish that the response is the type that we expect.
assert isinstance(response, pagers.ListBudgetsAsyncPager)
@@ -751,6 +917,11 @@ async def test_list_budgets_async(transport: str = "grpc_asyncio"):
assert response.next_page_token == "next_page_token_value"
+@pytest.mark.asyncio
+async def test_list_budgets_async_from_dict():
+ await test_list_budgets_async(request_type=dict)
+
+
def test_list_budgets_field_headers():
client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
@@ -760,7 +931,7 @@ def test_list_budgets_field_headers():
request.parent = "parent/value"
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(type(client._transport.list_budgets), "__call__") as call:
+ with mock.patch.object(type(client.transport.list_budgets), "__call__") as call:
call.return_value = budget_service.ListBudgetsResponse()
client.list_budgets(request)
@@ -785,9 +956,7 @@ async def test_list_budgets_field_headers_async():
request.parent = "parent/value"
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.list_budgets), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.list_budgets), "__call__") as call:
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
budget_service.ListBudgetsResponse()
)
@@ -808,7 +977,7 @@ def test_list_budgets_pager():
client = BudgetServiceClient(credentials=credentials.AnonymousCredentials,)
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(type(client._transport.list_budgets), "__call__") as call:
+ with mock.patch.object(type(client.transport.list_budgets), "__call__") as call:
# Set the response to a series of pages.
call.side_effect = (
budget_service.ListBudgetsResponse(
@@ -846,7 +1015,7 @@ def test_list_budgets_pages():
client = BudgetServiceClient(credentials=credentials.AnonymousCredentials,)
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(type(client._transport.list_budgets), "__call__") as call:
+ with mock.patch.object(type(client.transport.list_budgets), "__call__") as call:
# Set the response to a series of pages.
call.side_effect = (
budget_service.ListBudgetsResponse(
@@ -867,8 +1036,8 @@ def test_list_budgets_pages():
RuntimeError,
)
pages = list(client.list_budgets(request={}).pages)
- for page, token in zip(pages, ["abc", "def", "ghi", ""]):
- assert page.raw_page.next_page_token == token
+ for page_, token in zip(pages, ["abc", "def", "ghi", ""]):
+ assert page_.raw_page.next_page_token == token
@pytest.mark.asyncio
@@ -877,9 +1046,7 @@ async def test_list_budgets_async_pager():
# Mock the actual call within the gRPC stub, and fake the request.
with mock.patch.object(
- type(client._client._transport.list_budgets),
- "__call__",
- new_callable=mock.AsyncMock,
+ type(client.transport.list_budgets), "__call__", new_callable=mock.AsyncMock
) as call:
# Set the response to a series of pages.
call.side_effect = (
@@ -916,9 +1083,7 @@ async def test_list_budgets_async_pages():
# Mock the actual call within the gRPC stub, and fake the request.
with mock.patch.object(
- type(client._client._transport.list_budgets),
- "__call__",
- new_callable=mock.AsyncMock,
+ type(client.transport.list_budgets), "__call__", new_callable=mock.AsyncMock
) as call:
# Set the response to a series of pages.
call.side_effect = (
@@ -940,23 +1105,25 @@ async def test_list_budgets_async_pages():
RuntimeError,
)
pages = []
- async for page in (await client.list_budgets(request={})).pages:
- pages.append(page)
- for page, token in zip(pages, ["abc", "def", "ghi", ""]):
- assert page.raw_page.next_page_token == token
+ async for page_ in (await client.list_budgets(request={})).pages:
+ pages.append(page_)
+ for page_, token in zip(pages, ["abc", "def", "ghi", ""]):
+ assert page_.raw_page.next_page_token == token
-def test_delete_budget(transport: str = "grpc"):
+def test_delete_budget(
+ transport: str = "grpc", request_type=budget_service.DeleteBudgetRequest
+):
client = BudgetServiceClient(
credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
# and we are mocking out the actual API, so just send an empty request.
- request = budget_service.DeleteBudgetRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(type(client._transport.delete_budget), "__call__") as call:
+ with mock.patch.object(type(client.transport.delete_budget), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = None
@@ -966,26 +1133,30 @@ def test_delete_budget(transport: str = "grpc"):
assert len(call.mock_calls) == 1
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == budget_service.DeleteBudgetRequest()
# Establish that the response is the type that we expect.
assert response is None
+def test_delete_budget_from_dict():
+ test_delete_budget(request_type=dict)
+
+
@pytest.mark.asyncio
-async def test_delete_budget_async(transport: str = "grpc_asyncio"):
+async def test_delete_budget_async(
+ transport: str = "grpc_asyncio", request_type=budget_service.DeleteBudgetRequest
+):
client = BudgetServiceAsyncClient(
credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
# and we are mocking out the actual API, so just send an empty request.
- request = budget_service.DeleteBudgetRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.delete_budget), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.delete_budget), "__call__") as call:
# Designate an appropriate return value for the call.
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None)
@@ -995,12 +1166,17 @@ async def test_delete_budget_async(transport: str = "grpc_asyncio"):
assert len(call.mock_calls)
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == budget_service.DeleteBudgetRequest()
# Establish that the response is the type that we expect.
assert response is None
+@pytest.mark.asyncio
+async def test_delete_budget_async_from_dict():
+ await test_delete_budget_async(request_type=dict)
+
+
def test_delete_budget_field_headers():
client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
@@ -1010,7 +1186,7 @@ def test_delete_budget_field_headers():
request.name = "name/value"
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(type(client._transport.delete_budget), "__call__") as call:
+ with mock.patch.object(type(client.transport.delete_budget), "__call__") as call:
call.return_value = None
client.delete_budget(request)
@@ -1035,9 +1211,7 @@ async def test_delete_budget_field_headers_async():
request.name = "name/value"
# Mock the actual call within the gRPC stub, and fake the request.
- with mock.patch.object(
- type(client._client._transport.delete_budget), "__call__"
- ) as call:
+ with mock.patch.object(type(client.transport.delete_budget), "__call__") as call:
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None)
await client.delete_budget(request)
@@ -1088,7 +1262,7 @@ def test_transport_instance():
credentials=credentials.AnonymousCredentials(),
)
client = BudgetServiceClient(transport=transport)
- assert client._transport is transport
+ assert client.transport is transport
def test_transport_get_channel():
@@ -1106,10 +1280,25 @@ def test_transport_get_channel():
assert channel
+@pytest.mark.parametrize(
+ "transport_class",
+ [
+ transports.BudgetServiceGrpcTransport,
+ transports.BudgetServiceGrpcAsyncIOTransport,
+ ],
+)
+def test_transport_adc(transport_class):
+ # Test default credentials are used if not provided.
+ with mock.patch.object(auth, "default") as adc:
+ adc.return_value = (credentials.AnonymousCredentials(), None)
+ transport_class()
+ adc.assert_called_once()
+
+
def test_transport_grpc_default():
# A client should use the gRPC transport by default.
client = BudgetServiceClient(credentials=credentials.AnonymousCredentials(),)
- assert isinstance(client._transport, transports.BudgetServiceGrpcTransport,)
+ assert isinstance(client.transport, transports.BudgetServiceGrpcTransport,)
def test_budget_service_base_transport_error():
@@ -1123,9 +1312,13 @@ def test_budget_service_base_transport_error():
def test_budget_service_base_transport():
# Instantiate the base transport.
- transport = transports.BudgetServiceTransport(
- credentials=credentials.AnonymousCredentials(),
- )
+ with mock.patch(
+ "google.cloud.billing.budgets_v1beta1.services.budget_service.transports.BudgetServiceTransport.__init__"
+ ) as Transport:
+ Transport.return_value = None
+ transport = transports.BudgetServiceTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
# Every method on the transport should just blindly
# raise NotImplementedError.
@@ -1143,24 +1336,48 @@ def test_budget_service_base_transport():
def test_budget_service_base_transport_with_credentials_file():
# Instantiate the base transport with a credentials file
- with mock.patch.object(auth, "load_credentials_from_file") as load_creds:
+ with mock.patch.object(
+ auth, "load_credentials_from_file"
+ ) as load_creds, mock.patch(
+ "google.cloud.billing.budgets_v1beta1.services.budget_service.transports.BudgetServiceTransport._prep_wrapped_messages"
+ ) as Transport:
+ Transport.return_value = None
load_creds.return_value = (credentials.AnonymousCredentials(), None)
transport = transports.BudgetServiceTransport(
- credentials_file="credentials.json",
+ credentials_file="credentials.json", quota_project_id="octopus",
)
load_creds.assert_called_once_with(
"credentials.json",
- scopes=("https://www.googleapis.com/auth/cloud-platform",),
+ scopes=(
+ "https://www.googleapis.com/auth/cloud-billing",
+ "https://www.googleapis.com/auth/cloud-platform",
+ ),
+ quota_project_id="octopus",
)
+def test_budget_service_base_transport_with_adc():
+ # Test the default credentials are used if credentials and credentials_file are None.
+ with mock.patch.object(auth, "default") as adc, mock.patch(
+ "google.cloud.billing.budgets_v1beta1.services.budget_service.transports.BudgetServiceTransport._prep_wrapped_messages"
+ ) as Transport:
+ Transport.return_value = None
+ adc.return_value = (credentials.AnonymousCredentials(), None)
+ transport = transports.BudgetServiceTransport()
+ adc.assert_called_once()
+
+
def test_budget_service_auth_adc():
# If no credentials are provided, we should use ADC credentials.
with mock.patch.object(auth, "default") as adc:
adc.return_value = (credentials.AnonymousCredentials(), None)
BudgetServiceClient()
adc.assert_called_once_with(
- scopes=("https://www.googleapis.com/auth/cloud-platform",)
+ scopes=(
+ "https://www.googleapis.com/auth/cloud-billing",
+ "https://www.googleapis.com/auth/cloud-platform",
+ ),
+ quota_project_id=None,
)
@@ -1169,9 +1386,15 @@ def test_budget_service_transport_auth_adc():
# ADC credentials.
with mock.patch.object(auth, "default") as adc:
adc.return_value = (credentials.AnonymousCredentials(), None)
- transports.BudgetServiceGrpcTransport(host="squid.clam.whelk")
+ transports.BudgetServiceGrpcTransport(
+ host="squid.clam.whelk", quota_project_id="octopus"
+ )
adc.assert_called_once_with(
- scopes=("https://www.googleapis.com/auth/cloud-platform",)
+ scopes=(
+ "https://www.googleapis.com/auth/cloud-billing",
+ "https://www.googleapis.com/auth/cloud-platform",
+ ),
+ quota_project_id="octopus",
)
@@ -1182,7 +1405,7 @@ def test_budget_service_host_no_port():
api_endpoint="billingbudgets.googleapis.com"
),
)
- assert client._transport._host == "billingbudgets.googleapis.com:443"
+ assert client.transport._host == "billingbudgets.googleapis.com:443"
def test_budget_service_host_with_port():
@@ -1192,181 +1415,123 @@ def test_budget_service_host_with_port():
api_endpoint="billingbudgets.googleapis.com:8000"
),
)
- assert client._transport._host == "billingbudgets.googleapis.com:8000"
+ assert client.transport._host == "billingbudgets.googleapis.com:8000"
def test_budget_service_grpc_transport_channel():
channel = grpc.insecure_channel("http://localhost/")
- # Check that if channel is provided, mtls endpoint and client_cert_source
- # won't be used.
- callback = mock.MagicMock()
+ # Check that channel is used if provided.
transport = transports.BudgetServiceGrpcTransport(
- host="squid.clam.whelk",
- channel=channel,
- api_mtls_endpoint="mtls.squid.clam.whelk",
- client_cert_source=callback,
+ host="squid.clam.whelk", channel=channel,
)
assert transport.grpc_channel == channel
assert transport._host == "squid.clam.whelk:443"
- assert not callback.called
+ assert transport._ssl_channel_credentials == None
def test_budget_service_grpc_asyncio_transport_channel():
channel = aio.insecure_channel("http://localhost/")
- # Check that if channel is provided, mtls endpoint and client_cert_source
- # won't be used.
- callback = mock.MagicMock()
+ # Check that channel is used if provided.
transport = transports.BudgetServiceGrpcAsyncIOTransport(
- host="squid.clam.whelk",
- channel=channel,
- api_mtls_endpoint="mtls.squid.clam.whelk",
- client_cert_source=callback,
+ host="squid.clam.whelk", channel=channel,
)
assert transport.grpc_channel == channel
assert transport._host == "squid.clam.whelk:443"
- assert not callback.called
-
-
-@mock.patch("grpc.ssl_channel_credentials", autospec=True)
-@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True)
-def test_budget_service_grpc_transport_channel_mtls_with_client_cert_source(
- grpc_create_channel, grpc_ssl_channel_cred
-):
- # Check that if channel is None, but api_mtls_endpoint and client_cert_source
- # are provided, then a mTLS channel will be created.
- mock_cred = mock.Mock()
-
- mock_ssl_cred = mock.Mock()
- grpc_ssl_channel_cred.return_value = mock_ssl_cred
-
- mock_grpc_channel = mock.Mock()
- grpc_create_channel.return_value = mock_grpc_channel
-
- transport = transports.BudgetServiceGrpcTransport(
- host="squid.clam.whelk",
- credentials=mock_cred,
- api_mtls_endpoint="mtls.squid.clam.whelk",
- client_cert_source=client_cert_source_callback,
- )
- grpc_ssl_channel_cred.assert_called_once_with(
- certificate_chain=b"cert bytes", private_key=b"key bytes"
- )
- grpc_create_channel.assert_called_once_with(
- "mtls.squid.clam.whelk:443",
- credentials=mock_cred,
- credentials_file=None,
- scopes=("https://www.googleapis.com/auth/cloud-platform",),
- ssl_credentials=mock_ssl_cred,
- )
- assert transport.grpc_channel == mock_grpc_channel
-
-
-@mock.patch("grpc.ssl_channel_credentials", autospec=True)
-@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True)
-def test_budget_service_grpc_asyncio_transport_channel_mtls_with_client_cert_source(
- grpc_create_channel, grpc_ssl_channel_cred
-):
- # Check that if channel is None, but api_mtls_endpoint and client_cert_source
- # are provided, then a mTLS channel will be created.
- mock_cred = mock.Mock()
-
- mock_ssl_cred = mock.Mock()
- grpc_ssl_channel_cred.return_value = mock_ssl_cred
-
- mock_grpc_channel = mock.Mock()
- grpc_create_channel.return_value = mock_grpc_channel
-
- transport = transports.BudgetServiceGrpcAsyncIOTransport(
- host="squid.clam.whelk",
- credentials=mock_cred,
- api_mtls_endpoint="mtls.squid.clam.whelk",
- client_cert_source=client_cert_source_callback,
- )
- grpc_ssl_channel_cred.assert_called_once_with(
- certificate_chain=b"cert bytes", private_key=b"key bytes"
- )
- grpc_create_channel.assert_called_once_with(
- "mtls.squid.clam.whelk:443",
- credentials=mock_cred,
- credentials_file=None,
- scopes=("https://www.googleapis.com/auth/cloud-platform",),
- ssl_credentials=mock_ssl_cred,
- )
- assert transport.grpc_channel == mock_grpc_channel
+ assert transport._ssl_channel_credentials == None
@pytest.mark.parametrize(
- "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"]
+ "transport_class",
+ [
+ transports.BudgetServiceGrpcTransport,
+ transports.BudgetServiceGrpcAsyncIOTransport,
+ ],
)
-@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True)
-def test_budget_service_grpc_transport_channel_mtls_with_adc(
- grpc_create_channel, api_mtls_endpoint
-):
- # Check that if channel and client_cert_source are None, but api_mtls_endpoint
- # is provided, then a mTLS channel will be created with SSL ADC.
- mock_grpc_channel = mock.Mock()
- grpc_create_channel.return_value = mock_grpc_channel
-
- # Mock google.auth.transport.grpc.SslCredentials class.
- mock_ssl_cred = mock.Mock()
- with mock.patch.multiple(
- "google.auth.transport.grpc.SslCredentials",
- __init__=mock.Mock(return_value=None),
- ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred),
- ):
- mock_cred = mock.Mock()
- transport = transports.BudgetServiceGrpcTransport(
- host="squid.clam.whelk",
- credentials=mock_cred,
- api_mtls_endpoint=api_mtls_endpoint,
- client_cert_source=None,
- )
- grpc_create_channel.assert_called_once_with(
- "mtls.squid.clam.whelk:443",
- credentials=mock_cred,
- credentials_file=None,
- scopes=("https://www.googleapis.com/auth/cloud-platform",),
- ssl_credentials=mock_ssl_cred,
- )
- assert transport.grpc_channel == mock_grpc_channel
+def test_budget_service_transport_channel_mtls_with_client_cert_source(transport_class):
+ with mock.patch(
+ "grpc.ssl_channel_credentials", autospec=True
+ ) as grpc_ssl_channel_cred:
+ with mock.patch.object(
+ transport_class, "create_channel", autospec=True
+ ) as grpc_create_channel:
+ mock_ssl_cred = mock.Mock()
+ grpc_ssl_channel_cred.return_value = mock_ssl_cred
+
+ mock_grpc_channel = mock.Mock()
+ grpc_create_channel.return_value = mock_grpc_channel
+
+ cred = credentials.AnonymousCredentials()
+ with pytest.warns(DeprecationWarning):
+ with mock.patch.object(auth, "default") as adc:
+ adc.return_value = (cred, None)
+ transport = transport_class(
+ host="squid.clam.whelk",
+ api_mtls_endpoint="mtls.squid.clam.whelk",
+ client_cert_source=client_cert_source_callback,
+ )
+ adc.assert_called_once()
+
+ grpc_ssl_channel_cred.assert_called_once_with(
+ certificate_chain=b"cert bytes", private_key=b"key bytes"
+ )
+ grpc_create_channel.assert_called_once_with(
+ "mtls.squid.clam.whelk:443",
+ credentials=cred,
+ credentials_file=None,
+ scopes=(
+ "https://www.googleapis.com/auth/cloud-billing",
+ "https://www.googleapis.com/auth/cloud-platform",
+ ),
+ ssl_credentials=mock_ssl_cred,
+ quota_project_id=None,
+ )
+ assert transport.grpc_channel == mock_grpc_channel
+ assert transport._ssl_channel_credentials == mock_ssl_cred
@pytest.mark.parametrize(
- "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"]
+ "transport_class",
+ [
+ transports.BudgetServiceGrpcTransport,
+ transports.BudgetServiceGrpcAsyncIOTransport,
+ ],
)
-@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True)
-def test_budget_service_grpc_asyncio_transport_channel_mtls_with_adc(
- grpc_create_channel, api_mtls_endpoint
-):
- # Check that if channel and client_cert_source are None, but api_mtls_endpoint
- # is provided, then a mTLS channel will be created with SSL ADC.
- mock_grpc_channel = mock.Mock()
- grpc_create_channel.return_value = mock_grpc_channel
-
- # Mock google.auth.transport.grpc.SslCredentials class.
+def test_budget_service_transport_channel_mtls_with_adc(transport_class):
mock_ssl_cred = mock.Mock()
with mock.patch.multiple(
"google.auth.transport.grpc.SslCredentials",
__init__=mock.Mock(return_value=None),
ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred),
):
- mock_cred = mock.Mock()
- transport = transports.BudgetServiceGrpcAsyncIOTransport(
- host="squid.clam.whelk",
- credentials=mock_cred,
- api_mtls_endpoint=api_mtls_endpoint,
- client_cert_source=None,
- )
- grpc_create_channel.assert_called_once_with(
- "mtls.squid.clam.whelk:443",
- credentials=mock_cred,
- credentials_file=None,
- scopes=("https://www.googleapis.com/auth/cloud-platform",),
- ssl_credentials=mock_ssl_cred,
- )
- assert transport.grpc_channel == mock_grpc_channel
+ with mock.patch.object(
+ transport_class, "create_channel", autospec=True
+ ) as grpc_create_channel:
+ mock_grpc_channel = mock.Mock()
+ grpc_create_channel.return_value = mock_grpc_channel
+ mock_cred = mock.Mock()
+
+ with pytest.warns(DeprecationWarning):
+ transport = transport_class(
+ host="squid.clam.whelk",
+ credentials=mock_cred,
+ api_mtls_endpoint="mtls.squid.clam.whelk",
+ client_cert_source=None,
+ )
+
+ grpc_create_channel.assert_called_once_with(
+ "mtls.squid.clam.whelk:443",
+ credentials=mock_cred,
+ credentials_file=None,
+ scopes=(
+ "https://www.googleapis.com/auth/cloud-billing",
+ "https://www.googleapis.com/auth/cloud-platform",
+ ),
+ ssl_credentials=mock_ssl_cred,
+ quota_project_id=None,
+ )
+ assert transport.grpc_channel == mock_grpc_channel
def test_budget_path():
@@ -1390,3 +1555,125 @@ def test_parse_budget_path():
# Check that the path construction is reversible.
actual = BudgetServiceClient.parse_budget_path(path)
assert expected == actual
+
+
+def test_common_billing_account_path():
+ billing_account = "oyster"
+
+ expected = "billingAccounts/{billing_account}".format(
+ billing_account=billing_account,
+ )
+ actual = BudgetServiceClient.common_billing_account_path(billing_account)
+ assert expected == actual
+
+
+def test_parse_common_billing_account_path():
+ expected = {
+ "billing_account": "nudibranch",
+ }
+ path = BudgetServiceClient.common_billing_account_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = BudgetServiceClient.parse_common_billing_account_path(path)
+ assert expected == actual
+
+
+def test_common_folder_path():
+ folder = "cuttlefish"
+
+ expected = "folders/{folder}".format(folder=folder,)
+ actual = BudgetServiceClient.common_folder_path(folder)
+ assert expected == actual
+
+
+def test_parse_common_folder_path():
+ expected = {
+ "folder": "mussel",
+ }
+ path = BudgetServiceClient.common_folder_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = BudgetServiceClient.parse_common_folder_path(path)
+ assert expected == actual
+
+
+def test_common_organization_path():
+ organization = "winkle"
+
+ expected = "organizations/{organization}".format(organization=organization,)
+ actual = BudgetServiceClient.common_organization_path(organization)
+ assert expected == actual
+
+
+def test_parse_common_organization_path():
+ expected = {
+ "organization": "nautilus",
+ }
+ path = BudgetServiceClient.common_organization_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = BudgetServiceClient.parse_common_organization_path(path)
+ assert expected == actual
+
+
+def test_common_project_path():
+ project = "scallop"
+
+ expected = "projects/{project}".format(project=project,)
+ actual = BudgetServiceClient.common_project_path(project)
+ assert expected == actual
+
+
+def test_parse_common_project_path():
+ expected = {
+ "project": "abalone",
+ }
+ path = BudgetServiceClient.common_project_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = BudgetServiceClient.parse_common_project_path(path)
+ assert expected == actual
+
+
+def test_common_location_path():
+ project = "squid"
+ location = "clam"
+
+ expected = "projects/{project}/locations/{location}".format(
+ project=project, location=location,
+ )
+ actual = BudgetServiceClient.common_location_path(project, location)
+ assert expected == actual
+
+
+def test_parse_common_location_path():
+ expected = {
+ "project": "whelk",
+ "location": "octopus",
+ }
+ path = BudgetServiceClient.common_location_path(**expected)
+
+ # Check that the path construction is reversible.
+ actual = BudgetServiceClient.parse_common_location_path(path)
+ assert expected == actual
+
+
+def test_client_withDEFAULT_CLIENT_INFO():
+ client_info = gapic_v1.client_info.ClientInfo()
+
+ with mock.patch.object(
+ transports.BudgetServiceTransport, "_prep_wrapped_messages"
+ ) as prep:
+ client = BudgetServiceClient(
+ credentials=credentials.AnonymousCredentials(), client_info=client_info,
+ )
+ prep.assert_called_once_with(client_info)
+
+ with mock.patch.object(
+ transports.BudgetServiceTransport, "_prep_wrapped_messages"
+ ) as prep:
+ transport_class = BudgetServiceClient.get_transport_class()
+ transport = transport_class(
+ credentials=credentials.AnonymousCredentials(), client_info=client_info,
+ )
+ prep.assert_called_once_with(client_info)