diff --git a/.flake8 b/.flake8
index d1134d72..ed931638 100644
--- a/.flake8
+++ b/.flake8
@@ -16,7 +16,7 @@
# Generated by synthtool. DO NOT EDIT!
[flake8]
-ignore = E203, E266, E501, W503, F401, F841
+ignore = E203, E266, E501, W503
exclude =
# Exclude generated code.
**/proto/**
diff --git a/.github/snippet-bot.yml b/.github/snippet-bot.yml
new file mode 100644
index 00000000..e69de29b
diff --git a/.gitignore b/.gitignore
index b87e1ed5..b9daa52f 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 5d6cf5d1..4eb2a8df 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 00000000..412b0b56
--- /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 00000000..d653dd86
--- /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 64f2d7be..fcc98d5e 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-documentai/.kokoro/trampoline.sh"
+build_file: "python-documentai/.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-staging"
+}
+
+# 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 00000000..11181078
--- /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 00000000..f5251425
--- /dev/null
+++ b/.kokoro/populate-secrets.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+# Copyright 2020 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -eo pipefail
+
+function now { date +"%Y-%m-%d %H:%M:%S" | tr -d '\n' ;}
+function msg { println "$*" >&2 ;}
+function println { printf '%s\n' "$(now) $*" ;}
+
+
+# Populates requested secrets set in SECRET_MANAGER_KEYS from service account:
+# kokoro-trampoline@cloud-devrel-kokoro-resources.iam.gserviceaccount.com
+SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager"
+msg "Creating folder on disk for secrets: ${SECRET_LOCATION}"
+mkdir -p ${SECRET_LOCATION}
+for key in $(echo ${SECRET_MANAGER_KEYS} | sed "s/,/ /g")
+do
+ msg "Retrieving secret ${key}"
+ docker run --entrypoint=gcloud \
+ --volume=${KOKORO_GFILE_DIR}:${KOKORO_GFILE_DIR} \
+ gcr.io/google.com/cloudsdktool/cloud-sdk \
+ secrets versions access latest \
+ --project cloud-devrel-kokoro-resources \
+ --secret ${key} > \
+ "${SECRET_LOCATION}/${key}"
+ if [[ $? == 0 ]]; then
+ msg "Secret written to ${SECRET_LOCATION}/${key}"
+ else
+ msg "Error retrieving secret ${key}"
+ fi
+done
diff --git a/.kokoro/publish-docs.sh b/.kokoro/publish-docs.sh
index d74c1f65..8acb14e8 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-documentai
-
-# 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 45c96b33..2d80fa2e 100644
--- a/.kokoro/release/common.cfg
+++ b/.kokoro/release/common.cfg
@@ -23,42 +23,18 @@ env_vars: {
value: "github/python-documentai/.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/trampoline.sh b/.kokoro/trampoline.sh
index e8c4251f..f39236e9 100755
--- a/.kokoro/trampoline.sh
+++ b/.kokoro/trampoline.sh
@@ -15,9 +15,14 @@
set -eo pipefail
-python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" || ret_code=$?
+# Always run the cleanup script, regardless of the success of bouncing into
+# the container.
+function cleanup() {
+ chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh
+ ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh
+ echo "cleanup";
+}
+trap cleanup EXIT
-chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh
-${KOKORO_GFILE_DIR}/trampoline_cleanup.sh || true
-
-exit ${ret_code}
+$(dirname $0)/populate-secrets.sh # Secret Manager secrets.
+python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py"
\ No newline at end of file
diff --git a/.kokoro/trampoline_v2.sh b/.kokoro/trampoline_v2.sh
new file mode 100755
index 00000000..719bcd5b
--- /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 00000000..995ee291
--- /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/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 17e4bfbf..9ea187b2 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/_templates/layout.html b/docs/_templates/layout.html
index 228529ef..6316a537 100644
--- a/docs/_templates/layout.html
+++ b/docs/_templates/layout.html
@@ -21,8 +21,8 @@
- On January 1, 2020 this library will no longer support Python 2 on the latest released version.
- Previously released library versions will continue to be available. For more information please
+ As of January 1, 2020 this library no longer supports Python 2 on the latest released version.
+ Library versions released prior to that date will continue to be available. For more information please
visit
Python 2 support on Google Cloud.
{% block body %} {% endblock %}
diff --git a/docs/conf.py b/docs/conf.py
index 207121ae..d5ee4abd 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.
@@ -337,7 +347,7 @@
intersphinx_mapping = {
"python": ("http://python.readthedocs.org/en/latest/", None),
"google-auth": ("https://google-auth.readthedocs.io/en/stable", None),
- "google.api_core": ("https://googleapis.dev/python/google-api-core/latest/", None),
+ "google.api_core": ("https://googleapis.dev/python/google-api-core/latest/", None,),
"grpc": ("https://grpc.io/grpc/python/", None),
}
diff --git a/docs/documentai_v1beta3/services.rst b/docs/documentai_v1beta3/services.rst
new file mode 100644
index 00000000..b4a1011c
--- /dev/null
+++ b/docs/documentai_v1beta3/services.rst
@@ -0,0 +1,6 @@
+Services for Google Cloud Documentai v1beta3 API
+================================================
+
+.. automodule:: google.cloud.documentai_v1beta3.services.document_processor_service
+ :members:
+ :inherited-members:
diff --git a/docs/documentai_v1beta3/types.rst b/docs/documentai_v1beta3/types.rst
new file mode 100644
index 00000000..03bcbfa7
--- /dev/null
+++ b/docs/documentai_v1beta3/types.rst
@@ -0,0 +1,5 @@
+Types for Google Cloud Documentai v1beta3 API
+=============================================
+
+.. automodule:: google.cloud.documentai_v1beta3.types
+ :members:
diff --git a/docs/index.rst b/docs/index.rst
index 4cafd455..c6c5efde 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -7,6 +7,8 @@ API Reference
.. toctree::
:maxdepth: 2
+ documentai_v1beta3/services
+ documentai_v1beta3/types
documentai_v1beta2/services
documentai_v1beta2/types
diff --git a/google/cloud/documentai/__init__.py b/google/cloud/documentai/__init__.py
index a59e3b40..edd80431 100644
--- a/google/cloud/documentai/__init__.py
+++ b/google/cloud/documentai/__init__.py
@@ -15,72 +15,54 @@
# limitations under the License.
#
-from google.cloud.documentai_v1beta2.services.document_understanding_service.async_client import (
- DocumentUnderstandingServiceAsyncClient,
+from google.cloud.documentai_v1beta3.services.document_processor_service.async_client import (
+ DocumentProcessorServiceAsyncClient,
)
-from google.cloud.documentai_v1beta2.services.document_understanding_service.client import (
- DocumentUnderstandingServiceClient,
+from google.cloud.documentai_v1beta3.services.document_processor_service.client import (
+ DocumentProcessorServiceClient,
)
-from google.cloud.documentai_v1beta2.types.document import Document
-from google.cloud.documentai_v1beta2.types.document_understanding import AutoMlParams
-from google.cloud.documentai_v1beta2.types.document_understanding import (
- BatchProcessDocumentsRequest,
+from google.cloud.documentai_v1beta3.types.document import Document
+from google.cloud.documentai_v1beta3.types.document_processor_service import (
+ BatchProcessMetadata,
)
-from google.cloud.documentai_v1beta2.types.document_understanding import (
- BatchProcessDocumentsResponse,
+from google.cloud.documentai_v1beta3.types.document_processor_service import (
+ BatchProcessRequest,
)
-from google.cloud.documentai_v1beta2.types.document_understanding import (
- EntityExtractionParams,
+from google.cloud.documentai_v1beta3.types.document_processor_service import (
+ BatchProcessResponse,
)
-from google.cloud.documentai_v1beta2.types.document_understanding import (
- FormExtractionParams,
+from google.cloud.documentai_v1beta3.types.document_processor_service import (
+ ProcessRequest,
)
-from google.cloud.documentai_v1beta2.types.document_understanding import GcsDestination
-from google.cloud.documentai_v1beta2.types.document_understanding import GcsSource
-from google.cloud.documentai_v1beta2.types.document_understanding import InputConfig
-from google.cloud.documentai_v1beta2.types.document_understanding import (
- KeyValuePairHint,
+from google.cloud.documentai_v1beta3.types.document_processor_service import (
+ ProcessResponse,
)
-from google.cloud.documentai_v1beta2.types.document_understanding import OcrParams
-from google.cloud.documentai_v1beta2.types.document_understanding import (
- OperationMetadata,
+from google.cloud.documentai_v1beta3.types.document_processor_service import (
+ ReviewDocumentOperationMetadata,
)
-from google.cloud.documentai_v1beta2.types.document_understanding import OutputConfig
-from google.cloud.documentai_v1beta2.types.document_understanding import (
- ProcessDocumentRequest,
+from google.cloud.documentai_v1beta3.types.document_processor_service import (
+ ReviewDocumentRequest,
)
-from google.cloud.documentai_v1beta2.types.document_understanding import (
- ProcessDocumentResponse,
+from google.cloud.documentai_v1beta3.types.document_processor_service import (
+ ReviewDocumentResponse,
)
-from google.cloud.documentai_v1beta2.types.document_understanding import TableBoundHint
-from google.cloud.documentai_v1beta2.types.document_understanding import (
- TableExtractionParams,
-)
-from google.cloud.documentai_v1beta2.types.geometry import BoundingPoly
-from google.cloud.documentai_v1beta2.types.geometry import NormalizedVertex
-from google.cloud.documentai_v1beta2.types.geometry import Vertex
+from google.cloud.documentai_v1beta3.types.geometry import BoundingPoly
+from google.cloud.documentai_v1beta3.types.geometry import NormalizedVertex
+from google.cloud.documentai_v1beta3.types.geometry import Vertex
__all__ = (
- "AutoMlParams",
- "BatchProcessDocumentsRequest",
- "BatchProcessDocumentsResponse",
+ "BatchProcessMetadata",
+ "BatchProcessRequest",
+ "BatchProcessResponse",
"BoundingPoly",
"Document",
- "DocumentUnderstandingServiceAsyncClient",
- "DocumentUnderstandingServiceClient",
- "EntityExtractionParams",
- "FormExtractionParams",
- "GcsDestination",
- "GcsSource",
- "InputConfig",
- "KeyValuePairHint",
+ "DocumentProcessorServiceAsyncClient",
+ "DocumentProcessorServiceClient",
"NormalizedVertex",
- "OcrParams",
- "OperationMetadata",
- "OutputConfig",
- "ProcessDocumentRequest",
- "ProcessDocumentResponse",
- "TableBoundHint",
- "TableExtractionParams",
+ "ProcessRequest",
+ "ProcessResponse",
+ "ReviewDocumentOperationMetadata",
+ "ReviewDocumentRequest",
+ "ReviewDocumentResponse",
"Vertex",
)
diff --git a/google/cloud/documentai_v1beta2/services/document_understanding_service/async_client.py b/google/cloud/documentai_v1beta2/services/document_understanding_service/async_client.py
index a5984e35..a293e4b2 100644
--- a/google/cloud/documentai_v1beta2/services/document_understanding_service/async_client.py
+++ b/google/cloud/documentai_v1beta2/services/document_understanding_service/async_client.py
@@ -28,13 +28,13 @@
from google.auth import credentials # type: ignore
from google.oauth2 import service_account # type: ignore
-from google.api_core import operation
-from google.api_core import operation_async
+from google.api_core import operation # type: ignore
+from google.api_core import operation_async # type: ignore
from google.cloud.documentai_v1beta2.types import document
from google.cloud.documentai_v1beta2.types import document_understanding
from google.rpc import status_pb2 as status # type: ignore
-from .transports.base import DocumentUnderstandingServiceTransport
+from .transports.base import DocumentUnderstandingServiceTransport, DEFAULT_CLIENT_INFO
from .transports.grpc_asyncio import DocumentUnderstandingServiceGrpcAsyncIOTransport
from .client import DocumentUnderstandingServiceClient
@@ -66,6 +66,7 @@ def __init__(
credentials: credentials.Credentials = None,
transport: Union[str, DocumentUnderstandingServiceTransport] = "grpc_asyncio",
client_options: ClientOptions = None,
+ client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
) -> None:
"""Instantiate the document understanding service client.
@@ -81,16 +82,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
@@ -98,7 +102,10 @@ def __init__(
"""
self._client = DocumentUnderstandingServiceClient(
- credentials=credentials, transport=transport, client_options=client_options
+ credentials=credentials,
+ transport=transport,
+ client_options=client_options,
+ client_info=client_info,
)
async def batch_process_documents(
@@ -163,8 +170,16 @@ async def batch_process_documents(
# and friendly error handling.
rpc = gapic_v1.method_async.wrap_method(
self._client._transport.batch_process_documents,
- 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.ServiceUnavailable, exceptions.DeadlineExceeded,
+ ),
+ ),
+ default_timeout=120.0,
+ client_info=DEFAULT_CLIENT_INFO,
)
# Certain fields should be provided within the metadata header;
@@ -174,7 +189,7 @@ async def batch_process_documents(
)
# Send the request.
- response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata)
+ response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,)
# Wrap the response in an operation future.
response = operation_async.from_gapic(
@@ -226,8 +241,16 @@ async def process_document(
# and friendly error handling.
rpc = gapic_v1.method_async.wrap_method(
self._client._transport.process_document,
- 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.ServiceUnavailable, exceptions.DeadlineExceeded,
+ ),
+ ),
+ default_timeout=120.0,
+ client_info=DEFAULT_CLIENT_INFO,
)
# Certain fields should be provided within the metadata header;
@@ -237,18 +260,20 @@ async def process_document(
)
# Send the request.
- response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata)
+ response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,)
# Done; return the response.
return response
try:
- _client_info = gapic_v1.client_info.ClientInfo(
- gapic_version=pkg_resources.get_distribution("google-cloud-documentai").version
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
+ gapic_version=pkg_resources.get_distribution(
+ "google-cloud-documentai",
+ ).version,
)
except pkg_resources.DistributionNotFound:
- _client_info = gapic_v1.client_info.ClientInfo()
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo()
__all__ = ("DocumentUnderstandingServiceAsyncClient",)
diff --git a/google/cloud/documentai_v1beta2/services/document_understanding_service/client.py b/google/cloud/documentai_v1beta2/services/document_understanding_service/client.py
index 1aefee2f..433de5c1 100644
--- a/google/cloud/documentai_v1beta2/services/document_understanding_service/client.py
+++ b/google/cloud/documentai_v1beta2/services/document_understanding_service/client.py
@@ -16,27 +16,29 @@
#
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
-from google.api_core import operation
-from google.api_core import operation_async
+from google.api_core import operation # type: ignore
+from google.api_core import operation_async # type: ignore
from google.cloud.documentai_v1beta2.types import document
from google.cloud.documentai_v1beta2.types import document_understanding
from google.rpc import status_pb2 as status # type: ignore
-from .transports.base import DocumentUnderstandingServiceTransport
+from .transports.base import DocumentUnderstandingServiceTransport, DEFAULT_CLIENT_INFO
from .transports.grpc import DocumentUnderstandingServiceGrpcTransport
from .transports.grpc_asyncio import DocumentUnderstandingServiceGrpcAsyncIOTransport
@@ -58,7 +60,7 @@ class DocumentUnderstandingServiceClientMeta(type):
] = DocumentUnderstandingServiceGrpcAsyncIOTransport
def get_transport_class(
- cls, label: str = None
+ cls, label: str = None,
) -> Type[DocumentUnderstandingServiceTransport]:
"""Return an appropriate transport class.
@@ -143,9 +145,10 @@ def from_service_account_file(cls, filename: str, *args, **kwargs):
def __init__(
self,
*,
- credentials: credentials.Credentials = None,
- transport: Union[str, DocumentUnderstandingServiceTransport] = None,
- client_options: ClientOptions = None,
+ credentials: Optional[credentials.Credentials] = None,
+ transport: Union[str, DocumentUnderstandingServiceTransport, None] = None,
+ client_options: Optional[client_options_lib.ClientOptions] = None,
+ client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
) -> None:
"""Instantiate the document understanding service client.
@@ -158,48 +161,74 @@ def __init__(
transport (Union[str, ~.DocumentUnderstandingServiceTransport]): 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()
- if client_options.api_endpoint is None:
- use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "never")
+ # 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":
- 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.
@@ -207,19 +236,27 @@ def __init__(
# instance provides an extensibility point for unusual situations.
if isinstance(transport, DocumentUnderstandingServiceTransport):
# transport is a DocumentUnderstandingServiceTransport instance.
- if credentials:
+ 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,
- host=client_options.api_endpoint,
- api_mtls_endpoint=client_options.api_endpoint,
- client_cert_source=client_options.client_cert_source,
+ 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 batch_process_documents(
@@ -266,27 +303,29 @@ def batch_process_documents(
# Create or coerce a protobuf request object.
# Sanity check: If we got a request object, we should *not* have
# gotten any keyword arguments that map to the request.
- if request is not None and any([requests]):
+ has_flattened_params = any([requests])
+ 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 = document_understanding.BatchProcessDocumentsRequest(request)
+ # Minor optimization to avoid making a copy if the user passes
+ # in a document_understanding.BatchProcessDocumentsRequest.
+ # There's no risk of modifying the input as we've already verified
+ # there are no flattened fields.
+ if not isinstance(request, document_understanding.BatchProcessDocumentsRequest):
+ request = document_understanding.BatchProcessDocumentsRequest(request)
- # If we have keyword arguments corresponding to fields on the
- # request, apply these.
+ # If we have keyword arguments corresponding to fields on the
+ # request, apply these.
- if requests is not None:
- request.requests = requests
+ if requests is not None:
+ request.requests = requests
# Wrap the RPC method; this adds retry and timeout information,
# and friendly error handling.
- rpc = gapic_v1.method.wrap_method(
- self._transport.batch_process_documents,
- default_timeout=None,
- client_info=_client_info,
- )
+ rpc = self._transport._wrapped_methods[self._transport.batch_process_documents]
# Certain fields should be provided within the metadata header;
# add these here.
@@ -295,7 +334,7 @@ def batch_process_documents(
)
# Send the request.
- response = rpc(request, retry=retry, timeout=timeout, metadata=metadata)
+ response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,)
# Wrap the response in an operation future.
response = operation.from_gapic(
@@ -341,15 +380,16 @@ def process_document(
"""
# Create or coerce a protobuf request object.
- request = document_understanding.ProcessDocumentRequest(request)
+ # Minor optimization to avoid making a copy if the user passes
+ # in a document_understanding.ProcessDocumentRequest.
+ # There's no risk of modifying the input as we've already verified
+ # there are no flattened fields.
+ if not isinstance(request, document_understanding.ProcessDocumentRequest):
+ request = document_understanding.ProcessDocumentRequest(request)
# Wrap the RPC method; this adds retry and timeout information,
# and friendly error handling.
- rpc = gapic_v1.method.wrap_method(
- self._transport.process_document,
- default_timeout=None,
- client_info=_client_info,
- )
+ rpc = self._transport._wrapped_methods[self._transport.process_document]
# Certain fields should be provided within the metadata header;
# add these here.
@@ -358,18 +398,20 @@ def process_document(
)
# Send the request.
- response = rpc(request, retry=retry, timeout=timeout, metadata=metadata)
+ response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,)
# Done; return the response.
return response
try:
- _client_info = gapic_v1.client_info.ClientInfo(
- gapic_version=pkg_resources.get_distribution("google-cloud-documentai").version
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
+ gapic_version=pkg_resources.get_distribution(
+ "google-cloud-documentai",
+ ).version,
)
except pkg_resources.DistributionNotFound:
- _client_info = gapic_v1.client_info.ClientInfo()
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo()
__all__ = ("DocumentUnderstandingServiceClient",)
diff --git a/google/cloud/documentai_v1beta2/services/document_understanding_service/transports/base.py b/google/cloud/documentai_v1beta2/services/document_understanding_service/transports/base.py
index 17cc0ba0..df52dbcc 100644
--- a/google/cloud/documentai_v1beta2/services/document_understanding_service/transports/base.py
+++ b/google/cloud/documentai_v1beta2/services/document_understanding_service/transports/base.py
@@ -17,8 +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.api_core import operations_v1 # type: ignore
from google.auth import credentials # type: ignore
@@ -27,6 +31,16 @@
from google.longrunning import operations_pb2 as operations # type: ignore
+try:
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
+ gapic_version=pkg_resources.get_distribution(
+ "google-cloud-documentai",
+ ).version,
+ )
+except pkg_resources.DistributionNotFound:
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo()
+
+
class DocumentUnderstandingServiceTransport(abc.ABC):
"""Abstract transport class for DocumentUnderstandingService."""
@@ -37,6 +51,10 @@ def __init__(
*,
host: str = "us-documentai.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.
@@ -48,6 +66,17 @@ def __init__(
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:
@@ -56,12 +85,58 @@ def __init__(
# If no credentials are provided, then determine the appropriate
# defaults.
- if credentials is None:
- credentials, _ = auth.default(scopes=self.AUTH_SCOPES)
+ 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.batch_process_documents: gapic_v1.method.wrap_method(
+ self.batch_process_documents,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.ServiceUnavailable, exceptions.DeadlineExceeded,
+ ),
+ ),
+ default_timeout=120.0,
+ client_info=client_info,
+ ),
+ self.process_document: gapic_v1.method.wrap_method(
+ self.process_document,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.ServiceUnavailable, exceptions.DeadlineExceeded,
+ ),
+ ),
+ default_timeout=120.0,
+ client_info=client_info,
+ ),
+ }
+
@property
def operations_client(self) -> operations_v1.OperationsClient:
"""Return the client designed to process long-running operations."""
@@ -69,7 +144,7 @@ def operations_client(self) -> operations_v1.OperationsClient:
@property
def batch_process_documents(
- self
+ self,
) -> typing.Callable[
[document_understanding.BatchProcessDocumentsRequest],
typing.Union[operations.Operation, typing.Awaitable[operations.Operation]],
@@ -78,7 +153,7 @@ def batch_process_documents(
@property
def process_document(
- self
+ self,
) -> typing.Callable[
[document_understanding.ProcessDocumentRequest],
typing.Union[document.Document, typing.Awaitable[document.Document]],
diff --git a/google/cloud/documentai_v1beta2/services/document_understanding_service/transports/grpc.py b/google/cloud/documentai_v1beta2/services/document_understanding_service/transports/grpc.py
index e5acb31c..60f3e8b8 100644
--- a/google/cloud/documentai_v1beta2/services/document_understanding_service/transports/grpc.py
+++ b/google/cloud/documentai_v1beta2/services/document_understanding_service/transports/grpc.py
@@ -15,22 +15,23 @@
# 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 operations_v1 # 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.documentai_v1beta2.types import document
from google.cloud.documentai_v1beta2.types import document_understanding
from google.longrunning import operations_pb2 as operations # type: ignore
-from .base import DocumentUnderstandingServiceTransport
+from .base import DocumentUnderstandingServiceTransport, DEFAULT_CLIENT_INFO
class DocumentUnderstandingServiceGrpcTransport(DocumentUnderstandingServiceTransport):
@@ -55,9 +56,14 @@ def __init__(
*,
host: str = "us-documentai.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
+ 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.
@@ -69,20 +75,36 @@ def __init__(
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]): 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
- creation failed for any reason.
+ 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.
"""
if channel:
# Sanity check: Ensure that channel and credentials are not both
@@ -92,6 +114,11 @@ def __init__(
# If a channel was explicitly provided, set it.
self._grpc_channel = channel
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
@@ -99,7 +126,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.
@@ -115,21 +144,50 @@ def __init__(
self._grpc_channel = type(self).create_channel(
host,
credentials=credentials,
+ credentials_file=credentials_file,
ssl_credentials=ssl_credentials,
- scopes=self.AUTH_SCOPES,
+ scopes=scopes or self.AUTH_SCOPES,
+ quota_project_id=quota_project_id,
+ )
+ 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)
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 = "us-documentai.googleapis.com",
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:
@@ -139,17 +197,31 @@ def create_channel(
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, scopes=scopes, **kwargs
+ host,
+ credentials=credentials,
+ credentials_file=credentials_file,
+ scopes=scopes,
+ quota_project_id=quota_project_id,
+ **kwargs,
)
@property
@@ -159,13 +231,6 @@ def grpc_channel(self) -> grpc.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
@@ -187,7 +252,7 @@ def operations_client(self) -> operations_v1.OperationsClient:
@property
def batch_process_documents(
- self
+ self,
) -> Callable[
[document_understanding.BatchProcessDocumentsRequest], operations.Operation
]:
@@ -216,7 +281,7 @@ def batch_process_documents(
@property
def process_document(
- self
+ self,
) -> Callable[[document_understanding.ProcessDocumentRequest], document.Document]:
r"""Return a callable for the process document method over gRPC.
diff --git a/google/cloud/documentai_v1beta2/services/document_understanding_service/transports/grpc_asyncio.py b/google/cloud/documentai_v1beta2/services/document_understanding_service/transports/grpc_asyncio.py
index 063370da..315795e5 100644
--- a/google/cloud/documentai_v1beta2/services/document_understanding_service/transports/grpc_asyncio.py
+++ b/google/cloud/documentai_v1beta2/services/document_understanding_service/transports/grpc_asyncio.py
@@ -15,10 +15,13 @@
# 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.api_core import operations_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
@@ -29,7 +32,7 @@
from google.cloud.documentai_v1beta2.types import document_understanding
from google.longrunning import operations_pb2 as operations # type: ignore
-from .base import DocumentUnderstandingServiceTransport
+from .base import DocumentUnderstandingServiceTransport, DEFAULT_CLIENT_INFO
from .grpc import DocumentUnderstandingServiceGrpcTransport
@@ -58,8 +61,10 @@ def create_channel(
cls,
host: str = "us-documentai.googleapis.com",
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:
@@ -69,9 +74,14 @@ def create_channel(
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:
@@ -79,7 +89,12 @@ def create_channel(
"""
scopes = scopes or cls.AUTH_SCOPES
return grpc_helpers_async.create_channel(
- host, credentials=credentials, scopes=scopes, **kwargs
+ host,
+ credentials=credentials,
+ credentials_file=credentials_file,
+ scopes=scopes,
+ quota_project_id=quota_project_id,
+ **kwargs,
)
def __init__(
@@ -87,9 +102,14 @@ def __init__(
*,
host: str = "us-documentai.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
+ 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.
@@ -101,20 +121,37 @@ def __init__(
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]): 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
+ 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.
"""
if channel:
# Sanity check: Ensure that channel and credentials are not both
@@ -124,12 +161,22 @@ def __init__(
# If a channel was explicitly provided, set it.
self._grpc_channel = channel
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:
@@ -144,12 +191,39 @@ def __init__(
self._grpc_channel = type(self).create_channel(
host,
credentials=credentials,
+ credentials_file=credentials_file,
ssl_credentials=ssl_credentials,
- scopes=self.AUTH_SCOPES,
+ scopes=scopes or self.AUTH_SCOPES,
+ quota_project_id=quota_project_id,
+ )
+ 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)
+ 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
@@ -159,13 +233,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
@@ -187,7 +254,7 @@ def operations_client(self) -> operations_v1.OperationsAsyncClient:
@property
def batch_process_documents(
- self
+ self,
) -> Callable[
[document_understanding.BatchProcessDocumentsRequest],
Awaitable[operations.Operation],
@@ -217,7 +284,7 @@ def batch_process_documents(
@property
def process_document(
- self
+ self,
) -> Callable[
[document_understanding.ProcessDocumentRequest], Awaitable[document.Document]
]:
diff --git a/google/cloud/documentai_v1beta2/types/__init__.py b/google/cloud/documentai_v1beta2/types/__init__.py
index eeb87607..5d05c6b1 100644
--- a/google/cloud/documentai_v1beta2/types/__init__.py
+++ b/google/cloud/documentai_v1beta2/types/__init__.py
@@ -15,7 +15,11 @@
# limitations under the License.
#
-from .geometry import Vertex, NormalizedVertex, BoundingPoly
+from .geometry import (
+ Vertex,
+ NormalizedVertex,
+ BoundingPoly,
+)
from .document import Document
from .document_understanding import (
BatchProcessDocumentsRequest,
diff --git a/google/cloud/documentai_v1beta2/types/document.py b/google/cloud/documentai_v1beta2/types/document.py
index 086ee8bf..7b7d15af 100644
--- a/google/cloud/documentai_v1beta2/types/document.py
+++ b/google/cloud/documentai_v1beta2/types/document.py
@@ -24,7 +24,7 @@
__protobuf__ = proto.module(
- package="google.cloud.documentai.v1beta2", manifest={"Document"}
+ package="google.cloud.documentai.v1beta2", manifest={"Document",},
)
@@ -99,7 +99,9 @@ class ShardInfo(proto.Message):
"""
shard_index = proto.Field(proto.INT64, number=1)
+
shard_count = proto.Field(proto.INT64, number=2)
+
text_offset = proto.Field(proto.INT64, number=3)
class Label(proto.Message):
@@ -127,8 +129,10 @@ class Label(proto.Message):
assignment.
"""
- automl_model = proto.Field(proto.STRING, number=2)
+ automl_model = proto.Field(proto.STRING, number=2, oneof="source")
+
name = proto.Field(proto.STRING, number=1)
+
confidence = proto.Field(proto.FLOAT, number=3)
class Style(proto.Message):
@@ -168,18 +172,25 @@ class FontSize(proto.Message):
"""
size = proto.Field(proto.FLOAT, number=1)
+
unit = proto.Field(proto.STRING, number=2)
text_anchor = proto.Field(
- proto.MESSAGE, number=1, message="Document.TextAnchor"
+ proto.MESSAGE, number=1, message="Document.TextAnchor",
)
- color = proto.Field(proto.MESSAGE, number=2, message=gt_color.Color)
- background_color = proto.Field(proto.MESSAGE, number=3, message=gt_color.Color)
+
+ color = proto.Field(proto.MESSAGE, number=2, message=gt_color.Color,)
+
+ background_color = proto.Field(proto.MESSAGE, number=3, message=gt_color.Color,)
+
font_weight = proto.Field(proto.STRING, number=4)
+
text_style = proto.Field(proto.STRING, number=5)
+
text_decoration = proto.Field(proto.STRING, number=6)
+
font_size = proto.Field(
- proto.MESSAGE, number=7, message="Document.Style.FontSize"
+ proto.MESSAGE, number=7, message="Document.Style.FontSize",
)
class Page(proto.Message):
@@ -241,7 +252,9 @@ class Dimension(proto.Message):
"""
width = proto.Field(proto.FLOAT, number=1)
+
height = proto.Field(proto.FLOAT, number=2)
+
unit = proto.Field(proto.STRING, number=3)
class Layout(proto.Message):
@@ -277,15 +290,19 @@ class Orientation(proto.Enum):
PAGE_LEFT = 4
text_anchor = proto.Field(
- proto.MESSAGE, number=1, message="Document.TextAnchor"
+ proto.MESSAGE, number=1, message="Document.TextAnchor",
)
+
confidence = proto.Field(proto.FLOAT, number=2)
+
bounding_poly = proto.Field(
- proto.MESSAGE, number=3, message=geometry.BoundingPoly
+ proto.MESSAGE, number=3, message=geometry.BoundingPoly,
)
+
orientation = proto.Field(
- proto.ENUM, number=4, enum="Document.Page.Layout.Orientation"
+ proto.ENUM, number=4, enum="Document.Page.Layout.Orientation",
)
+
id = proto.Field(proto.STRING, number=5)
class Block(proto.Message):
@@ -303,10 +320,11 @@ class Block(proto.Message):
"""
layout = proto.Field(
- proto.MESSAGE, number=1, message="Document.Page.Layout"
+ proto.MESSAGE, number=1, message="Document.Page.Layout",
)
+
detected_languages = proto.RepeatedField(
- proto.MESSAGE, number=2, message="Document.Page.DetectedLanguage"
+ proto.MESSAGE, number=2, message="Document.Page.DetectedLanguage",
)
class Paragraph(proto.Message):
@@ -324,10 +342,11 @@ class Paragraph(proto.Message):
"""
layout = proto.Field(
- proto.MESSAGE, number=1, message="Document.Page.Layout"
+ proto.MESSAGE, number=1, message="Document.Page.Layout",
)
+
detected_languages = proto.RepeatedField(
- proto.MESSAGE, number=2, message="Document.Page.DetectedLanguage"
+ proto.MESSAGE, number=2, message="Document.Page.DetectedLanguage",
)
class Line(proto.Message):
@@ -346,10 +365,11 @@ class Line(proto.Message):
"""
layout = proto.Field(
- proto.MESSAGE, number=1, message="Document.Page.Layout"
+ proto.MESSAGE, number=1, message="Document.Page.Layout",
)
+
detected_languages = proto.RepeatedField(
- proto.MESSAGE, number=2, message="Document.Page.DetectedLanguage"
+ proto.MESSAGE, number=2, message="Document.Page.DetectedLanguage",
)
class Token(proto.Message):
@@ -373,7 +393,7 @@ class DetectedBreak(proto.Message):
[Token][google.cloud.documentai.v1beta2.Document.Page.Token].
Attributes:
- type (~.document.Document.Page.Token.DetectedBreak.Type):
+ type_ (~.document.Document.Page.Token.DetectedBreak.Type):
Detected break type.
"""
@@ -384,18 +404,20 @@ class Type(proto.Enum):
WIDE_SPACE = 2
HYPHEN = 3
- type = proto.Field(
- proto.ENUM, number=1, enum="Document.Page.Token.DetectedBreak.Type"
+ type_ = proto.Field(
+ proto.ENUM, number=1, enum="Document.Page.Token.DetectedBreak.Type",
)
layout = proto.Field(
- proto.MESSAGE, number=1, message="Document.Page.Layout"
+ proto.MESSAGE, number=1, message="Document.Page.Layout",
)
+
detected_break = proto.Field(
- proto.MESSAGE, number=2, message="Document.Page.Token.DetectedBreak"
+ proto.MESSAGE, number=2, message="Document.Page.Token.DetectedBreak",
)
+
detected_languages = proto.RepeatedField(
- proto.MESSAGE, number=3, message="Document.Page.DetectedLanguage"
+ proto.MESSAGE, number=3, message="Document.Page.DetectedLanguage",
)
class VisualElement(proto.Message):
@@ -407,7 +429,7 @@ class VisualElement(proto.Message):
[Layout][google.cloud.documentai.v1beta2.Document.Page.Layout]
for
[VisualElement][google.cloud.documentai.v1beta2.Document.Page.VisualElement].
- type (str):
+ type_ (str):
Type of the
[VisualElement][google.cloud.documentai.v1beta2.Document.Page.VisualElement].
detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
@@ -416,11 +438,13 @@ class VisualElement(proto.Message):
"""
layout = proto.Field(
- proto.MESSAGE, number=1, message="Document.Page.Layout"
+ proto.MESSAGE, number=1, message="Document.Page.Layout",
)
- type = proto.Field(proto.STRING, number=2)
+
+ type_ = proto.Field(proto.STRING, number=2)
+
detected_languages = proto.RepeatedField(
- proto.MESSAGE, number=3, message="Document.Page.DetectedLanguage"
+ proto.MESSAGE, number=3, message="Document.Page.DetectedLanguage",
)
class Table(proto.Message):
@@ -449,7 +473,7 @@ class TableRow(proto.Message):
"""
cells = proto.RepeatedField(
- proto.MESSAGE, number=1, message="Document.Page.Table.TableCell"
+ proto.MESSAGE, number=1, message="Document.Page.Table.TableCell",
)
class TableCell(proto.Message):
@@ -470,25 +494,31 @@ class TableCell(proto.Message):
"""
layout = proto.Field(
- proto.MESSAGE, number=1, message="Document.Page.Layout"
+ proto.MESSAGE, number=1, message="Document.Page.Layout",
)
+
row_span = proto.Field(proto.INT32, number=2)
+
col_span = proto.Field(proto.INT32, number=3)
+
detected_languages = proto.RepeatedField(
- proto.MESSAGE, number=4, message="Document.Page.DetectedLanguage"
+ proto.MESSAGE, number=4, message="Document.Page.DetectedLanguage",
)
layout = proto.Field(
- proto.MESSAGE, number=1, message="Document.Page.Layout"
+ proto.MESSAGE, number=1, message="Document.Page.Layout",
)
+
header_rows = proto.RepeatedField(
- proto.MESSAGE, number=2, message="Document.Page.Table.TableRow"
+ proto.MESSAGE, number=2, message="Document.Page.Table.TableRow",
)
+
body_rows = proto.RepeatedField(
- proto.MESSAGE, number=3, message="Document.Page.Table.TableRow"
+ proto.MESSAGE, number=3, message="Document.Page.Table.TableRow",
)
+
detected_languages = proto.RepeatedField(
- proto.MESSAGE, number=4, message="Document.Page.DetectedLanguage"
+ proto.MESSAGE, number=4, message="Document.Page.DetectedLanguage",
)
class FormField(proto.Message):
@@ -528,19 +558,25 @@ class FormField(proto.Message):
"""
field_name = proto.Field(
- proto.MESSAGE, number=1, message="Document.Page.Layout"
+ proto.MESSAGE, number=1, message="Document.Page.Layout",
)
+
field_value = proto.Field(
- proto.MESSAGE, number=2, message="Document.Page.Layout"
+ proto.MESSAGE, number=2, message="Document.Page.Layout",
)
+
name_detected_languages = proto.RepeatedField(
- proto.MESSAGE, number=3, message="Document.Page.DetectedLanguage"
+ proto.MESSAGE, number=3, message="Document.Page.DetectedLanguage",
)
+
value_detected_languages = proto.RepeatedField(
- proto.MESSAGE, number=4, message="Document.Page.DetectedLanguage"
+ proto.MESSAGE, number=4, message="Document.Page.DetectedLanguage",
)
+
value_type = proto.Field(proto.STRING, number=5)
+
corrected_key_text = proto.Field(proto.STRING, number=6)
+
corrected_value_text = proto.Field(proto.STRING, number=7)
class DetectedLanguage(proto.Message):
@@ -556,36 +592,47 @@ class DetectedLanguage(proto.Message):
"""
language_code = proto.Field(proto.STRING, number=1)
+
confidence = proto.Field(proto.FLOAT, number=2)
page_number = proto.Field(proto.INT32, number=1)
+
dimension = proto.Field(
- proto.MESSAGE, number=2, message="Document.Page.Dimension"
+ proto.MESSAGE, number=2, message="Document.Page.Dimension",
)
- layout = proto.Field(proto.MESSAGE, number=3, message="Document.Page.Layout")
+
+ layout = proto.Field(proto.MESSAGE, number=3, message="Document.Page.Layout",)
+
detected_languages = proto.RepeatedField(
- proto.MESSAGE, number=4, message="Document.Page.DetectedLanguage"
+ proto.MESSAGE, number=4, message="Document.Page.DetectedLanguage",
)
+
blocks = proto.RepeatedField(
- proto.MESSAGE, number=5, message="Document.Page.Block"
+ proto.MESSAGE, number=5, message="Document.Page.Block",
)
+
paragraphs = proto.RepeatedField(
- proto.MESSAGE, number=6, message="Document.Page.Paragraph"
+ proto.MESSAGE, number=6, message="Document.Page.Paragraph",
)
+
lines = proto.RepeatedField(
- proto.MESSAGE, number=7, message="Document.Page.Line"
+ proto.MESSAGE, number=7, message="Document.Page.Line",
)
+
tokens = proto.RepeatedField(
- proto.MESSAGE, number=8, message="Document.Page.Token"
+ proto.MESSAGE, number=8, message="Document.Page.Token",
)
+
visual_elements = proto.RepeatedField(
- proto.MESSAGE, number=9, message="Document.Page.VisualElement"
+ proto.MESSAGE, number=9, message="Document.Page.VisualElement",
)
+
tables = proto.RepeatedField(
- proto.MESSAGE, number=10, message="Document.Page.Table"
+ proto.MESSAGE, number=10, message="Document.Page.Table",
)
+
form_fields = proto.RepeatedField(
- proto.MESSAGE, number=11, message="Document.Page.FormField"
+ proto.MESSAGE, number=11, message="Document.Page.FormField",
)
class Entity(proto.Message):
@@ -596,7 +643,7 @@ class Entity(proto.Message):
text_anchor (~.document.Document.TextAnchor):
Provenance of the entity. Text anchor indexing into the
[Document.text][google.cloud.documentai.v1beta2.Document.text].
- type (str):
+ type_ (str):
Entity type from a schema e.g. ``Address``.
mention_text (str):
Text value in the document e.g. ``1600 Amphitheatre Pkwy``.
@@ -620,18 +667,25 @@ class Entity(proto.Message):
"""
text_anchor = proto.Field(
- proto.MESSAGE, number=1, message="Document.TextAnchor"
+ proto.MESSAGE, number=1, message="Document.TextAnchor",
)
- type = proto.Field(proto.STRING, number=2)
+
+ type_ = proto.Field(proto.STRING, number=2)
+
mention_text = proto.Field(proto.STRING, number=3)
+
mention_id = proto.Field(proto.STRING, number=4)
+
confidence = proto.Field(proto.FLOAT, number=5)
+
page_anchor = proto.Field(
- proto.MESSAGE, number=6, message="Document.PageAnchor"
+ proto.MESSAGE, number=6, message="Document.PageAnchor",
)
+
id = proto.Field(proto.STRING, number=7)
+
bounding_poly_for_demo_frontend = proto.Field(
- proto.MESSAGE, number=8, message=geometry.BoundingPoly
+ proto.MESSAGE, number=8, message=geometry.BoundingPoly,
)
class EntityRelation(proto.Message):
@@ -648,7 +702,9 @@ class EntityRelation(proto.Message):
"""
subject_id = proto.Field(proto.STRING, number=1)
+
object_id = proto.Field(proto.STRING, number=2)
+
relation = proto.Field(proto.STRING, number=3)
class TextAnchor(proto.Message):
@@ -680,10 +736,11 @@ class TextSegment(proto.Message):
"""
start_index = proto.Field(proto.INT64, number=1)
+
end_index = proto.Field(proto.INT64, number=2)
text_segments = proto.RepeatedField(
- proto.MESSAGE, number=1, message="Document.TextAnchor.TextSegment"
+ proto.MESSAGE, number=1, message="Document.TextAnchor.TextSegment",
)
class PageAnchor(proto.Message):
@@ -729,28 +786,40 @@ class LayoutType(proto.Enum):
FORM_FIELD = 7
page = proto.Field(proto.INT64, number=1)
+
layout_type = proto.Field(
- proto.ENUM, number=2, enum="Document.PageAnchor.PageRef.LayoutType"
+ proto.ENUM, number=2, enum="Document.PageAnchor.PageRef.LayoutType",
)
+
layout_id = proto.Field(proto.STRING, number=3)
page_refs = proto.RepeatedField(
- proto.MESSAGE, number=1, message="Document.PageAnchor.PageRef"
+ proto.MESSAGE, number=1, message="Document.PageAnchor.PageRef",
)
- uri = proto.Field(proto.STRING, number=1)
- content = proto.Field(proto.BYTES, number=2)
+ uri = proto.Field(proto.STRING, number=1, oneof="source")
+
+ content = proto.Field(proto.BYTES, number=2, oneof="source")
+
mime_type = proto.Field(proto.STRING, number=3)
+
text = proto.Field(proto.STRING, number=4)
- text_styles = proto.RepeatedField(proto.MESSAGE, number=5, message=Style)
- pages = proto.RepeatedField(proto.MESSAGE, number=6, message=Page)
- entities = proto.RepeatedField(proto.MESSAGE, number=7, message=Entity)
+
+ text_styles = proto.RepeatedField(proto.MESSAGE, number=5, message=Style,)
+
+ pages = proto.RepeatedField(proto.MESSAGE, number=6, message=Page,)
+
+ entities = proto.RepeatedField(proto.MESSAGE, number=7, message=Entity,)
+
entity_relations = proto.RepeatedField(
- proto.MESSAGE, number=8, message=EntityRelation
+ proto.MESSAGE, number=8, message=EntityRelation,
)
- shard_info = proto.Field(proto.MESSAGE, number=9, message=ShardInfo)
- labels = proto.RepeatedField(proto.MESSAGE, number=11, message=Label)
- error = proto.Field(proto.MESSAGE, number=10, message=status.Status)
+
+ shard_info = proto.Field(proto.MESSAGE, number=9, message=ShardInfo,)
+
+ labels = proto.RepeatedField(proto.MESSAGE, number=11, message=Label,)
+
+ error = proto.Field(proto.MESSAGE, number=10, message=status.Status,)
__all__ = tuple(sorted(__protobuf__.manifest))
diff --git a/google/cloud/documentai_v1beta2/types/document_understanding.py b/google/cloud/documentai_v1beta2/types/document_understanding.py
index 321344ae..bdf2299f 100644
--- a/google/cloud/documentai_v1beta2/types/document_understanding.py
+++ b/google/cloud/documentai_v1beta2/types/document_understanding.py
@@ -63,8 +63,9 @@ class BatchProcessDocumentsRequest(proto.Message):
"""
requests = proto.RepeatedField(
- proto.MESSAGE, number=1, message="ProcessDocumentRequest"
+ proto.MESSAGE, number=1, message="ProcessDocumentRequest",
)
+
parent = proto.Field(proto.STRING, number=2)
@@ -114,20 +115,28 @@ class ProcessDocumentRequest(proto.Message):
"""
parent = proto.Field(proto.STRING, number=9)
- input_config = proto.Field(proto.MESSAGE, number=1, message="InputConfig")
- output_config = proto.Field(proto.MESSAGE, number=2, message="OutputConfig")
+
+ input_config = proto.Field(proto.MESSAGE, number=1, message="InputConfig",)
+
+ output_config = proto.Field(proto.MESSAGE, number=2, message="OutputConfig",)
+
document_type = proto.Field(proto.STRING, number=3)
+
table_extraction_params = proto.Field(
- proto.MESSAGE, number=4, message="TableExtractionParams"
+ proto.MESSAGE, number=4, message="TableExtractionParams",
)
+
form_extraction_params = proto.Field(
- proto.MESSAGE, number=5, message="FormExtractionParams"
+ proto.MESSAGE, number=5, message="FormExtractionParams",
)
+
entity_extraction_params = proto.Field(
- proto.MESSAGE, number=6, message="EntityExtractionParams"
+ proto.MESSAGE, number=6, message="EntityExtractionParams",
)
- ocr_params = proto.Field(proto.MESSAGE, number=7, message="OcrParams")
- automl_params = proto.Field(proto.MESSAGE, number=8, message="AutoMlParams")
+
+ ocr_params = proto.Field(proto.MESSAGE, number=7, message="OcrParams",)
+
+ automl_params = proto.Field(proto.MESSAGE, number=8, message="AutoMlParams",)
class BatchProcessDocumentsResponse(proto.Message):
@@ -140,7 +149,7 @@ class BatchProcessDocumentsResponse(proto.Message):
"""
responses = proto.RepeatedField(
- proto.MESSAGE, number=1, message="ProcessDocumentResponse"
+ proto.MESSAGE, number=1, message="ProcessDocumentResponse",
)
@@ -158,8 +167,9 @@ class ProcessDocumentResponse(proto.Message):
objects.
"""
- input_config = proto.Field(proto.MESSAGE, number=1, message="InputConfig")
- output_config = proto.Field(proto.MESSAGE, number=2, message="OutputConfig")
+ input_config = proto.Field(proto.MESSAGE, number=1, message="InputConfig",)
+
+ output_config = proto.Field(proto.MESSAGE, number=2, message="OutputConfig",)
class OcrParams(proto.Message):
@@ -203,10 +213,13 @@ class TableExtractionParams(proto.Message):
"""
enabled = proto.Field(proto.BOOL, number=1)
+
table_bound_hints = proto.RepeatedField(
- proto.MESSAGE, number=2, message="TableBoundHint"
+ proto.MESSAGE, number=2, message="TableBoundHint",
)
+
header_hints = proto.RepeatedField(proto.STRING, number=3)
+
model_version = proto.Field(proto.STRING, number=4)
@@ -227,7 +240,8 @@ class TableBoundHint(proto.Message):
"""
page_number = proto.Field(proto.INT32, number=1)
- bounding_box = proto.Field(proto.MESSAGE, number=2, message=geometry.BoundingPoly)
+
+ bounding_box = proto.Field(proto.MESSAGE, number=2, message=geometry.BoundingPoly,)
class FormExtractionParams(proto.Message):
@@ -261,9 +275,11 @@ class FormExtractionParams(proto.Message):
"""
enabled = proto.Field(proto.BOOL, number=1)
+
key_value_pair_hints = proto.RepeatedField(
- proto.MESSAGE, number=2, message="KeyValuePairHint"
+ proto.MESSAGE, number=2, message="KeyValuePairHint",
)
+
model_version = proto.Field(proto.STRING, number=3)
@@ -281,6 +297,7 @@ class KeyValuePairHint(proto.Message):
"""
key = proto.Field(proto.STRING, number=1)
+
value_types = proto.RepeatedField(proto.STRING, number=2)
@@ -297,6 +314,7 @@ class EntityExtractionParams(proto.Message):
"""
enabled = proto.Field(proto.BOOL, number=1)
+
model_version = proto.Field(proto.STRING, number=2)
@@ -338,8 +356,12 @@ class InputConfig(proto.Message):
[Document][google.cloud.documentai.v1beta2.Document] format.
"""
- gcs_source = proto.Field(proto.MESSAGE, number=1, message="GcsSource")
- contents = proto.Field(proto.BYTES, number=3)
+ gcs_source = proto.Field(
+ proto.MESSAGE, number=1, oneof="source", message="GcsSource",
+ )
+
+ contents = proto.Field(proto.BYTES, number=3, oneof="source")
+
mime_type = proto.Field(proto.STRING, number=2)
@@ -370,7 +392,10 @@ class OutputConfig(proto.Message):
pages-101-to-150.json pages-151-to-157.json
"""
- gcs_destination = proto.Field(proto.MESSAGE, number=1, message="GcsDestination")
+ gcs_destination = proto.Field(
+ proto.MESSAGE, number=1, oneof="destination", message="GcsDestination",
+ )
+
pages_per_shard = proto.Field(proto.INT32, number=2)
@@ -423,10 +448,13 @@ class State(proto.Enum):
CANCELLED = 5
FAILED = 6
- state = proto.Field(proto.ENUM, number=1, enum=State)
+ state = proto.Field(proto.ENUM, number=1, enum=State,)
+
state_message = proto.Field(proto.STRING, number=2)
- create_time = proto.Field(proto.MESSAGE, number=3, message=timestamp.Timestamp)
- update_time = proto.Field(proto.MESSAGE, number=4, message=timestamp.Timestamp)
+
+ create_time = proto.Field(proto.MESSAGE, number=3, message=timestamp.Timestamp,)
+
+ update_time = proto.Field(proto.MESSAGE, number=4, message=timestamp.Timestamp,)
__all__ = tuple(sorted(__protobuf__.manifest))
diff --git a/google/cloud/documentai_v1beta2/types/geometry.py b/google/cloud/documentai_v1beta2/types/geometry.py
index a5173fa1..12d63f90 100644
--- a/google/cloud/documentai_v1beta2/types/geometry.py
+++ b/google/cloud/documentai_v1beta2/types/geometry.py
@@ -20,7 +20,7 @@
__protobuf__ = proto.module(
package="google.cloud.documentai.v1beta2",
- manifest={"Vertex", "NormalizedVertex", "BoundingPoly"},
+ manifest={"Vertex", "NormalizedVertex", "BoundingPoly",},
)
@@ -37,6 +37,7 @@ class Vertex(proto.Message):
"""
x = proto.Field(proto.INT32, number=1)
+
y = proto.Field(proto.INT32, number=2)
@@ -53,6 +54,7 @@ class NormalizedVertex(proto.Message):
"""
x = proto.Field(proto.FLOAT, number=1)
+
y = proto.Field(proto.FLOAT, number=2)
@@ -66,9 +68,10 @@ class BoundingPoly(proto.Message):
The bounding polygon normalized vertices.
"""
- vertices = proto.RepeatedField(proto.MESSAGE, number=1, message=Vertex)
+ vertices = proto.RepeatedField(proto.MESSAGE, number=1, message=Vertex,)
+
normalized_vertices = proto.RepeatedField(
- proto.MESSAGE, number=2, message=NormalizedVertex
+ proto.MESSAGE, number=2, message=NormalizedVertex,
)
diff --git a/google/cloud/documentai_v1beta3/__init__.py b/google/cloud/documentai_v1beta3/__init__.py
new file mode 100644
index 00000000..c93f255b
--- /dev/null
+++ b/google/cloud/documentai_v1beta3/__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.document_processor_service import DocumentProcessorServiceClient
+from .types.document import Document
+from .types.document_processor_service import BatchProcessMetadata
+from .types.document_processor_service import BatchProcessRequest
+from .types.document_processor_service import BatchProcessResponse
+from .types.document_processor_service import ProcessRequest
+from .types.document_processor_service import ProcessResponse
+from .types.document_processor_service import ReviewDocumentOperationMetadata
+from .types.document_processor_service import ReviewDocumentRequest
+from .types.document_processor_service import ReviewDocumentResponse
+from .types.geometry import BoundingPoly
+from .types.geometry import NormalizedVertex
+from .types.geometry import Vertex
+
+
+__all__ = (
+ "BatchProcessMetadata",
+ "BatchProcessRequest",
+ "BatchProcessResponse",
+ "BoundingPoly",
+ "Document",
+ "NormalizedVertex",
+ "ProcessRequest",
+ "ProcessResponse",
+ "ReviewDocumentOperationMetadata",
+ "ReviewDocumentRequest",
+ "ReviewDocumentResponse",
+ "Vertex",
+ "DocumentProcessorServiceClient",
+)
diff --git a/google/cloud/documentai_v1beta3/py.typed b/google/cloud/documentai_v1beta3/py.typed
new file mode 100644
index 00000000..81b45001
--- /dev/null
+++ b/google/cloud/documentai_v1beta3/py.typed
@@ -0,0 +1,2 @@
+# Marker file for PEP 561.
+# The google-cloud-documentai package uses inline types.
diff --git a/google/cloud/documentai_v1beta3/services/__init__.py b/google/cloud/documentai_v1beta3/services/__init__.py
new file mode 100644
index 00000000..42ffdf2b
--- /dev/null
+++ b/google/cloud/documentai_v1beta3/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/documentai_v1beta3/services/document_processor_service/__init__.py b/google/cloud/documentai_v1beta3/services/document_processor_service/__init__.py
new file mode 100644
index 00000000..9f87d9f4
--- /dev/null
+++ b/google/cloud/documentai_v1beta3/services/document_processor_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 DocumentProcessorServiceClient
+from .async_client import DocumentProcessorServiceAsyncClient
+
+__all__ = (
+ "DocumentProcessorServiceClient",
+ "DocumentProcessorServiceAsyncClient",
+)
diff --git a/google/cloud/documentai_v1beta3/services/document_processor_service/async_client.py b/google/cloud/documentai_v1beta3/services/document_processor_service/async_client.py
new file mode 100644
index 00000000..7ba80ac2
--- /dev/null
+++ b/google/cloud/documentai_v1beta3/services/document_processor_service/async_client.py
@@ -0,0 +1,385 @@
+# -*- 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.api_core import operation # type: ignore
+from google.api_core import operation_async # type: ignore
+from google.cloud.documentai_v1beta3.types import document
+from google.cloud.documentai_v1beta3.types import document_processor_service
+
+from .transports.base import DocumentProcessorServiceTransport, DEFAULT_CLIENT_INFO
+from .transports.grpc_asyncio import DocumentProcessorServiceGrpcAsyncIOTransport
+from .client import DocumentProcessorServiceClient
+
+
+class DocumentProcessorServiceAsyncClient:
+ """Service to call Cloud DocumentAI to process documents
+ according to the processor's definition. Processors are built
+ using state-of-the-art Google AI such as natural language,
+ computer vision, and translation to extract structured
+ information from unstructured or semi-structured documents.
+ """
+
+ _client: DocumentProcessorServiceClient
+
+ DEFAULT_ENDPOINT = DocumentProcessorServiceClient.DEFAULT_ENDPOINT
+ DEFAULT_MTLS_ENDPOINT = DocumentProcessorServiceClient.DEFAULT_MTLS_ENDPOINT
+
+ from_service_account_file = DocumentProcessorServiceClient.from_service_account_file
+ from_service_account_json = from_service_account_file
+
+ get_transport_class = functools.partial(
+ type(DocumentProcessorServiceClient).get_transport_class,
+ type(DocumentProcessorServiceClient),
+ )
+
+ def __init__(
+ self,
+ *,
+ credentials: credentials.Credentials = None,
+ transport: Union[str, DocumentProcessorServiceTransport] = "grpc_asyncio",
+ client_options: ClientOptions = None,
+ client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
+ ) -> None:
+ """Instantiate the document processor 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, ~.DocumentProcessorServiceTransport]): 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 = DocumentProcessorServiceClient(
+ credentials=credentials,
+ transport=transport,
+ client_options=client_options,
+ client_info=client_info,
+ )
+
+ async def process_document(
+ self,
+ request: document_processor_service.ProcessRequest = None,
+ *,
+ name: str = None,
+ retry: retries.Retry = gapic_v1.method.DEFAULT,
+ timeout: float = None,
+ metadata: Sequence[Tuple[str, str]] = (),
+ ) -> document_processor_service.ProcessResponse:
+ r"""Processes a single document.
+
+ Args:
+ request (:class:`~.document_processor_service.ProcessRequest`):
+ The request object. Request message for the process
+ document method.
+ name (:class:`str`):
+ Required. The processor resource
+ name.
+ 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:
+ ~.document_processor_service.ProcessResponse:
+ Response message for the process
+ document method.
+
+ """
+ # Create or coerce a protobuf request object.
+ # Sanity check: If we got a request object, we should *not* have
+ # gotten any keyword arguments that map to the request.
+ if request is not None and any([name]):
+ raise ValueError(
+ "If the `request` argument is set, then none of "
+ "the individual field arguments should be set."
+ )
+
+ request = document_processor_service.ProcessRequest(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.process_document,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.ServiceUnavailable, exceptions.DeadlineExceeded,
+ ),
+ ),
+ default_timeout=120.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 batch_process_documents(
+ self,
+ request: document_processor_service.BatchProcessRequest = None,
+ *,
+ name: str = None,
+ retry: retries.Retry = gapic_v1.method.DEFAULT,
+ timeout: float = None,
+ metadata: Sequence[Tuple[str, str]] = (),
+ ) -> operation_async.AsyncOperation:
+ r"""LRO endpoint to batch process many documents. The output is
+ written to Cloud Storage as JSON in the [Document] format.
+
+ Args:
+ request (:class:`~.document_processor_service.BatchProcessRequest`):
+ The request object. Request message for batch process
+ document method.
+ name (:class:`str`):
+ Required. The processor resource
+ name.
+ 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:
+ ~.operation_async.AsyncOperation:
+ An object representing a long-running operation.
+
+ The result type for the operation will be
+ :class:``~.document_processor_service.BatchProcessResponse``:
+ Response message for batch process document method.
+
+ """
+ # Create or coerce a protobuf request object.
+ # Sanity check: If we got a request object, we should *not* have
+ # gotten any keyword arguments that map to the request.
+ if request is not None and any([name]):
+ raise ValueError(
+ "If the `request` argument is set, then none of "
+ "the individual field arguments should be set."
+ )
+
+ request = document_processor_service.BatchProcessRequest(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.batch_process_documents,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.ServiceUnavailable, exceptions.DeadlineExceeded,
+ ),
+ ),
+ default_timeout=120.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,)
+
+ # Wrap the response in an operation future.
+ response = operation_async.from_gapic(
+ response,
+ self._client._transport.operations_client,
+ document_processor_service.BatchProcessResponse,
+ metadata_type=document_processor_service.BatchProcessMetadata,
+ )
+
+ # Done; return the response.
+ return response
+
+ async def review_document(
+ self,
+ request: document_processor_service.ReviewDocumentRequest = None,
+ *,
+ human_review_config: str = None,
+ retry: retries.Retry = gapic_v1.method.DEFAULT,
+ timeout: float = None,
+ metadata: Sequence[Tuple[str, str]] = (),
+ ) -> operation_async.AsyncOperation:
+ r"""Send a document for Human Review. The input document
+ should be processed by the specified processor.
+
+ Args:
+ request (:class:`~.document_processor_service.ReviewDocumentRequest`):
+ The request object. Request message for review document
+ method.
+ human_review_config (:class:`str`):
+ Required. The resource name of the
+ HumanReviewConfig that the document will
+ be reviewed with.
+ This corresponds to the ``human_review_config`` 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:
+ ~.operation_async.AsyncOperation:
+ An object representing a long-running operation.
+
+ The result type for the operation will be
+ :class:``~.document_processor_service.ReviewDocumentResponse``:
+ Response message for review document method.
+
+ """
+ # Create or coerce a protobuf request object.
+ # Sanity check: If we got a request object, we should *not* have
+ # gotten any keyword arguments that map to the request.
+ if request is not None and any([human_review_config]):
+ raise ValueError(
+ "If the `request` argument is set, then none of "
+ "the individual field arguments should be set."
+ )
+
+ request = document_processor_service.ReviewDocumentRequest(request)
+
+ # If we have keyword arguments corresponding to fields on the
+ # request, apply these.
+
+ if human_review_config is not None:
+ request.human_review_config = human_review_config
+
+ # Wrap the RPC method; this adds retry and timeout information,
+ # and friendly error handling.
+ rpc = gapic_v1.method_async.wrap_method(
+ self._client._transport.review_document,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.ServiceUnavailable, exceptions.DeadlineExceeded,
+ ),
+ ),
+ default_timeout=120.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(
+ (("human_review_config", request.human_review_config),)
+ ),
+ )
+
+ # Send the request.
+ response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,)
+
+ # Wrap the response in an operation future.
+ response = operation_async.from_gapic(
+ response,
+ self._client._transport.operations_client,
+ document_processor_service.ReviewDocumentResponse,
+ metadata_type=document_processor_service.ReviewDocumentOperationMetadata,
+ )
+
+ # Done; return the response.
+ return response
+
+
+try:
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
+ gapic_version=pkg_resources.get_distribution(
+ "google-cloud-documentai",
+ ).version,
+ )
+except pkg_resources.DistributionNotFound:
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo()
+
+
+__all__ = ("DocumentProcessorServiceAsyncClient",)
diff --git a/google/cloud/documentai_v1beta3/services/document_processor_service/client.py b/google/cloud/documentai_v1beta3/services/document_processor_service/client.py
new file mode 100644
index 00000000..84b57f36
--- /dev/null
+++ b/google/cloud/documentai_v1beta3/services/document_processor_service/client.py
@@ -0,0 +1,516 @@
+# -*- 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.api_core import operation # type: ignore
+from google.api_core import operation_async # type: ignore
+from google.cloud.documentai_v1beta3.types import document
+from google.cloud.documentai_v1beta3.types import document_processor_service
+
+from .transports.base import DocumentProcessorServiceTransport, DEFAULT_CLIENT_INFO
+from .transports.grpc import DocumentProcessorServiceGrpcTransport
+from .transports.grpc_asyncio import DocumentProcessorServiceGrpcAsyncIOTransport
+
+
+class DocumentProcessorServiceClientMeta(type):
+ """Metaclass for the DocumentProcessorService 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[DocumentProcessorServiceTransport]]
+ _transport_registry["grpc"] = DocumentProcessorServiceGrpcTransport
+ _transport_registry["grpc_asyncio"] = DocumentProcessorServiceGrpcAsyncIOTransport
+
+ def get_transport_class(
+ cls, label: str = None,
+ ) -> Type[DocumentProcessorServiceTransport]:
+ """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 DocumentProcessorServiceClient(metaclass=DocumentProcessorServiceClientMeta):
+ """Service to call Cloud DocumentAI to process documents
+ according to the processor's definition. Processors are built
+ using state-of-the-art Google AI such as natural language,
+ computer vision, and translation to extract structured
+ information from unstructured or semi-structured documents.
+ """
+
+ @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 = "us-documentai.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
+
+ def __init__(
+ self,
+ *,
+ credentials: Optional[credentials.Credentials] = None,
+ transport: Union[str, DocumentProcessorServiceTransport, None] = None,
+ client_options: Optional[client_options_lib.ClientOptions] = None,
+ client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
+ ) -> None:
+ """Instantiate the document processor 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, ~.DocumentProcessorServiceTransport]): 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, DocumentProcessorServiceTransport):
+ # transport is a DocumentProcessorServiceTransport 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 process_document(
+ self,
+ request: document_processor_service.ProcessRequest = None,
+ *,
+ name: str = None,
+ retry: retries.Retry = gapic_v1.method.DEFAULT,
+ timeout: float = None,
+ metadata: Sequence[Tuple[str, str]] = (),
+ ) -> document_processor_service.ProcessResponse:
+ r"""Processes a single document.
+
+ Args:
+ request (:class:`~.document_processor_service.ProcessRequest`):
+ The request object. Request message for the process
+ document method.
+ name (:class:`str`):
+ Required. The processor resource
+ name.
+ 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:
+ ~.document_processor_service.ProcessResponse:
+ Response message for the process
+ document method.
+
+ """
+ # 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 document_processor_service.ProcessRequest.
+ # There's no risk of modifying the input as we've already verified
+ # there are no flattened fields.
+ if not isinstance(request, document_processor_service.ProcessRequest):
+ request = document_processor_service.ProcessRequest(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.process_document]
+
+ # 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 batch_process_documents(
+ self,
+ request: document_processor_service.BatchProcessRequest = None,
+ *,
+ name: str = None,
+ retry: retries.Retry = gapic_v1.method.DEFAULT,
+ timeout: float = None,
+ metadata: Sequence[Tuple[str, str]] = (),
+ ) -> operation.Operation:
+ r"""LRO endpoint to batch process many documents. The output is
+ written to Cloud Storage as JSON in the [Document] format.
+
+ Args:
+ request (:class:`~.document_processor_service.BatchProcessRequest`):
+ The request object. Request message for batch process
+ document method.
+ name (:class:`str`):
+ Required. The processor resource
+ name.
+ 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:
+ ~.operation.Operation:
+ An object representing a long-running operation.
+
+ The result type for the operation will be
+ :class:``~.document_processor_service.BatchProcessResponse``:
+ Response message for batch process document method.
+
+ """
+ # 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 document_processor_service.BatchProcessRequest.
+ # There's no risk of modifying the input as we've already verified
+ # there are no flattened fields.
+ if not isinstance(request, document_processor_service.BatchProcessRequest):
+ request = document_processor_service.BatchProcessRequest(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.batch_process_documents]
+
+ # 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,)
+
+ # Wrap the response in an operation future.
+ response = operation.from_gapic(
+ response,
+ self._transport.operations_client,
+ document_processor_service.BatchProcessResponse,
+ metadata_type=document_processor_service.BatchProcessMetadata,
+ )
+
+ # Done; return the response.
+ return response
+
+ def review_document(
+ self,
+ request: document_processor_service.ReviewDocumentRequest = None,
+ *,
+ human_review_config: str = None,
+ retry: retries.Retry = gapic_v1.method.DEFAULT,
+ timeout: float = None,
+ metadata: Sequence[Tuple[str, str]] = (),
+ ) -> operation.Operation:
+ r"""Send a document for Human Review. The input document
+ should be processed by the specified processor.
+
+ Args:
+ request (:class:`~.document_processor_service.ReviewDocumentRequest`):
+ The request object. Request message for review document
+ method.
+ human_review_config (:class:`str`):
+ Required. The resource name of the
+ HumanReviewConfig that the document will
+ be reviewed with.
+ This corresponds to the ``human_review_config`` 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:
+ ~.operation.Operation:
+ An object representing a long-running operation.
+
+ The result type for the operation will be
+ :class:``~.document_processor_service.ReviewDocumentResponse``:
+ Response message for review document method.
+
+ """
+ # 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([human_review_config])
+ 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 document_processor_service.ReviewDocumentRequest.
+ # There's no risk of modifying the input as we've already verified
+ # there are no flattened fields.
+ if not isinstance(request, document_processor_service.ReviewDocumentRequest):
+ request = document_processor_service.ReviewDocumentRequest(request)
+
+ # If we have keyword arguments corresponding to fields on the
+ # request, apply these.
+
+ if human_review_config is not None:
+ request.human_review_config = human_review_config
+
+ # Wrap the RPC method; this adds retry and timeout information,
+ # and friendly error handling.
+ rpc = self._transport._wrapped_methods[self._transport.review_document]
+
+ # Certain fields should be provided within the metadata header;
+ # add these here.
+ metadata = tuple(metadata) + (
+ gapic_v1.routing_header.to_grpc_metadata(
+ (("human_review_config", request.human_review_config),)
+ ),
+ )
+
+ # Send the request.
+ response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,)
+
+ # Wrap the response in an operation future.
+ response = operation.from_gapic(
+ response,
+ self._transport.operations_client,
+ document_processor_service.ReviewDocumentResponse,
+ metadata_type=document_processor_service.ReviewDocumentOperationMetadata,
+ )
+
+ # Done; return the response.
+ return response
+
+
+try:
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
+ gapic_version=pkg_resources.get_distribution(
+ "google-cloud-documentai",
+ ).version,
+ )
+except pkg_resources.DistributionNotFound:
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo()
+
+
+__all__ = ("DocumentProcessorServiceClient",)
diff --git a/google/cloud/documentai_v1beta3/services/document_processor_service/transports/__init__.py b/google/cloud/documentai_v1beta3/services/document_processor_service/transports/__init__.py
new file mode 100644
index 00000000..a613297c
--- /dev/null
+++ b/google/cloud/documentai_v1beta3/services/document_processor_service/transports/__init__.py
@@ -0,0 +1,38 @@
+# -*- 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 DocumentProcessorServiceTransport
+from .grpc import DocumentProcessorServiceGrpcTransport
+from .grpc_asyncio import DocumentProcessorServiceGrpcAsyncIOTransport
+
+
+# Compile a registry of transports.
+_transport_registry = (
+ OrderedDict()
+) # type: Dict[str, Type[DocumentProcessorServiceTransport]]
+_transport_registry["grpc"] = DocumentProcessorServiceGrpcTransport
+_transport_registry["grpc_asyncio"] = DocumentProcessorServiceGrpcAsyncIOTransport
+
+
+__all__ = (
+ "DocumentProcessorServiceTransport",
+ "DocumentProcessorServiceGrpcTransport",
+ "DocumentProcessorServiceGrpcAsyncIOTransport",
+)
diff --git a/google/cloud/documentai_v1beta3/services/document_processor_service/transports/base.py b/google/cloud/documentai_v1beta3/services/document_processor_service/transports/base.py
new file mode 100644
index 00000000..dfa32e24
--- /dev/null
+++ b/google/cloud/documentai_v1beta3/services/document_processor_service/transports/base.py
@@ -0,0 +1,188 @@
+# -*- 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.api_core import operations_v1 # type: ignore
+from google.auth import credentials # type: ignore
+
+from google.cloud.documentai_v1beta3.types import document_processor_service
+from google.longrunning import operations_pb2 as operations # type: ignore
+
+
+try:
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
+ gapic_version=pkg_resources.get_distribution(
+ "google-cloud-documentai",
+ ).version,
+ )
+except pkg_resources.DistributionNotFound:
+ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo()
+
+
+class DocumentProcessorServiceTransport(abc.ABC):
+ """Abstract transport class for DocumentProcessorService."""
+
+ AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",)
+
+ def __init__(
+ self,
+ *,
+ host: str = "us-documentai.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.process_document: gapic_v1.method.wrap_method(
+ self.process_document,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.ServiceUnavailable, exceptions.DeadlineExceeded,
+ ),
+ ),
+ default_timeout=120.0,
+ client_info=client_info,
+ ),
+ self.batch_process_documents: gapic_v1.method.wrap_method(
+ self.batch_process_documents,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.ServiceUnavailable, exceptions.DeadlineExceeded,
+ ),
+ ),
+ default_timeout=120.0,
+ client_info=client_info,
+ ),
+ self.review_document: gapic_v1.method.wrap_method(
+ self.review_document,
+ default_retry=retries.Retry(
+ initial=0.1,
+ maximum=60.0,
+ multiplier=1.3,
+ predicate=retries.if_exception_type(
+ exceptions.ServiceUnavailable, exceptions.DeadlineExceeded,
+ ),
+ ),
+ default_timeout=120.0,
+ client_info=client_info,
+ ),
+ }
+
+ @property
+ def operations_client(self) -> operations_v1.OperationsClient:
+ """Return the client designed to process long-running operations."""
+ raise NotImplementedError()
+
+ @property
+ def process_document(
+ self,
+ ) -> typing.Callable[
+ [document_processor_service.ProcessRequest],
+ typing.Union[
+ document_processor_service.ProcessResponse,
+ typing.Awaitable[document_processor_service.ProcessResponse],
+ ],
+ ]:
+ raise NotImplementedError()
+
+ @property
+ def batch_process_documents(
+ self,
+ ) -> typing.Callable[
+ [document_processor_service.BatchProcessRequest],
+ typing.Union[operations.Operation, typing.Awaitable[operations.Operation]],
+ ]:
+ raise NotImplementedError()
+
+ @property
+ def review_document(
+ self,
+ ) -> typing.Callable[
+ [document_processor_service.ReviewDocumentRequest],
+ typing.Union[operations.Operation, typing.Awaitable[operations.Operation]],
+ ]:
+ raise NotImplementedError()
+
+
+__all__ = ("DocumentProcessorServiceTransport",)
diff --git a/google/cloud/documentai_v1beta3/services/document_processor_service/transports/grpc.py b/google/cloud/documentai_v1beta3/services/document_processor_service/transports/grpc.py
new file mode 100644
index 00000000..d7220126
--- /dev/null
+++ b/google/cloud/documentai_v1beta3/services/document_processor_service/transports/grpc.py
@@ -0,0 +1,342 @@
+# -*- 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 operations_v1 # 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.documentai_v1beta3.types import document_processor_service
+from google.longrunning import operations_pb2 as operations # type: ignore
+
+from .base import DocumentProcessorServiceTransport, DEFAULT_CLIENT_INFO
+
+
+class DocumentProcessorServiceGrpcTransport(DocumentProcessorServiceTransport):
+ """gRPC backend transport for DocumentProcessorService.
+
+ Service to call Cloud DocumentAI to process documents
+ according to the processor's definition. Processors are built
+ using state-of-the-art Google AI such as natural language,
+ computer vision, and translation to extract structured
+ information from unstructured or semi-structured documents.
+
+ 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 = "us-documentai.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.
+ """
+ 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
+ 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,
+ )
+ 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 = "us-documentai.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:
+ """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 operations_client(self) -> operations_v1.OperationsClient:
+ """Create the client designed to process long-running operations.
+
+ This property caches on the instance; repeated calls return the same
+ client.
+ """
+ # Sanity check: Only create a new client if we do not already have one.
+ if "operations_client" not in self.__dict__:
+ self.__dict__["operations_client"] = operations_v1.OperationsClient(
+ self.grpc_channel
+ )
+
+ # Return the client from cache.
+ return self.__dict__["operations_client"]
+
+ @property
+ def process_document(
+ self,
+ ) -> Callable[
+ [document_processor_service.ProcessRequest],
+ document_processor_service.ProcessResponse,
+ ]:
+ r"""Return a callable for the process document method over gRPC.
+
+ Processes a single document.
+
+ Returns:
+ Callable[[~.ProcessRequest],
+ ~.ProcessResponse]:
+ 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 "process_document" not in self._stubs:
+ self._stubs["process_document"] = self.grpc_channel.unary_unary(
+ "/google.cloud.documentai.v1beta3.DocumentProcessorService/ProcessDocument",
+ request_serializer=document_processor_service.ProcessRequest.serialize,
+ response_deserializer=document_processor_service.ProcessResponse.deserialize,
+ )
+ return self._stubs["process_document"]
+
+ @property
+ def batch_process_documents(
+ self,
+ ) -> Callable[
+ [document_processor_service.BatchProcessRequest], operations.Operation
+ ]:
+ r"""Return a callable for the batch process documents method over gRPC.
+
+ LRO endpoint to batch process many documents. The output is
+ written to Cloud Storage as JSON in the [Document] format.
+
+ Returns:
+ Callable[[~.BatchProcessRequest],
+ ~.Operation]:
+ 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 "batch_process_documents" not in self._stubs:
+ self._stubs["batch_process_documents"] = self.grpc_channel.unary_unary(
+ "/google.cloud.documentai.v1beta3.DocumentProcessorService/BatchProcessDocuments",
+ request_serializer=document_processor_service.BatchProcessRequest.serialize,
+ response_deserializer=operations.Operation.FromString,
+ )
+ return self._stubs["batch_process_documents"]
+
+ @property
+ def review_document(
+ self,
+ ) -> Callable[
+ [document_processor_service.ReviewDocumentRequest], operations.Operation
+ ]:
+ r"""Return a callable for the review document method over gRPC.
+
+ Send a document for Human Review. The input document
+ should be processed by the specified processor.
+
+ Returns:
+ Callable[[~.ReviewDocumentRequest],
+ ~.Operation]:
+ 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 "review_document" not in self._stubs:
+ self._stubs["review_document"] = self.grpc_channel.unary_unary(
+ "/google.cloud.documentai.v1beta3.DocumentProcessorService/ReviewDocument",
+ request_serializer=document_processor_service.ReviewDocumentRequest.serialize,
+ response_deserializer=operations.Operation.FromString,
+ )
+ return self._stubs["review_document"]
+
+
+__all__ = ("DocumentProcessorServiceGrpcTransport",)
diff --git a/google/cloud/documentai_v1beta3/services/document_processor_service/transports/grpc_asyncio.py b/google/cloud/documentai_v1beta3/services/document_processor_service/transports/grpc_asyncio.py
new file mode 100644
index 00000000..391819cf
--- /dev/null
+++ b/google/cloud/documentai_v1beta3/services/document_processor_service/transports/grpc_asyncio.py
@@ -0,0 +1,344 @@
+# -*- 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.api_core import operations_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 grpc.experimental import aio # type: ignore
+
+from google.cloud.documentai_v1beta3.types import document_processor_service
+from google.longrunning import operations_pb2 as operations # type: ignore
+
+from .base import DocumentProcessorServiceTransport, DEFAULT_CLIENT_INFO
+from .grpc import DocumentProcessorServiceGrpcTransport
+
+
+class DocumentProcessorServiceGrpcAsyncIOTransport(DocumentProcessorServiceTransport):
+ """gRPC AsyncIO backend transport for DocumentProcessorService.
+
+ Service to call Cloud DocumentAI to process documents
+ according to the processor's definition. Processors are built
+ using state-of-the-art Google AI such as natural language,
+ computer vision, and translation to extract structured
+ information from unstructured or semi-structured documents.
+
+ 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 = "us-documentai.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 = "us-documentai.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.
+ """
+ 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
+ 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,
+ )
+ 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 operations_client(self) -> operations_v1.OperationsAsyncClient:
+ """Create the client designed to process long-running operations.
+
+ This property caches on the instance; repeated calls return the same
+ client.
+ """
+ # Sanity check: Only create a new client if we do not already have one.
+ if "operations_client" not in self.__dict__:
+ self.__dict__["operations_client"] = operations_v1.OperationsAsyncClient(
+ self.grpc_channel
+ )
+
+ # Return the client from cache.
+ return self.__dict__["operations_client"]
+
+ @property
+ def process_document(
+ self,
+ ) -> Callable[
+ [document_processor_service.ProcessRequest],
+ Awaitable[document_processor_service.ProcessResponse],
+ ]:
+ r"""Return a callable for the process document method over gRPC.
+
+ Processes a single document.
+
+ Returns:
+ Callable[[~.ProcessRequest],
+ Awaitable[~.ProcessResponse]]:
+ 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 "process_document" not in self._stubs:
+ self._stubs["process_document"] = self.grpc_channel.unary_unary(
+ "/google.cloud.documentai.v1beta3.DocumentProcessorService/ProcessDocument",
+ request_serializer=document_processor_service.ProcessRequest.serialize,
+ response_deserializer=document_processor_service.ProcessResponse.deserialize,
+ )
+ return self._stubs["process_document"]
+
+ @property
+ def batch_process_documents(
+ self,
+ ) -> Callable[
+ [document_processor_service.BatchProcessRequest],
+ Awaitable[operations.Operation],
+ ]:
+ r"""Return a callable for the batch process documents method over gRPC.
+
+ LRO endpoint to batch process many documents. The output is
+ written to Cloud Storage as JSON in the [Document] format.
+
+ Returns:
+ Callable[[~.BatchProcessRequest],
+ Awaitable[~.Operation]]:
+ 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 "batch_process_documents" not in self._stubs:
+ self._stubs["batch_process_documents"] = self.grpc_channel.unary_unary(
+ "/google.cloud.documentai.v1beta3.DocumentProcessorService/BatchProcessDocuments",
+ request_serializer=document_processor_service.BatchProcessRequest.serialize,
+ response_deserializer=operations.Operation.FromString,
+ )
+ return self._stubs["batch_process_documents"]
+
+ @property
+ def review_document(
+ self,
+ ) -> Callable[
+ [document_processor_service.ReviewDocumentRequest],
+ Awaitable[operations.Operation],
+ ]:
+ r"""Return a callable for the review document method over gRPC.
+
+ Send a document for Human Review. The input document
+ should be processed by the specified processor.
+
+ Returns:
+ Callable[[~.ReviewDocumentRequest],
+ Awaitable[~.Operation]]:
+ 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 "review_document" not in self._stubs:
+ self._stubs["review_document"] = self.grpc_channel.unary_unary(
+ "/google.cloud.documentai.v1beta3.DocumentProcessorService/ReviewDocument",
+ request_serializer=document_processor_service.ReviewDocumentRequest.serialize,
+ response_deserializer=operations.Operation.FromString,
+ )
+ return self._stubs["review_document"]
+
+
+__all__ = ("DocumentProcessorServiceGrpcAsyncIOTransport",)
diff --git a/google/cloud/documentai_v1beta3/types/__init__.py b/google/cloud/documentai_v1beta3/types/__init__.py
new file mode 100644
index 00000000..4b5768f7
--- /dev/null
+++ b/google/cloud/documentai_v1beta3/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 .geometry import (
+ Vertex,
+ NormalizedVertex,
+ BoundingPoly,
+)
+from .document import Document
+from .document_processor_service import (
+ ProcessRequest,
+ ProcessResponse,
+ BatchProcessRequest,
+ BatchProcessResponse,
+ BatchProcessMetadata,
+ ReviewDocumentRequest,
+ ReviewDocumentResponse,
+ ReviewDocumentOperationMetadata,
+)
+
+
+__all__ = (
+ "Vertex",
+ "NormalizedVertex",
+ "BoundingPoly",
+ "Document",
+ "ProcessRequest",
+ "ProcessResponse",
+ "BatchProcessRequest",
+ "BatchProcessResponse",
+ "BatchProcessMetadata",
+ "ReviewDocumentRequest",
+ "ReviewDocumentResponse",
+ "ReviewDocumentOperationMetadata",
+)
diff --git a/google/cloud/documentai_v1beta3/types/document.py b/google/cloud/documentai_v1beta3/types/document.py
new file mode 100644
index 00000000..104a366c
--- /dev/null
+++ b/google/cloud/documentai_v1beta3/types/document.py
@@ -0,0 +1,1148 @@
+# -*- 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.documentai_v1beta3.types import geometry
+from google.protobuf import timestamp_pb2 as timestamp # type: ignore
+from google.rpc import status_pb2 as status # type: ignore
+from google.type import color_pb2 as gt_color # type: ignore
+from google.type import date_pb2 as date # type: ignore
+from google.type import datetime_pb2 as datetime # type: ignore
+from google.type import money_pb2 as money # type: ignore
+from google.type import postal_address_pb2 as postal_address # type: ignore
+
+
+__protobuf__ = proto.module(
+ package="google.cloud.documentai.v1beta3", manifest={"Document",},
+)
+
+
+class Document(proto.Message):
+ r"""Document represents the canonical document resource in
+ Document Understanding AI.
+ It is an interchange format that provides insights into
+ documents and allows for collaboration between users and
+ Document Understanding AI to iterate and optimize for quality.
+
+ Attributes:
+ uri (str):
+ Currently supports Google Cloud Storage URI of the form
+ ``gs://bucket_name/object_name``. Object versioning is not
+ supported. See `Google Cloud Storage Request
+ URIs `__
+ for more info.
+ content (bytes):
+ Inline document content, represented as a stream of bytes.
+ Note: As with all ``bytes`` fields, protobuffers use a pure
+ binary representation, whereas JSON representations use
+ base64.
+ mime_type (str):
+ An IANA published MIME type (also referred to
+ as media type). For more information, see
+ https://www.iana.org/assignments/media-
+ types/media-types.xhtml.
+ text (str):
+ UTF-8 encoded text in reading order from the
+ document.
+ text_styles (Sequence[~.document.Document.Style]):
+ Styles for the
+ [Document.text][google.cloud.documentai.v1beta3.Document.text].
+ pages (Sequence[~.document.Document.Page]):
+ Visual page layout for the
+ [Document][google.cloud.documentai.v1beta3.Document].
+ entities (Sequence[~.document.Document.Entity]):
+ A list of entities detected on
+ [Document.text][google.cloud.documentai.v1beta3.Document.text].
+ For document shards, entities in this list may cross shard
+ boundaries.
+ entity_relations (Sequence[~.document.Document.EntityRelation]):
+ Relationship among
+ [Document.entities][google.cloud.documentai.v1beta3.Document.entities].
+ translations (Sequence[~.document.Document.Translation]):
+ A list of translations on
+ [Document.text][google.cloud.documentai.v1beta3.Document.text].
+ For document shards, translations in this list may cross
+ shard boundaries.
+ text_changes (Sequence[~.document.Document.TextChange]):
+ A list of text corrections made to [Document.text]. This is
+ usually used for annotating corrections to OCR mistakes.
+ Text changes for a given revision may not overlap with each
+ other.
+ shard_info (~.document.Document.ShardInfo):
+ Information about the sharding if this
+ document is sharded part of a larger document.
+ If the document is not sharded, this message is
+ not specified.
+ error (~.status.Status):
+ Any error that occurred while processing this
+ document.
+ revisions (Sequence[~.document.Document.Revision]):
+ Revision history of this document.
+ """
+
+ class ShardInfo(proto.Message):
+ r"""For a large document, sharding may be performed to produce
+ several document shards. Each document shard contains this field
+ to detail which shard it is.
+
+ Attributes:
+ shard_index (int):
+ The 0-based index of this shard.
+ shard_count (int):
+ Total number of shards.
+ text_offset (int):
+ The index of the first character in
+ [Document.text][google.cloud.documentai.v1beta3.Document.text]
+ in the overall document global text.
+ """
+
+ shard_index = proto.Field(proto.INT64, number=1)
+
+ shard_count = proto.Field(proto.INT64, number=2)
+
+ text_offset = proto.Field(proto.INT64, number=3)
+
+ class Style(proto.Message):
+ r"""Annotation for common text style attributes. This adheres to
+ CSS conventions as much as possible.
+
+ Attributes:
+ text_anchor (~.document.Document.TextAnchor):
+ Text anchor indexing into the
+ [Document.text][google.cloud.documentai.v1beta3.Document.text].
+ color (~.gt_color.Color):
+ Text color.
+ background_color (~.gt_color.Color):
+ Text background color.
+ font_weight (str):
+ Font weight. Possible values are normal, bold, bolder, and
+ lighter. https://www.w3schools.com/cssref/pr_font_weight.asp
+ text_style (str):
+ Text style. Possible values are normal, italic, and oblique.
+ https://www.w3schools.com/cssref/pr_font_font-style.asp
+ text_decoration (str):
+ Text decoration. Follows CSS standard.
+ https://www.w3schools.com/cssref/pr_text_text-decoration.asp
+ font_size (~.document.Document.Style.FontSize):
+ Font size.
+ """
+
+ class FontSize(proto.Message):
+ r"""Font size with unit.
+
+ Attributes:
+ size (float):
+ Font size for the text.
+ unit (str):
+ Unit for the font size. Follows CSS naming
+ (in, px, pt, etc.).
+ """
+
+ size = proto.Field(proto.FLOAT, number=1)
+
+ unit = proto.Field(proto.STRING, number=2)
+
+ text_anchor = proto.Field(
+ proto.MESSAGE, number=1, message="Document.TextAnchor",
+ )
+
+ color = proto.Field(proto.MESSAGE, number=2, message=gt_color.Color,)
+
+ background_color = proto.Field(proto.MESSAGE, number=3, message=gt_color.Color,)
+
+ font_weight = proto.Field(proto.STRING, number=4)
+
+ text_style = proto.Field(proto.STRING, number=5)
+
+ text_decoration = proto.Field(proto.STRING, number=6)
+
+ font_size = proto.Field(
+ proto.MESSAGE, number=7, message="Document.Style.FontSize",
+ )
+
+ class Page(proto.Message):
+ r"""A page in a [Document][google.cloud.documentai.v1beta3.Document].
+
+ Attributes:
+ page_number (int):
+ 1-based index for current
+ [Page][google.cloud.documentai.v1beta3.Document.Page] in a
+ parent [Document][google.cloud.documentai.v1beta3.Document].
+ Useful when a page is taken out of a
+ [Document][google.cloud.documentai.v1beta3.Document] for
+ individual processing.
+ image (~.document.Document.Page.Image):
+ Rendered image for this page. This image is
+ preprocessed to remove any skew, rotation, and
+ distortions such that the annotation bounding
+ boxes can be upright and axis-aligned.
+ transforms (Sequence[~.document.Document.Page.Matrix]):
+ Transformation matrices that were applied to the original
+ document image to produce
+ [Page.image][google.cloud.documentai.v1beta3.Document.Page.image].
+ dimension (~.document.Document.Page.Dimension):
+ Physical dimension of the page.
+ layout (~.document.Document.Page.Layout):
+ [Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
+ for the page.
+ detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ A list of detected languages together with
+ confidence.
+ blocks (Sequence[~.document.Document.Page.Block]):
+ A list of visually detected text blocks on
+ the page. A block has a set of lines (collected
+ into paragraphs) that have a common line-spacing
+ and orientation.
+ paragraphs (Sequence[~.document.Document.Page.Paragraph]):
+ A list of visually detected text paragraphs
+ on the page. A collection of lines that a human
+ would perceive as a paragraph.
+ lines (Sequence[~.document.Document.Page.Line]):
+ A list of visually detected text lines on the
+ page. A collection of tokens that a human would
+ perceive as a line.
+ tokens (Sequence[~.document.Document.Page.Token]):
+ A list of visually detected tokens on the
+ page.
+ visual_elements (Sequence[~.document.Document.Page.VisualElement]):
+ A list of detected non-text visual elements
+ e.g. checkbox, signature etc. on the page.
+ tables (Sequence[~.document.Document.Page.Table]):
+ A list of visually detected tables on the
+ page.
+ form_fields (Sequence[~.document.Document.Page.FormField]):
+ A list of visually detected form fields on
+ the page.
+ """
+
+ class Dimension(proto.Message):
+ r"""Dimension for the page.
+
+ Attributes:
+ width (float):
+ Page width.
+ height (float):
+ Page height.
+ unit (str):
+ Dimension unit.
+ """
+
+ width = proto.Field(proto.FLOAT, number=1)
+
+ height = proto.Field(proto.FLOAT, number=2)
+
+ unit = proto.Field(proto.STRING, number=3)
+
+ class Image(proto.Message):
+ r"""Rendered image contents for this page.
+
+ Attributes:
+ content (bytes):
+ Raw byte content of the image.
+ mime_type (str):
+ Encoding mime type for the image.
+ width (int):
+ Width of the image in pixels.
+ height (int):
+ Height of the image in pixels.
+ """
+
+ content = proto.Field(proto.BYTES, number=1)
+
+ mime_type = proto.Field(proto.STRING, number=2)
+
+ width = proto.Field(proto.INT32, number=3)
+
+ height = proto.Field(proto.INT32, number=4)
+
+ class Matrix(proto.Message):
+ r"""Representation for transformation matrix, intended to be
+ compatible and used with OpenCV format for image manipulation.
+
+ Attributes:
+ rows (int):
+ Number of rows in the matrix.
+ cols (int):
+ Number of columns in the matrix.
+ type_ (int):
+ This encodes information about what data type the matrix
+ uses. For example, 0 (CV_8U) is an unsigned 8-bit image. For
+ the full list of OpenCV primitive data types, please refer
+ to
+ https://docs.opencv.org/4.3.0/d1/d1b/group__core__hal__interface.html
+ data (bytes):
+ The matrix data.
+ """
+
+ rows = proto.Field(proto.INT32, number=1)
+
+ cols = proto.Field(proto.INT32, number=2)
+
+ type_ = proto.Field(proto.INT32, number=3)
+
+ data = proto.Field(proto.BYTES, number=4)
+
+ class Layout(proto.Message):
+ r"""Visual element describing a layout unit on a page.
+
+ Attributes:
+ text_anchor (~.document.Document.TextAnchor):
+ Text anchor indexing into the
+ [Document.text][google.cloud.documentai.v1beta3.Document.text].
+ confidence (float):
+ Confidence of the current
+ [Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
+ within context of the object this layout is for. e.g.
+ confidence can be for a single token, a table, a visual
+ element, etc. depending on context. Range [0, 1].
+ bounding_poly (~.geometry.BoundingPoly):
+ The bounding polygon for the
+ [Layout][google.cloud.documentai.v1beta3.Document.Page.Layout].
+ orientation (~.document.Document.Page.Layout.Orientation):
+ Detected orientation for the
+ [Layout][google.cloud.documentai.v1beta3.Document.Page.Layout].
+ """
+
+ class Orientation(proto.Enum):
+ r"""Detected human reading orientation."""
+ ORIENTATION_UNSPECIFIED = 0
+ PAGE_UP = 1
+ PAGE_RIGHT = 2
+ PAGE_DOWN = 3
+ PAGE_LEFT = 4
+
+ text_anchor = proto.Field(
+ proto.MESSAGE, number=1, message="Document.TextAnchor",
+ )
+
+ confidence = proto.Field(proto.FLOAT, number=2)
+
+ bounding_poly = proto.Field(
+ proto.MESSAGE, number=3, message=geometry.BoundingPoly,
+ )
+
+ orientation = proto.Field(
+ proto.ENUM, number=4, enum="Document.Page.Layout.Orientation",
+ )
+
+ class Block(proto.Message):
+ r"""A block has a set of lines (collected into paragraphs) that
+ have a common line-spacing and orientation.
+
+ Attributes:
+ layout (~.document.Document.Page.Layout):
+ [Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
+ for
+ [Block][google.cloud.documentai.v1beta3.Document.Page.Block].
+ detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ A list of detected languages together with
+ confidence.
+ provenance (~.document.Document.Provenance):
+ The history of this annotation.
+ """
+
+ layout = proto.Field(
+ proto.MESSAGE, number=1, message="Document.Page.Layout",
+ )
+
+ detected_languages = proto.RepeatedField(
+ proto.MESSAGE, number=2, message="Document.Page.DetectedLanguage",
+ )
+
+ provenance = proto.Field(
+ proto.MESSAGE, number=3, message="Document.Provenance",
+ )
+
+ class Paragraph(proto.Message):
+ r"""A collection of lines that a human would perceive as a
+ paragraph.
+
+ Attributes:
+ layout (~.document.Document.Page.Layout):
+ [Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
+ for
+ [Paragraph][google.cloud.documentai.v1beta3.Document.Page.Paragraph].
+ detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ A list of detected languages together with
+ confidence.
+ provenance (~.document.Document.Provenance):
+ The history of this annotation.
+ """
+
+ layout = proto.Field(
+ proto.MESSAGE, number=1, message="Document.Page.Layout",
+ )
+
+ detected_languages = proto.RepeatedField(
+ proto.MESSAGE, number=2, message="Document.Page.DetectedLanguage",
+ )
+
+ provenance = proto.Field(
+ proto.MESSAGE, number=3, message="Document.Provenance",
+ )
+
+ class Line(proto.Message):
+ r"""A collection of tokens that a human would perceive as a line.
+ Does not cross column boundaries, can be horizontal, vertical,
+ etc.
+
+ Attributes:
+ layout (~.document.Document.Page.Layout):
+ [Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
+ for
+ [Line][google.cloud.documentai.v1beta3.Document.Page.Line].
+ detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ A list of detected languages together with
+ confidence.
+ provenance (~.document.Document.Provenance):
+ The history of this annotation.
+ """
+
+ layout = proto.Field(
+ proto.MESSAGE, number=1, message="Document.Page.Layout",
+ )
+
+ detected_languages = proto.RepeatedField(
+ proto.MESSAGE, number=2, message="Document.Page.DetectedLanguage",
+ )
+
+ provenance = proto.Field(
+ proto.MESSAGE, number=3, message="Document.Provenance",
+ )
+
+ class Token(proto.Message):
+ r"""A detected token.
+
+ Attributes:
+ layout (~.document.Document.Page.Layout):
+ [Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
+ for
+ [Token][google.cloud.documentai.v1beta3.Document.Page.Token].
+ detected_break (~.document.Document.Page.Token.DetectedBreak):
+ Detected break at the end of a
+ [Token][google.cloud.documentai.v1beta3.Document.Page.Token].
+ detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ A list of detected languages together with
+ confidence.
+ provenance (~.document.Document.Provenance):
+ The history of this annotation.
+ """
+
+ class DetectedBreak(proto.Message):
+ r"""Detected break at the end of a
+ [Token][google.cloud.documentai.v1beta3.Document.Page.Token].
+
+ Attributes:
+ type_ (~.document.Document.Page.Token.DetectedBreak.Type):
+ Detected break type.
+ """
+
+ class Type(proto.Enum):
+ r"""Enum to denote the type of break found."""
+ TYPE_UNSPECIFIED = 0
+ SPACE = 1
+ WIDE_SPACE = 2
+ HYPHEN = 3
+
+ type_ = proto.Field(
+ proto.ENUM, number=1, enum="Document.Page.Token.DetectedBreak.Type",
+ )
+
+ layout = proto.Field(
+ proto.MESSAGE, number=1, message="Document.Page.Layout",
+ )
+
+ detected_break = proto.Field(
+ proto.MESSAGE, number=2, message="Document.Page.Token.DetectedBreak",
+ )
+
+ detected_languages = proto.RepeatedField(
+ proto.MESSAGE, number=3, message="Document.Page.DetectedLanguage",
+ )
+
+ provenance = proto.Field(
+ proto.MESSAGE, number=4, message="Document.Provenance",
+ )
+
+ class VisualElement(proto.Message):
+ r"""Detected non-text visual elements e.g. checkbox, signature
+ etc. on the page.
+
+ Attributes:
+ layout (~.document.Document.Page.Layout):
+ [Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
+ for
+ [VisualElement][google.cloud.documentai.v1beta3.Document.Page.VisualElement].
+ type_ (str):
+ Type of the
+ [VisualElement][google.cloud.documentai.v1beta3.Document.Page.VisualElement].
+ detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ A list of detected languages together with
+ confidence.
+ """
+
+ layout = proto.Field(
+ proto.MESSAGE, number=1, message="Document.Page.Layout",
+ )
+
+ type_ = proto.Field(proto.STRING, number=2)
+
+ detected_languages = proto.RepeatedField(
+ proto.MESSAGE, number=3, message="Document.Page.DetectedLanguage",
+ )
+
+ class Table(proto.Message):
+ r"""A table representation similar to HTML table structure.
+
+ Attributes:
+ layout (~.document.Document.Page.Layout):
+ [Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
+ for
+ [Table][google.cloud.documentai.v1beta3.Document.Page.Table].
+ header_rows (Sequence[~.document.Document.Page.Table.TableRow]):
+ Header rows of the table.
+ body_rows (Sequence[~.document.Document.Page.Table.TableRow]):
+ Body rows of the table.
+ detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ A list of detected languages together with
+ confidence.
+ """
+
+ class TableRow(proto.Message):
+ r"""A row of table cells.
+
+ Attributes:
+ cells (Sequence[~.document.Document.Page.Table.TableCell]):
+ Cells that make up this row.
+ """
+
+ cells = proto.RepeatedField(
+ proto.MESSAGE, number=1, message="Document.Page.Table.TableCell",
+ )
+
+ class TableCell(proto.Message):
+ r"""A cell representation inside the table.
+
+ Attributes:
+ layout (~.document.Document.Page.Layout):
+ [Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
+ for
+ [TableCell][google.cloud.documentai.v1beta3.Document.Page.Table.TableCell].
+ row_span (int):
+ How many rows this cell spans.
+ col_span (int):
+ How many columns this cell spans.
+ detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ A list of detected languages together with
+ confidence.
+ """
+
+ layout = proto.Field(
+ proto.MESSAGE, number=1, message="Document.Page.Layout",
+ )
+
+ row_span = proto.Field(proto.INT32, number=2)
+
+ col_span = proto.Field(proto.INT32, number=3)
+
+ detected_languages = proto.RepeatedField(
+ proto.MESSAGE, number=4, message="Document.Page.DetectedLanguage",
+ )
+
+ layout = proto.Field(
+ proto.MESSAGE, number=1, message="Document.Page.Layout",
+ )
+
+ header_rows = proto.RepeatedField(
+ proto.MESSAGE, number=2, message="Document.Page.Table.TableRow",
+ )
+
+ body_rows = proto.RepeatedField(
+ proto.MESSAGE, number=3, message="Document.Page.Table.TableRow",
+ )
+
+ detected_languages = proto.RepeatedField(
+ proto.MESSAGE, number=4, message="Document.Page.DetectedLanguage",
+ )
+
+ class FormField(proto.Message):
+ r"""A form field detected on the page.
+
+ Attributes:
+ field_name (~.document.Document.Page.Layout):
+ [Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
+ for the
+ [FormField][google.cloud.documentai.v1beta3.Document.Page.FormField]
+ name. e.g. ``Address``, ``Email``, ``Grand total``,
+ ``Phone number``, etc.
+ field_value (~.document.Document.Page.Layout):
+ [Layout][google.cloud.documentai.v1beta3.Document.Page.Layout]
+ for the
+ [FormField][google.cloud.documentai.v1beta3.Document.Page.FormField]
+ value.
+ name_detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ A list of detected languages for name
+ together with confidence.
+ value_detected_languages (Sequence[~.document.Document.Page.DetectedLanguage]):
+ A list of detected languages for value
+ together with confidence.
+ value_type (str):
+ If the value is non-textual, this field represents the type.
+ Current valid values are:
+
+ - blank (this indicates the field_value is normal text)
+ - "unfilled_checkbox"
+ - "filled_checkbox".
+ """
+
+ field_name = proto.Field(
+ proto.MESSAGE, number=1, message="Document.Page.Layout",
+ )
+
+ field_value = proto.Field(
+ proto.MESSAGE, number=2, message="Document.Page.Layout",
+ )
+
+ name_detected_languages = proto.RepeatedField(
+ proto.MESSAGE, number=3, message="Document.Page.DetectedLanguage",
+ )
+
+ value_detected_languages = proto.RepeatedField(
+ proto.MESSAGE, number=4, message="Document.Page.DetectedLanguage",
+ )
+
+ value_type = proto.Field(proto.STRING, number=5)
+
+ class DetectedLanguage(proto.Message):
+ r"""Detected language for a structural component.
+
+ Attributes:
+ language_code (str):
+ The BCP-47 language code, such as "en-US" or "sr-Latn". For
+ more information, see
+ http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
+ confidence (float):
+ Confidence of detected language. Range [0, 1].
+ """
+
+ language_code = proto.Field(proto.STRING, number=1)
+
+ confidence = proto.Field(proto.FLOAT, number=2)
+
+ page_number = proto.Field(proto.INT32, number=1)
+
+ image = proto.Field(proto.MESSAGE, number=13, message="Document.Page.Image",)
+
+ transforms = proto.RepeatedField(
+ proto.MESSAGE, number=14, message="Document.Page.Matrix",
+ )
+
+ dimension = proto.Field(
+ proto.MESSAGE, number=2, message="Document.Page.Dimension",
+ )
+
+ layout = proto.Field(proto.MESSAGE, number=3, message="Document.Page.Layout",)
+
+ detected_languages = proto.RepeatedField(
+ proto.MESSAGE, number=4, message="Document.Page.DetectedLanguage",
+ )
+
+ blocks = proto.RepeatedField(
+ proto.MESSAGE, number=5, message="Document.Page.Block",
+ )
+
+ paragraphs = proto.RepeatedField(
+ proto.MESSAGE, number=6, message="Document.Page.Paragraph",
+ )
+
+ lines = proto.RepeatedField(
+ proto.MESSAGE, number=7, message="Document.Page.Line",
+ )
+
+ tokens = proto.RepeatedField(
+ proto.MESSAGE, number=8, message="Document.Page.Token",
+ )
+
+ visual_elements = proto.RepeatedField(
+ proto.MESSAGE, number=9, message="Document.Page.VisualElement",
+ )
+
+ tables = proto.RepeatedField(
+ proto.MESSAGE, number=10, message="Document.Page.Table",
+ )
+
+ form_fields = proto.RepeatedField(
+ proto.MESSAGE, number=11, message="Document.Page.FormField",
+ )
+
+ class Entity(proto.Message):
+ r"""A phrase in the text that is a known entity type, such as a
+ person, an organization, or location.
+
+ Attributes:
+ text_anchor (~.document.Document.TextAnchor):
+ Provenance of the entity. Text anchor indexing into the
+ [Document.text][google.cloud.documentai.v1beta3.Document.text].
+ type_ (str):
+ Entity type from a schema e.g. ``Address``.
+ mention_text (str):
+ Text value in the document e.g. ``1600 Amphitheatre Pkwy``.
+ mention_id (str):
+ Deprecated. Use ``id`` field instead.
+ confidence (float):
+ Optional. Confidence of detected Schema entity. Range [0,
+ 1].
+ page_anchor (~.document.Document.PageAnchor):
+ Optional. Represents the provenance of this
+ entity wrt. the location on the page where it
+ was found.
+ id (str):
+ Canonical id. This will be a unique value in
+ the entity list for this document.
+ normalized_value (~.document.Document.Entity.NormalizedValue):
+ Optional. Normalized entity value. Absent if
+ the extracted value could not be converted or
+ the type (e.g. address) is not supported for
+ certain parsers. This field is also only
+ populated for certain supported document types.
+ properties (Sequence[~.document.Document.Entity]):
+ Optional. Entities can be nested to form a
+ hierarchical data structure representing the
+ content in the document.
+ provenance (~.document.Document.Provenance):
+ Optional. The history of this annotation.
+ redacted (bool):
+ Optional. Whether the entity will be redacted
+ for de-identification purposes.
+ """
+
+ class NormalizedValue(proto.Message):
+ r"""Parsed and normalized entity value.
+
+ Attributes:
+ money_value (~.money.Money):
+ Money value. See also:
+ https:
+ github.com/googleapis/googleapis/blob/master/google/type/money.proto
+ date_value (~.date.Date):
+ Date value. Includes year, month, day. See
+ also:
+ https:
+ github.com/googleapis/googleapis/blob/master/google/type/date.proto
+ datetime_value (~.datetime.DateTime):
+ DateTime value. Includes date, time, and
+ timezone. See also:
+ https:
+ github.com/googleapis/googleapis/blob/master/google/type/datetime.proto
+ address_value (~.postal_address.PostalAddress):
+ Postal address. See also:
+
+ https:
+ github.com/googleapis/googleapis/blob/master/google/type/postal_address.proto
+ text (str):
+ Required. Normalized entity value stored as a string. This
+ field is populated for supported document type (e.g.
+ Invoice). For some entity types, one of respective
+ 'structured_value' fields may also be populated.
+
+ - Money/Currency type (``money_value``) is in the ISO 4217
+ text format.
+ - Date type (``date_value``) is in the ISO 8601 text
+ format.
+ - Datetime type (``datetime_value``) is in the ISO 8601
+ text format.
+ """
+
+ money_value = proto.Field(
+ proto.MESSAGE, number=2, oneof="structured_value", message=money.Money,
+ )
+
+ date_value = proto.Field(
+ proto.MESSAGE, number=3, oneof="structured_value", message=date.Date,
+ )
+
+ datetime_value = proto.Field(
+ proto.MESSAGE,
+ number=4,
+ oneof="structured_value",
+ message=datetime.DateTime,
+ )
+
+ address_value = proto.Field(
+ proto.MESSAGE,
+ number=5,
+ oneof="structured_value",
+ message=postal_address.PostalAddress,
+ )
+
+ text = proto.Field(proto.STRING, number=1)
+
+ text_anchor = proto.Field(
+ proto.MESSAGE, number=1, message="Document.TextAnchor",
+ )
+
+ type_ = proto.Field(proto.STRING, number=2)
+
+ mention_text = proto.Field(proto.STRING, number=3)
+
+ mention_id = proto.Field(proto.STRING, number=4)
+
+ confidence = proto.Field(proto.FLOAT, number=5)
+
+ page_anchor = proto.Field(
+ proto.MESSAGE, number=6, message="Document.PageAnchor",
+ )
+
+ id = proto.Field(proto.STRING, number=7)
+
+ normalized_value = proto.Field(
+ proto.MESSAGE, number=9, message="Document.Entity.NormalizedValue",
+ )
+
+ properties = proto.RepeatedField(
+ proto.MESSAGE, number=10, message="Document.Entity",
+ )
+
+ provenance = proto.Field(
+ proto.MESSAGE, number=11, message="Document.Provenance",
+ )
+
+ redacted = proto.Field(proto.BOOL, number=12)
+
+ class EntityRelation(proto.Message):
+ r"""Relationship between
+ [Entities][google.cloud.documentai.v1beta3.Document.Entity].
+
+ Attributes:
+ subject_id (str):
+ Subject entity id.
+ object_id (str):
+ Object entity id.
+ relation (str):
+ Relationship description.
+ """
+
+ subject_id = proto.Field(proto.STRING, number=1)
+
+ object_id = proto.Field(proto.STRING, number=2)
+
+ relation = proto.Field(proto.STRING, number=3)
+
+ class Translation(proto.Message):
+ r"""A translation of the text segment.
+
+ Attributes:
+ text_anchor (~.document.Document.TextAnchor):
+ Provenance of the translation. Text anchor indexing into the
+ [Document.text][google.cloud.documentai.v1beta3.Document.text].
+ There can only be a single ``TextAnchor.text_segments``
+ element. If the start and end index of the text segment are
+ the same, the text change is inserted before that index.
+ language_code (str):
+ The BCP-47 language code, such as "en-US" or "sr-Latn". For
+ more information, see
+ http://www.unicode.org/reports/tr35/#Unicode_locale_identifier.
+ translated_text (str):
+ Text translated into the target language.
+ provenance (Sequence[~.document.Document.Provenance]):
+ The history of this annotation.
+ """
+
+ text_anchor = proto.Field(
+ proto.MESSAGE, number=1, message="Document.TextAnchor",
+ )
+
+ language_code = proto.Field(proto.STRING, number=2)
+
+ translated_text = proto.Field(proto.STRING, number=3)
+
+ provenance = proto.RepeatedField(
+ proto.MESSAGE, number=4, message="Document.Provenance",
+ )
+
+ class TextAnchor(proto.Message):
+ r"""Text reference indexing into the
+ [Document.text][google.cloud.documentai.v1beta3.Document.text].
+
+ Attributes:
+ text_segments (Sequence[~.document.Document.TextAnchor.TextSegment]):
+ The text segments from the
+ [Document.text][google.cloud.documentai.v1beta3.Document.text].
+ content (str):
+ Contains the content of the text span so that users do not
+ have to look it up in the text_segments.
+ """
+
+ class TextSegment(proto.Message):
+ r"""A text segment in the
+ [Document.text][google.cloud.documentai.v1beta3.Document.text]. The
+ indices may be out of bounds which indicate that the text extends
+ into another document shard for large sharded documents. See
+ [ShardInfo.text_offset][google.cloud.documentai.v1beta3.Document.ShardInfo.text_offset]
+
+ Attributes:
+ start_index (int):
+ [TextSegment][google.cloud.documentai.v1beta3.Document.TextAnchor.TextSegment]
+ start UTF-8 char index in the
+ [Document.text][google.cloud.documentai.v1beta3.Document.text].
+ end_index (int):
+ [TextSegment][google.cloud.documentai.v1beta3.Document.TextAnchor.TextSegment]
+ half open end UTF-8 char index in the
+ [Document.text][google.cloud.documentai.v1beta3.Document.text].
+ """
+
+ start_index = proto.Field(proto.INT64, number=1)
+
+ end_index = proto.Field(proto.INT64, number=2)
+
+ text_segments = proto.RepeatedField(
+ proto.MESSAGE, number=1, message="Document.TextAnchor.TextSegment",
+ )
+
+ content = proto.Field(proto.STRING, number=2)
+
+ class PageAnchor(proto.Message):
+ r"""Referencing the visual context of the entity in the
+ [Document.pages][google.cloud.documentai.v1beta3.Document.pages].
+ Page anchors can be cross-page, consist of multiple bounding
+ polygons and optionally reference specific layout element types.
+
+ Attributes:
+ page_refs (Sequence[~.document.Document.PageAnchor.PageRef]):
+ One or more references to visual page
+ elements
+ """
+
+ class PageRef(proto.Message):
+ r"""Represents a weak reference to a page element within a
+ document.
+
+ Attributes:
+ page (int):
+ Required. Index into the
+ [Document.pages][google.cloud.documentai.v1beta3.Document.pages]
+ element
+ layout_type (~.document.Document.PageAnchor.PageRef.LayoutType):
+ Optional. The type of the layout element that
+ is being referenced if any.
+ layout_id (str):
+ Optional. Deprecated. Use
+ [PageRef.bounding_poly][google.cloud.documentai.v1beta3.Document.PageAnchor.PageRef.bounding_poly]
+ instead.
+ bounding_poly (~.geometry.BoundingPoly):
+ Optional. Identifies the bounding polygon of
+ a layout element on the page.
+ """
+
+ class LayoutType(proto.Enum):
+ r"""The type of layout that is being referenced."""
+ LAYOUT_TYPE_UNSPECIFIED = 0
+ BLOCK = 1
+ PARAGRAPH = 2
+ LINE = 3
+ TOKEN = 4
+ VISUAL_ELEMENT = 5
+ TABLE = 6
+ FORM_FIELD = 7
+
+ page = proto.Field(proto.INT64, number=1)
+
+ layout_type = proto.Field(
+ proto.ENUM, number=2, enum="Document.PageAnchor.PageRef.LayoutType",
+ )
+
+ layout_id = proto.Field(proto.STRING, number=3)
+
+ bounding_poly = proto.Field(
+ proto.MESSAGE, number=4, message=geometry.BoundingPoly,
+ )
+
+ page_refs = proto.RepeatedField(
+ proto.MESSAGE, number=1, message="Document.PageAnchor.PageRef",
+ )
+
+ class Provenance(proto.Message):
+ r"""Structure to identify provenance relationships between
+ annotations in different revisions.
+
+ Attributes:
+ revision (int):
+ The index of the revision that produced this
+ element.
+ id (int):
+ The Id of this operation. Needs to be unique
+ within the scope of the revision.
+ parents (Sequence[~.document.Document.Provenance.Parent]):
+ References to the original elements that are
+ replaced.
+ type_ (~.document.Document.Provenance.OperationType):
+ The type of provenance operation.
+ """
+
+ class OperationType(proto.Enum):
+ r"""If a processor or agent does an explicit operation on
+ existing elements.
+ """
+ OPERATION_TYPE_UNSPECIFIED = 0
+ ADD = 1
+ REMOVE = 2
+ REPLACE = 3
+ EVAL_REQUESTED = 4
+ EVAL_APPROVED = 5
+
+ class Parent(proto.Message):
+ r"""Structure for referencing parent provenances. When an
+ element replaces one of more other elements parent references
+ identify the elements that are replaced.
+
+ Attributes:
+ revision (int):
+ The index of the [Document.revisions] identifying the parent
+ revision.
+ id (int):
+ The id of the parent provenance.
+ """
+
+ revision = proto.Field(proto.INT32, number=1)
+
+ id = proto.Field(proto.INT32, number=2)
+
+ revision = proto.Field(proto.INT32, number=1)
+
+ id = proto.Field(proto.INT32, number=2)
+
+ parents = proto.RepeatedField(
+ proto.MESSAGE, number=3, message="Document.Provenance.Parent",
+ )
+
+ type_ = proto.Field(
+ proto.ENUM, number=4, enum="Document.Provenance.OperationType",
+ )
+
+ class Revision(proto.Message):
+ r"""Contains past or forward revisions of this document.
+
+ Attributes:
+ agent (str):
+ If the change was made by a person specify
+ the name or id of that person.
+ processor (str):
+ If the annotation was made by processor
+ identify the processor by its resource name.
+ id (str):
+ Id of the revision. Unique within the
+ context of the document.
+ parent (Sequence[int]):
+ The revisions that this revision is based on. This can
+ include one or more parent (when documents are merged.) This
+ field represents the index into the ``revisions`` field.
+ create_time (~.timestamp.Timestamp):
+ The time that the revision was created.
+ human_review (~.document.Document.Revision.HumanReview):
+ Human Review information of this revision.
+ """
+
+ class HumanReview(proto.Message):
+ r"""Human Review information of the document.
+
+ Attributes:
+ state (str):
+ Human review state. e.g. ``requested``, ``succeeded``,
+ ``rejected``.
+ state_message (str):
+ A message providing more details about the current state of
+ processing. For example, the rejection reason when the state
+ is ``rejected``.
+ """
+
+ state = proto.Field(proto.STRING, number=1)
+
+ state_message = proto.Field(proto.STRING, number=2)
+
+ agent = proto.Field(proto.STRING, number=4, oneof="source")
+
+ processor = proto.Field(proto.STRING, number=5, oneof="source")
+
+ id = proto.Field(proto.STRING, number=1)
+
+ parent = proto.RepeatedField(proto.INT32, number=2)
+
+ create_time = proto.Field(proto.MESSAGE, number=3, message=timestamp.Timestamp,)
+
+ human_review = proto.Field(
+ proto.MESSAGE, number=6, message="Document.Revision.HumanReview",
+ )
+
+ class TextChange(proto.Message):
+ r"""This message is used for text changes aka. OCR corrections.
+
+ Attributes:
+ text_anchor (~.document.Document.TextAnchor):
+ Provenance of the correction. Text anchor indexing into the
+ [Document.text][google.cloud.documentai.v1beta3.Document.text].
+ There can only be a single ``TextAnchor.text_segments``
+ element. If the start and end index of the text segment are
+ the same, the text change is inserted before that index.
+ changed_text (str):
+ The text that replaces the text identified in the
+ ``text_anchor``.
+ provenance (Sequence[~.document.Document.Provenance]):
+ The history of this annotation.
+ """
+
+ text_anchor = proto.Field(
+ proto.MESSAGE, number=1, message="Document.TextAnchor",
+ )
+
+ changed_text = proto.Field(proto.STRING, number=2)
+
+ provenance = proto.RepeatedField(
+ proto.MESSAGE, number=3, message="Document.Provenance",
+ )
+
+ uri = proto.Field(proto.STRING, number=1, oneof="source")
+
+ content = proto.Field(proto.BYTES, number=2, oneof="source")
+
+ mime_type = proto.Field(proto.STRING, number=3)
+
+ text = proto.Field(proto.STRING, number=4)
+
+ text_styles = proto.RepeatedField(proto.MESSAGE, number=5, message=Style,)
+
+ pages = proto.RepeatedField(proto.MESSAGE, number=6, message=Page,)
+
+ entities = proto.RepeatedField(proto.MESSAGE, number=7, message=Entity,)
+
+ entity_relations = proto.RepeatedField(
+ proto.MESSAGE, number=8, message=EntityRelation,
+ )
+
+ translations = proto.RepeatedField(proto.MESSAGE, number=12, message=Translation,)
+
+ text_changes = proto.RepeatedField(proto.MESSAGE, number=14, message=TextChange,)
+
+ shard_info = proto.Field(proto.MESSAGE, number=9, message=ShardInfo,)
+
+ error = proto.Field(proto.MESSAGE, number=10, message=status.Status,)
+
+ revisions = proto.RepeatedField(proto.MESSAGE, number=13, message=Revision,)
+
+
+__all__ = tuple(sorted(__protobuf__.manifest))
diff --git a/google/cloud/documentai_v1beta3/types/document_processor_service.py b/google/cloud/documentai_v1beta3/types/document_processor_service.py
new file mode 100644
index 00000000..7d235c25
--- /dev/null
+++ b/google/cloud/documentai_v1beta3/types/document_processor_service.py
@@ -0,0 +1,278 @@
+# -*- 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.documentai_v1beta3.types import document as gcd_document
+from google.protobuf import timestamp_pb2 as timestamp # type: ignore
+from google.rpc import status_pb2 as gr_status # type: ignore
+
+
+__protobuf__ = proto.module(
+ package="google.cloud.documentai.v1beta3",
+ manifest={
+ "ProcessRequest",
+ "ProcessResponse",
+ "BatchProcessRequest",
+ "BatchProcessResponse",
+ "BatchProcessMetadata",
+ "ReviewDocumentRequest",
+ "ReviewDocumentResponse",
+ "ReviewDocumentOperationMetadata",
+ },
+)
+
+
+class ProcessRequest(proto.Message):
+ r"""Request message for the process document method.
+
+ Attributes:
+ name (str):
+ Required. The processor resource name.
+ document (~.gcd_document.Document):
+ The document payload, the [content] and [mime_type] fields
+ must be set.
+ skip_human_review (bool):
+ Whether Human Review feature should be
+ skipped for this request. Default to false.
+ """
+
+ name = proto.Field(proto.STRING, number=1)
+
+ document = proto.Field(proto.MESSAGE, number=2, message=gcd_document.Document,)
+
+ skip_human_review = proto.Field(proto.BOOL, number=3)
+
+
+class ProcessResponse(proto.Message):
+ r"""Response message for the process document method.
+
+ Attributes:
+ document (~.gcd_document.Document):
+ The document payload, will populate fields
+ based on the processor's behavior.
+ human_review_operation (str):
+ The name of the operation triggered by the
+ processed document. If the human review process
+ is not triggered, this field will be empty. It
+ has the same response type and metadata as the
+ long running operation returned by
+ ReviewDocument method.
+ """
+
+ document = proto.Field(proto.MESSAGE, number=1, message=gcd_document.Document,)
+
+ human_review_operation = proto.Field(proto.STRING, number=2)
+
+
+class BatchProcessRequest(proto.Message):
+ r"""Request message for batch process document method.
+
+ Attributes:
+ name (str):
+ Required. The processor resource name.
+ input_configs (Sequence[~.document_processor_service.BatchProcessRequest.BatchInputConfig]):
+ The input config for each single document in
+ the batch process.
+ output_config (~.document_processor_service.BatchProcessRequest.BatchOutputConfig):
+ The overall output config for batch process.
+ """
+
+ class BatchInputConfig(proto.Message):
+ r"""The message for input config in batch process.
+
+ Attributes:
+ gcs_source (str):
+ The Cloud Storage location as the source of
+ the document.
+ mime_type (str):
+ Mimetype of the input. If the input is a raw document, the
+ supported mimetypes are application/pdf, image/tiff, and
+ image/gif. If the input is a [Document] proto, the type
+ should be application/json.
+ """
+
+ gcs_source = proto.Field(proto.STRING, number=1)
+
+ mime_type = proto.Field(proto.STRING, number=2)
+
+ class BatchOutputConfig(proto.Message):
+ r"""The message for output config in batch process.
+
+ Attributes:
+ gcs_destination (str):
+ The output Cloud Storage directory to put the
+ processed documents.
+ """
+
+ gcs_destination = proto.Field(proto.STRING, number=1)
+
+ name = proto.Field(proto.STRING, number=1)
+
+ input_configs = proto.RepeatedField(
+ proto.MESSAGE, number=2, message=BatchInputConfig,
+ )
+
+ output_config = proto.Field(proto.MESSAGE, number=3, message=BatchOutputConfig,)
+
+
+class BatchProcessResponse(proto.Message):
+ r"""Response message for batch process document method."""
+
+
+class BatchProcessMetadata(proto.Message):
+ r"""The long running operation metadata for batch process method.
+
+ Attributes:
+ state (~.document_processor_service.BatchProcessMetadata.State):
+ The state of the current batch processing.
+ state_message (str):
+ A message providing more details about the
+ current state of processing. For example, the
+ error message if the operation is failed.
+ create_time (~.timestamp.Timestamp):
+ The creation time of the operation.
+ update_time (~.timestamp.Timestamp):
+ The last update time of the operation.
+ individual_process_statuses (Sequence[~.document_processor_service.BatchProcessMetadata.IndividualProcessStatus]):
+ The list of response details of each
+ document.
+ """
+
+ class State(proto.Enum):
+ r"""Possible states of the batch processing operation."""
+ STATE_UNSPECIFIED = 0
+ WAITING = 1
+ RUNNING = 2
+ SUCCEEDED = 3
+ CANCELLING = 4
+ CANCELLED = 5
+ FAILED = 6
+
+ class IndividualProcessStatus(proto.Message):
+ r"""The status of a each individual document in the batch
+ process.
+
+ Attributes:
+ input_gcs_source (str):
+ The source of the document, same as the [input_gcs_source]
+ field in the request when the batch process started. The
+ batch process is started by take snapshot of that document,
+ since a user can move or change that document during the
+ process.
+ status (~.gr_status.Status):
+ The status of the processing of the document.
+ output_gcs_destination (str):
+ The output_gcs_destination (in the request as
+ 'output_gcs_destination') of the processed document if it
+ was successful, otherwise empty.
+ human_review_operation (str):
+ The name of the operation triggered by the
+ processed document. If the human review process
+ is not triggered, this field will be empty. It
+ has the same response type and metadata as the
+ long running operation returned by
+ ReviewDocument method.
+ """
+
+ input_gcs_source = proto.Field(proto.STRING, number=1)
+
+ status = proto.Field(proto.MESSAGE, number=2, message=gr_status.Status,)
+
+ output_gcs_destination = proto.Field(proto.STRING, number=3)
+
+ human_review_operation = proto.Field(proto.STRING, number=4)
+
+ state = proto.Field(proto.ENUM, number=1, enum=State,)
+
+ state_message = proto.Field(proto.STRING, number=2)
+
+ create_time = proto.Field(proto.MESSAGE, number=3, message=timestamp.Timestamp,)
+
+ update_time = proto.Field(proto.MESSAGE, number=4, message=timestamp.Timestamp,)
+
+ individual_process_statuses = proto.RepeatedField(
+ proto.MESSAGE, number=5, message=IndividualProcessStatus,
+ )
+
+
+class ReviewDocumentRequest(proto.Message):
+ r"""Request message for review document method.
+
+ Attributes:
+ human_review_config (str):
+ Required. The resource name of the
+ HumanReviewConfig that the document will be
+ reviewed with.
+ document (~.gcd_document.Document):
+ The document that needs human review.
+ """
+
+ human_review_config = proto.Field(proto.STRING, number=1)
+
+ document = proto.Field(proto.MESSAGE, number=2, message=gcd_document.Document,)
+
+
+class ReviewDocumentResponse(proto.Message):
+ r"""Response message for review document method.
+
+ Attributes:
+ gcs_destination (str):
+ The Cloud Storage uri for the human reviewed
+ document.
+ """
+
+ gcs_destination = proto.Field(proto.STRING, number=1)
+
+
+class ReviewDocumentOperationMetadata(proto.Message):
+ r"""The long running operation metadata for review document
+ method.
+
+ Attributes:
+ state (~.document_processor_service.ReviewDocumentOperationMetadata.State):
+ Used only when Operation.done is false.
+ state_message (str):
+ A message providing more details about the
+ current state of processing. For example, the
+ error message if the operation is failed.
+ create_time (~.timestamp.Timestamp):
+ The creation time of the operation.
+ update_time (~.timestamp.Timestamp):
+ The last update time of the operation.
+ """
+
+ class State(proto.Enum):
+ r"""State of the longrunning operation."""
+ STATE_UNSPECIFIED = 0
+ RUNNING = 1
+ CANCELLING = 2
+ SUCCEEDED = 3
+ FAILED = 4
+ CANCELLED = 5
+
+ state = proto.Field(proto.ENUM, number=1, enum=State,)
+
+ state_message = proto.Field(proto.STRING, number=2)
+
+ create_time = proto.Field(proto.MESSAGE, number=3, message=timestamp.Timestamp,)
+
+ update_time = proto.Field(proto.MESSAGE, number=4, message=timestamp.Timestamp,)
+
+
+__all__ = tuple(sorted(__protobuf__.manifest))
diff --git a/google/cloud/documentai_v1beta3/types/geometry.py b/google/cloud/documentai_v1beta3/types/geometry.py
new file mode 100644
index 00000000..e87b87c7
--- /dev/null
+++ b/google/cloud/documentai_v1beta3/types/geometry.py
@@ -0,0 +1,78 @@
+# -*- 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
+
+
+__protobuf__ = proto.module(
+ package="google.cloud.documentai.v1beta3",
+ manifest={"Vertex", "NormalizedVertex", "BoundingPoly",},
+)
+
+
+class Vertex(proto.Message):
+ r"""A vertex represents a 2D point in the image.
+ NOTE: the vertex coordinates are in the same scale as the
+ original image.
+
+ Attributes:
+ x (int):
+ X coordinate.
+ y (int):
+ Y coordinate.
+ """
+
+ x = proto.Field(proto.INT32, number=1)
+
+ y = proto.Field(proto.INT32, number=2)
+
+
+class NormalizedVertex(proto.Message):
+ r"""A vertex represents a 2D point in the image.
+ NOTE: the normalized vertex coordinates are relative to the
+ original image and range from 0 to 1.
+
+ Attributes:
+ x (float):
+ X coordinate.
+ y (float):
+ Y coordinate.
+ """
+
+ x = proto.Field(proto.FLOAT, number=1)
+
+ y = proto.Field(proto.FLOAT, number=2)
+
+
+class BoundingPoly(proto.Message):
+ r"""A bounding polygon for the detected image annotation.
+
+ Attributes:
+ vertices (Sequence[~.geometry.Vertex]):
+ The bounding polygon vertices.
+ normalized_vertices (Sequence[~.geometry.NormalizedVertex]):
+ The bounding polygon normalized vertices.
+ """
+
+ vertices = proto.RepeatedField(proto.MESSAGE, number=1, message=Vertex,)
+
+ normalized_vertices = proto.RepeatedField(
+ proto.MESSAGE, number=2, message=NormalizedVertex,
+ )
+
+
+__all__ = tuple(sorted(__protobuf__.manifest))
diff --git a/noxfile.py b/noxfile.py
index 2e726c77..e446dd8d 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -23,11 +23,11 @@
import nox
-BLACK_VERSION = "black==19.3b0"
+BLACK_VERSION = "black==19.10b0"
BLACK_PATHS = ["docs", "google", "tests", "noxfile.py", "setup.py"]
-DEFAULT_PYTHON_VERSION = "3.7"
-SYSTEM_TEST_PYTHON_VERSIONS = ["3.7"]
+DEFAULT_PYTHON_VERSION = "3.8"
+SYSTEM_TEST_PYTHON_VERSIONS = ["3.8"]
UNIT_TEST_PYTHON_VERSIONS = ["3.6", "3.7", "3.8"]
@@ -39,7 +39,9 @@ def lint(session):
serious code quality issues.
"""
session.install("flake8", BLACK_VERSION)
- session.run("black", "--check", *BLACK_PATHS)
+ session.run(
+ "black", "--check", *BLACK_PATHS,
+ )
session.run("flake8", "google", "tests")
@@ -54,7 +56,9 @@ def blacken(session):
check the state of the `gcp_ubuntu_config` we use for that Kokoro run.
"""
session.install(BLACK_VERSION)
- session.run("black", *BLACK_PATHS)
+ session.run(
+ "black", *BLACK_PATHS,
+ )
@nox.session(python=DEFAULT_PYTHON_VERSION)
@@ -67,6 +71,7 @@ def lint_setup_py(session):
def default(session):
# Install all test dependencies, then install this package in-place.
session.install("asyncmock", "pytest-asyncio")
+
session.install("mock", "pytest", "pytest-cov")
session.install("-e", ".")
@@ -97,6 +102,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")
@@ -112,7 +121,9 @@ def system(session):
# Install all test dependencies, then install this package into the
# virtualenv's dist-packages.
- session.install("mock", "pytest", "google-cloud-testutils")
+ session.install(
+ "mock", "pytest", "google-cloud-testutils",
+ )
session.install("-e", ".")
# Run py.test against the system tests.
@@ -155,3 +166,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/samples/AUTHORING_GUIDE.md b/samples/AUTHORING_GUIDE.md
new file mode 100644
index 00000000..55c97b32
--- /dev/null
+++ b/samples/AUTHORING_GUIDE.md
@@ -0,0 +1 @@
+See https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/AUTHORING_GUIDE.md
\ No newline at end of file
diff --git a/samples/CONTRIBUTING.md b/samples/CONTRIBUTING.md
new file mode 100644
index 00000000..34c882b6
--- /dev/null
+++ b/samples/CONTRIBUTING.md
@@ -0,0 +1 @@
+See https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/CONTRIBUTING.md
\ No newline at end of file
diff --git a/scripts/decrypt-secrets.sh b/scripts/decrypt-secrets.sh
index ff599eb2..21f6d2a2 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_documentai_v1beta2_keywords.py b/scripts/fixup_documentai_v1beta2_keywords.py
new file mode 100644
index 00000000..0cb9fcbf
--- /dev/null
+++ b/scripts/fixup_documentai_v1beta2_keywords.py
@@ -0,0 +1,179 @@
+# -*- 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 documentaiCallTransformer(cst.CSTTransformer):
+ CTRL_PARAMS: Tuple[str] = ('retry', 'timeout', 'metadata')
+ METHOD_TO_PARAMS: Dict[str, Tuple[str]] = {
+ 'batch_process_documents': ('requests', 'parent', ),
+ 'process_document': ('input_config', 'parent', 'output_config', 'document_type', 'table_extraction_params', 'form_extraction_params', 'entity_extraction_params', 'ocr_params', 'automl_params', ),
+
+ }
+
+ 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=documentaiCallTransformer(),
+):
+ """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 documentai 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_documentai_v1beta3_keywords.py b/scripts/fixup_documentai_v1beta3_keywords.py
new file mode 100644
index 00000000..2b689522
--- /dev/null
+++ b/scripts/fixup_documentai_v1beta3_keywords.py
@@ -0,0 +1,180 @@
+# -*- 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 documentaiCallTransformer(cst.CSTTransformer):
+ CTRL_PARAMS: Tuple[str] = ('retry', 'timeout', 'metadata')
+ METHOD_TO_PARAMS: Dict[str, Tuple[str]] = {
+ 'batch_process_documents': ('name', 'input_configs', 'output_config', ),
+ 'process_document': ('name', 'document', 'skip_human_review', ),
+ 'review_document': ('human_review_config', 'document', ),
+
+ }
+
+ 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=documentaiCallTransformer(),
+):
+ """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 documentai 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/synth.metadata b/synth.metadata
index 3f79a2d8..3a7962ec 100644
--- a/synth.metadata
+++ b/synth.metadata
@@ -3,23 +3,22 @@
{
"git": {
"name": ".",
- "remote": "https://github.com/googleapis/python-documentai.git",
- "sha": "a566241ad52a86692cd2c188dda63449009d7bf4"
+ "remote": "git@github.com:googleapis/python-documentai",
+ "sha": "ec70a8cec0f1fbd0f8ec18189139e632ec28b025"
}
},
{
"git": {
- "name": "googleapis",
- "remote": "https://github.com/googleapis/googleapis.git",
- "sha": "ff1b4ff2cfb09b66e26510d2c659705607563c1b",
- "internalRef": "317199748"
+ "name": "synthtool",
+ "remote": "https://github.com/googleapis/synthtool.git",
+ "sha": "e6168630be3e31eede633ba2c6f1cd64248dec1c"
}
},
{
"git": {
"name": "synthtool",
"remote": "https://github.com/googleapis/synthtool.git",
- "sha": "cd522c3b4dde821766d95c80ae5aeb43d7a41170"
+ "sha": "e6168630be3e31eede633ba2c6f1cd64248dec1c"
}
}
],
@@ -37,7 +36,7 @@
"client": {
"source": "googleapis",
"apiName": "documentai",
- "apiVersion": "v1beta2",
+ "apiVersion": "v1beta3",
"language": "python",
"generator": "bazel"
}
diff --git a/synth.py b/synth.py
index ccb27f9e..3ae8445c 100644
--- a/synth.py
+++ b/synth.py
@@ -14,6 +14,8 @@
"""This script is used to synthesize generated parts of this library."""
+from synthtool.languages import python
+
import synthtool as s
import synthtool.gcp as gcp
import logging
@@ -26,29 +28,32 @@
# ----------------------------------------------------------------------------
# Generate document AI GAPIC layer
# ----------------------------------------------------------------------------
-library = gapic.py_library(
- service="documentai",
- version="v1beta2",
- bazel_target="//google/cloud/documentai/v1beta2:documentai-v1beta2-py",
-)
-library = gapic.py_library("documentai", "v1beta2")
+versions = ["v1beta2", "v1beta3"]
+
+for version in versions:
+ library = gapic.py_library(
+ service="documentai",
+ version=version,
+ bazel_target=f"//google/cloud/documentai/{version}:documentai-{version}-py",
+ )
-excludes = ["README.rst", "nox.py", "docs/index.rst", "setup.py"]
-s.move(library, excludes=excludes)
+ excludes = ["README.rst", "nox.py", "docs/index.rst", "setup.py"]
+ s.move(library, excludes=excludes)
# ----------------------------------------------------------------------------
# Add templated files
# ----------------------------------------------------------------------------
templated_files = common.py_library(
cov_level=100,
- unit_test_python_versions=["3.6", "3.7", "3.8"],
- system_test_python_versions=["3.7"],
+ microgenerator=True,
+ samples=False, # set to true if there are samples
)
-s.move(templated_files, excludes=[".coveragerc"]) # microgenerator has a good .coveragerc file
+s.move(
+ templated_files,
+ excludes=[".coveragerc"], # microgenerator has a good .coveragerc file
+)
-# Extra lint ignores for microgenerator tests
-# TODO: Remove when https://github.com/googleapis/gapic-generator-python/issues/425 is closed
-s.replace(".flake8", "(ignore = .*)", "\g<1>, F401, F841")
+python.py_samples()
s.shell.run(["nox", "-s", "blacken"], hide_output=False)
diff --git a/tests/unit/gapic/documentai_v1beta2/__init__.py b/tests/unit/gapic/documentai_v1beta2/__init__.py
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/tests/unit/gapic/documentai_v1beta2/__init__.py
@@ -0,0 +1 @@
+
diff --git a/tests/unit/gapic/documentai_v1beta2/test_document_understanding_service.py b/tests/unit/gapic/documentai_v1beta2/test_document_understanding_service.py
index 9036afdb..290cb2f2 100644
--- a/tests/unit/gapic/documentai_v1beta2/test_document_understanding_service.py
+++ b/tests/unit/gapic/documentai_v1beta2/test_document_understanding_service.py
@@ -22,13 +22,16 @@
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 future
+from google.api_core import gapic_v1
from google.api_core import grpc_helpers
from google.api_core import grpc_helpers_async
-from google.api_core import operation_async
+from google.api_core import operation_async # type: ignore
from google.api_core import operations_v1
from google.auth import credentials
from google.auth.exceptions import MutualTLSChannelError
@@ -53,6 +56,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"
@@ -127,6 +141,16 @@ def test_document_understanding_service_client_get_transport_class():
),
],
)
+@mock.patch.object(
+ DocumentUnderstandingServiceClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(DocumentUnderstandingServiceClient),
+)
+@mock.patch.object(
+ DocumentUnderstandingServiceAsyncClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(DocumentUnderstandingServiceAsyncClient),
+)
def test_document_understanding_service_client_client_options(
client_class, transport_class, transport_name
):
@@ -151,96 +175,290 @@ def test_document_understanding_service_client_client_options(
patched.return_value = None
client = client_class(client_options=options)
patched.assert_called_once_with(
- api_mtls_endpoint="squid.clam.whelk",
- client_cert_source=None,
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 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.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 = client_class(client_options=options)
patched.assert_called_once_with(
- api_mtls_endpoint=client.DEFAULT_ENDPOINT,
- client_cert_source=None,
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,
)
- # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is
- # "always".
- os.environ["GOOGLE_API_USE_MTLS"] = "always"
+
+@pytest.mark.parametrize(
+ "client_class,transport_class,transport_name,use_client_cert_env",
+ [
+ (
+ DocumentUnderstandingServiceClient,
+ transports.DocumentUnderstandingServiceGrpcTransport,
+ "grpc",
+ "true",
+ ),
+ (
+ DocumentUnderstandingServiceAsyncClient,
+ transports.DocumentUnderstandingServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ "true",
+ ),
+ (
+ DocumentUnderstandingServiceClient,
+ transports.DocumentUnderstandingServiceGrpcTransport,
+ "grpc",
+ "false",
+ ),
+ (
+ DocumentUnderstandingServiceAsyncClient,
+ transports.DocumentUnderstandingServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ "false",
+ ),
+ ],
+)
+@mock.patch.object(
+ DocumentUnderstandingServiceClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(DocumentUnderstandingServiceClient),
+)
+@mock.patch.object(
+ DocumentUnderstandingServiceAsyncClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(DocumentUnderstandingServiceAsyncClient),
+)
+@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"})
+def test_document_understanding_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",
+ [
+ (
+ DocumentUnderstandingServiceClient,
+ transports.DocumentUnderstandingServiceGrpcTransport,
+ "grpc",
+ ),
+ (
+ DocumentUnderstandingServiceAsyncClient,
+ transports.DocumentUnderstandingServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ ),
+ ],
+)
+def test_document_understanding_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 = client_class(client_options=options)
patched.assert_called_once_with(
- api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT,
- client_cert_source=None,
credentials=None,
- host=client.DEFAULT_MTLS_ENDPOINT,
+ 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,
)
- # 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
- )
+
+@pytest.mark.parametrize(
+ "client_class,transport_class,transport_name",
+ [
+ (
+ DocumentUnderstandingServiceClient,
+ transports.DocumentUnderstandingServiceGrpcTransport,
+ "grpc",
+ ),
+ (
+ DocumentUnderstandingServiceAsyncClient,
+ transports.DocumentUnderstandingServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ ),
+ ],
+)
+def test_document_understanding_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(
- api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT,
- client_cert_source=client_cert_source_callback,
credentials=None,
- host=client.DEFAULT_MTLS_ENDPOINT,
+ 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,
)
- # 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,
- ):
- patched.return_value = None
- client = client_class()
- patched.assert_called_once_with(
- api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT,
- client_cert_source=None,
- credentials=None,
- host=client.DEFAULT_MTLS_ENDPOINT,
- )
-
- # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is
- # "auto", but client_cert_source and default_client_cert_source are None.
- os.environ["GOOGLE_API_USE_MTLS"] = "auto"
- with mock.patch.object(transport_class, "__init__") as patched:
- with mock.patch(
- "google.auth.transport.mtls.has_default_client_cert_source",
- return_value=False,
- ):
- patched.return_value = None
- client = client_class()
- patched.assert_called_once_with(
- api_mtls_endpoint=client.DEFAULT_ENDPOINT,
- client_cert_source=None,
- credentials=None,
- host=client.DEFAULT_ENDPOINT,
- )
-
- # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has
- # unsupported value.
- os.environ["GOOGLE_API_USE_MTLS"] = "Unsupported"
- with pytest.raises(MutualTLSChannelError):
- client = client_class()
-
- del os.environ["GOOGLE_API_USE_MTLS"]
-
def test_document_understanding_service_client_client_options_from_dict():
with mock.patch(
@@ -251,21 +469,27 @@ def test_document_understanding_service_client_client_options_from_dict():
client_options={"api_endpoint": "squid.clam.whelk"}
)
grpc_transport.assert_called_once_with(
- api_mtls_endpoint="squid.clam.whelk",
- client_cert_source=None,
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_batch_process_documents(transport: str = "grpc"):
+def test_batch_process_documents(
+ transport: str = "grpc",
+ request_type=document_understanding.BatchProcessDocumentsRequest,
+):
client = DocumentUnderstandingServiceClient(
- credentials=credentials.AnonymousCredentials(), transport=transport
+ 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 = document_understanding.BatchProcessDocumentsRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
with mock.patch.object(
@@ -280,16 +504,20 @@ def test_batch_process_documents(transport: str = "grpc"):
assert len(call.mock_calls) == 1
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == document_understanding.BatchProcessDocumentsRequest()
# Establish that the response is the type that we expect.
assert isinstance(response, future.Future)
+def test_batch_process_documents_from_dict():
+ test_batch_process_documents(request_type=dict)
+
+
@pytest.mark.asyncio
async def test_batch_process_documents_async(transport: str = "grpc_asyncio"):
client = DocumentUnderstandingServiceAsyncClient(
- credentials=credentials.AnonymousCredentials(), transport=transport
+ credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
@@ -319,7 +547,7 @@ async def test_batch_process_documents_async(transport: str = "grpc_asyncio"):
def test_batch_process_documents_field_headers():
client = DocumentUnderstandingServiceClient(
- credentials=credentials.AnonymousCredentials()
+ credentials=credentials.AnonymousCredentials(),
)
# Any value that is part of the HTTP/1.1 URI should be sent as
@@ -342,13 +570,13 @@ def test_batch_process_documents_field_headers():
# Establish that the field header was sent.
_, _, kw = call.mock_calls[0]
- assert ("x-goog-request-params", "parent=parent/value") in kw["metadata"]
+ assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"]
@pytest.mark.asyncio
async def test_batch_process_documents_field_headers_async():
client = DocumentUnderstandingServiceAsyncClient(
- credentials=credentials.AnonymousCredentials()
+ credentials=credentials.AnonymousCredentials(),
)
# Any value that is part of the HTTP/1.1 URI should be sent as
@@ -373,12 +601,12 @@ async def test_batch_process_documents_field_headers_async():
# Establish that the field header was sent.
_, _, kw = call.mock_calls[0]
- assert ("x-goog-request-params", "parent=parent/value") in kw["metadata"]
+ assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"]
def test_batch_process_documents_flattened():
client = DocumentUnderstandingServiceClient(
- credentials=credentials.AnonymousCredentials()
+ credentials=credentials.AnonymousCredentials(),
)
# Mock the actual call within the gRPC stub, and fake the request.
@@ -393,13 +621,14 @@ def test_batch_process_documents_flattened():
client.batch_process_documents(
requests=[
document_understanding.ProcessDocumentRequest(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].requests == [
document_understanding.ProcessDocumentRequest(parent="parent_value")
]
@@ -407,7 +636,7 @@ def test_batch_process_documents_flattened():
def test_batch_process_documents_flattened_error():
client = DocumentUnderstandingServiceClient(
- credentials=credentials.AnonymousCredentials()
+ credentials=credentials.AnonymousCredentials(),
)
# Attempting to call a method with both a request object and flattened
@@ -424,7 +653,7 @@ def test_batch_process_documents_flattened_error():
@pytest.mark.asyncio
async def test_batch_process_documents_flattened_async():
client = DocumentUnderstandingServiceAsyncClient(
- credentials=credentials.AnonymousCredentials()
+ credentials=credentials.AnonymousCredentials(),
)
# Mock the actual call within the gRPC stub, and fake the request.
@@ -442,13 +671,14 @@ async def test_batch_process_documents_flattened_async():
response = await client.batch_process_documents(
requests=[
document_understanding.ProcessDocumentRequest(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].requests == [
document_understanding.ProcessDocumentRequest(parent="parent_value")
]
@@ -457,7 +687,7 @@ async def test_batch_process_documents_flattened_async():
@pytest.mark.asyncio
async def test_batch_process_documents_flattened_error_async():
client = DocumentUnderstandingServiceAsyncClient(
- credentials=credentials.AnonymousCredentials()
+ credentials=credentials.AnonymousCredentials(),
)
# Attempting to call a method with both a request object and flattened
@@ -471,14 +701,16 @@ async def test_batch_process_documents_flattened_error_async():
)
-def test_process_document(transport: str = "grpc"):
+def test_process_document(
+ transport: str = "grpc", request_type=document_understanding.ProcessDocumentRequest
+):
client = DocumentUnderstandingServiceClient(
- credentials=credentials.AnonymousCredentials(), transport=transport
+ 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 = document_understanding.ProcessDocumentRequest()
+ request = request_type()
# Mock the actual call within the gRPC stub, and fake the request.
with mock.patch.object(
@@ -486,10 +718,7 @@ def test_process_document(transport: str = "grpc"):
) as call:
# Designate an appropriate return value for the call.
call.return_value = document.Document(
- uri="uri_value",
- content=b"content_blob",
- mime_type="mime_type_value",
- text="text_value",
+ mime_type="mime_type_value", text="text_value", uri="uri_value",
)
response = client.process_document(request)
@@ -498,20 +727,24 @@ def test_process_document(transport: str = "grpc"):
assert len(call.mock_calls) == 1
_, args, _ = call.mock_calls[0]
- assert args[0] == request
+ assert args[0] == document_understanding.ProcessDocumentRequest()
# Establish that the response is the type that we expect.
assert isinstance(response, document.Document)
- assert response.uri == "uri_value"
- assert response.content == b"content_blob"
+
assert response.mime_type == "mime_type_value"
+
assert response.text == "text_value"
+def test_process_document_from_dict():
+ test_process_document(request_type=dict)
+
+
@pytest.mark.asyncio
async def test_process_document_async(transport: str = "grpc_asyncio"):
client = DocumentUnderstandingServiceAsyncClient(
- credentials=credentials.AnonymousCredentials(), transport=transport
+ credentials=credentials.AnonymousCredentials(), transport=transport,
)
# Everything is optional in proto3 as far as the runtime is concerned,
@@ -524,12 +757,7 @@ async def test_process_document_async(transport: str = "grpc_asyncio"):
) as call:
# Designate an appropriate return value for the call.
call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
- document.Document(
- uri="uri_value",
- content=b"content_blob",
- mime_type="mime_type_value",
- text="text_value",
- )
+ document.Document(mime_type="mime_type_value", text="text_value",)
)
response = await client.process_document(request)
@@ -542,15 +770,15 @@ async def test_process_document_async(transport: str = "grpc_asyncio"):
# Establish that the response is the type that we expect.
assert isinstance(response, document.Document)
- assert response.uri == "uri_value"
- assert response.content == b"content_blob"
+
assert response.mime_type == "mime_type_value"
+
assert response.text == "text_value"
def test_process_document_field_headers():
client = DocumentUnderstandingServiceClient(
- credentials=credentials.AnonymousCredentials()
+ credentials=credentials.AnonymousCredentials(),
)
# Any value that is part of the HTTP/1.1 URI should be sent as
@@ -573,13 +801,13 @@ def test_process_document_field_headers():
# Establish that the field header was sent.
_, _, kw = call.mock_calls[0]
- assert ("x-goog-request-params", "parent=parent/value") in kw["metadata"]
+ assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"]
@pytest.mark.asyncio
async def test_process_document_field_headers_async():
client = DocumentUnderstandingServiceAsyncClient(
- credentials=credentials.AnonymousCredentials()
+ credentials=credentials.AnonymousCredentials(),
)
# Any value that is part of the HTTP/1.1 URI should be sent as
@@ -602,24 +830,43 @@ async def test_process_document_field_headers_async():
# Establish that the field header was sent.
_, _, kw = call.mock_calls[0]
- assert ("x-goog-request-params", "parent=parent/value") in kw["metadata"]
+ assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"]
def test_credentials_transport_error():
# It is an error to provide credentials and a transport instance.
transport = transports.DocumentUnderstandingServiceGrpcTransport(
- credentials=credentials.AnonymousCredentials()
+ credentials=credentials.AnonymousCredentials(),
)
with pytest.raises(ValueError):
client = DocumentUnderstandingServiceClient(
- credentials=credentials.AnonymousCredentials(), transport=transport
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # It is an error to provide a credentials file and a transport instance.
+ transport = transports.DocumentUnderstandingServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ with pytest.raises(ValueError):
+ client = DocumentUnderstandingServiceClient(
+ client_options={"credentials_file": "credentials.json"},
+ transport=transport,
+ )
+
+ # It is an error to provide scopes and a transport instance.
+ transport = transports.DocumentUnderstandingServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ with pytest.raises(ValueError):
+ client = DocumentUnderstandingServiceClient(
+ client_options={"scopes": ["1", "2"]}, transport=transport,
)
def test_transport_instance():
# A client may be instantiated with a custom transport instance.
transport = transports.DocumentUnderstandingServiceGrpcTransport(
- credentials=credentials.AnonymousCredentials()
+ credentials=credentials.AnonymousCredentials(),
)
client = DocumentUnderstandingServiceClient(transport=transport)
assert client._transport is transport
@@ -628,37 +875,68 @@ def test_transport_instance():
def test_transport_get_channel():
# A client may be instantiated with a custom transport instance.
transport = transports.DocumentUnderstandingServiceGrpcTransport(
- credentials=credentials.AnonymousCredentials()
+ credentials=credentials.AnonymousCredentials(),
)
channel = transport.grpc_channel
assert channel
transport = transports.DocumentUnderstandingServiceGrpcAsyncIOTransport(
- credentials=credentials.AnonymousCredentials()
+ credentials=credentials.AnonymousCredentials(),
)
channel = transport.grpc_channel
assert channel
+@pytest.mark.parametrize(
+ "transport_class",
+ [
+ transports.DocumentUnderstandingServiceGrpcTransport,
+ transports.DocumentUnderstandingServiceGrpcAsyncIOTransport,
+ ],
+)
+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 = DocumentUnderstandingServiceClient(
- credentials=credentials.AnonymousCredentials()
+ credentials=credentials.AnonymousCredentials(),
)
assert isinstance(
- client._transport, transports.DocumentUnderstandingServiceGrpcTransport
+ client._transport, transports.DocumentUnderstandingServiceGrpcTransport,
)
+def test_document_understanding_service_base_transport_error():
+ # Passing both a credentials object and credentials_file should raise an error
+ with pytest.raises(exceptions.DuplicateCredentialArgs):
+ transport = transports.DocumentUnderstandingServiceTransport(
+ credentials=credentials.AnonymousCredentials(),
+ credentials_file="credentials.json",
+ )
+
+
def test_document_understanding_service_base_transport():
# Instantiate the base transport.
- transport = transports.DocumentUnderstandingServiceTransport(
- credentials=credentials.AnonymousCredentials()
- )
+ with mock.patch(
+ "google.cloud.documentai_v1beta2.services.document_understanding_service.transports.DocumentUnderstandingServiceTransport.__init__"
+ ) as Transport:
+ Transport.return_value = None
+ transport = transports.DocumentUnderstandingServiceTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
# Every method on the transport should just blindly
# raise NotImplementedError.
- methods = ("batch_process_documents", "process_document")
+ methods = (
+ "batch_process_documents",
+ "process_document",
+ )
for method in methods:
with pytest.raises(NotImplementedError):
getattr(transport, method)(request=object())
@@ -669,13 +947,44 @@ def test_document_understanding_service_base_transport():
transport.operations_client
+def test_document_understanding_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.documentai_v1beta2.services.document_understanding_service.transports.DocumentUnderstandingServiceTransport._prep_wrapped_messages"
+ ) as Transport:
+ Transport.return_value = None
+ load_creds.return_value = (credentials.AnonymousCredentials(), None)
+ transport = transports.DocumentUnderstandingServiceTransport(
+ credentials_file="credentials.json", quota_project_id="octopus",
+ )
+ load_creds.assert_called_once_with(
+ "credentials.json",
+ scopes=("https://www.googleapis.com/auth/cloud-platform",),
+ quota_project_id="octopus",
+ )
+
+
+def test_document_understanding_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.documentai_v1beta2.services.document_understanding_service.transports.DocumentUnderstandingServiceTransport._prep_wrapped_messages"
+ ) as Transport:
+ Transport.return_value = None
+ adc.return_value = (credentials.AnonymousCredentials(), None)
+ transport = transports.DocumentUnderstandingServiceTransport()
+ adc.assert_called_once()
+
+
def test_document_understanding_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)
DocumentUnderstandingServiceClient()
adc.assert_called_once_with(
- scopes=("https://www.googleapis.com/auth/cloud-platform",)
+ scopes=("https://www.googleapis.com/auth/cloud-platform",),
+ quota_project_id=None,
)
@@ -684,9 +993,12 @@ def test_document_understanding_service_transport_auth_adc():
# ADC credentials.
with mock.patch.object(auth, "default") as adc:
adc.return_value = (credentials.AnonymousCredentials(), None)
- transports.DocumentUnderstandingServiceGrpcTransport(host="squid.clam.whelk")
+ transports.DocumentUnderstandingServiceGrpcTransport(
+ 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-platform",),
+ quota_project_id="octopus",
)
@@ -713,181 +1025,122 @@ def test_document_understanding_service_host_with_port():
def test_document_understanding_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.DocumentUnderstandingServiceGrpcTransport(
- 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
def test_document_understanding_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.DocumentUnderstandingServiceGrpcAsyncIOTransport(
- 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_document_understanding_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.DocumentUnderstandingServiceGrpcTransport(
- 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,
- ssl_credentials=mock_ssl_cred,
- scopes=("https://www.googleapis.com/auth/cloud-platform",),
- )
- 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_document_understanding_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.DocumentUnderstandingServiceGrpcAsyncIOTransport(
- 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,
- ssl_credentials=mock_ssl_cred,
- scopes=("https://www.googleapis.com/auth/cloud-platform",),
- )
- assert transport.grpc_channel == mock_grpc_channel
@pytest.mark.parametrize(
- "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"]
+ "transport_class",
+ [
+ transports.DocumentUnderstandingServiceGrpcTransport,
+ transports.DocumentUnderstandingServiceGrpcAsyncIOTransport,
+ ],
)
-@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True)
-def test_document_understanding_service_grpc_transport_channel_mtls_with_adc(
- grpc_create_channel, api_mtls_endpoint
+def test_document_understanding_service_transport_channel_mtls_with_client_cert_source(
+ transport_class,
):
- # 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.DocumentUnderstandingServiceGrpcTransport(
- 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,
- ssl_credentials=mock_ssl_cred,
- scopes=("https://www.googleapis.com/auth/cloud-platform",),
- )
- assert transport.grpc_channel == mock_grpc_channel
+ 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-platform",),
+ ssl_credentials=mock_ssl_cred,
+ quota_project_id=None,
+ )
+ assert transport.grpc_channel == mock_grpc_channel
@pytest.mark.parametrize(
- "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"]
+ "transport_class",
+ [
+ transports.DocumentUnderstandingServiceGrpcTransport,
+ transports.DocumentUnderstandingServiceGrpcAsyncIOTransport,
+ ],
)
-@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True)
-def test_document_understanding_service_grpc_asyncio_transport_channel_mtls_with_adc(
- grpc_create_channel, api_mtls_endpoint
+def test_document_understanding_service_transport_channel_mtls_with_adc(
+ transport_class,
):
- # 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.DocumentUnderstandingServiceGrpcAsyncIOTransport(
- 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,
- ssl_credentials=mock_ssl_cred,
- scopes=("https://www.googleapis.com/auth/cloud-platform",),
- )
- 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-platform",),
+ ssl_credentials=mock_ssl_cred,
+ quota_project_id=None,
+ )
+ assert transport.grpc_channel == mock_grpc_channel
def test_document_understanding_service_grpc_lro_client():
client = DocumentUnderstandingServiceClient(
- credentials=credentials.AnonymousCredentials(), transport="grpc"
+ credentials=credentials.AnonymousCredentials(), transport="grpc",
)
transport = client._transport
# Ensure that we have a api-core operations client.
- assert isinstance(transport.operations_client, operations_v1.OperationsClient)
+ assert isinstance(transport.operations_client, operations_v1.OperationsClient,)
# Ensure that subsequent calls to the property send the exact same object.
assert transport.operations_client is transport.operations_client
@@ -895,12 +1148,33 @@ def test_document_understanding_service_grpc_lro_client():
def test_document_understanding_service_grpc_lro_async_client():
client = DocumentUnderstandingServiceAsyncClient(
- credentials=credentials.AnonymousCredentials(), transport="grpc_asyncio"
+ credentials=credentials.AnonymousCredentials(), transport="grpc_asyncio",
)
transport = client._client._transport
# Ensure that we have a api-core operations client.
- assert isinstance(transport.operations_client, operations_v1.OperationsAsyncClient)
+ assert isinstance(transport.operations_client, operations_v1.OperationsAsyncClient,)
# Ensure that subsequent calls to the property send the exact same object.
assert transport.operations_client is transport.operations_client
+
+
+def test_client_withDEFAULT_CLIENT_INFO():
+ client_info = gapic_v1.client_info.ClientInfo()
+
+ with mock.patch.object(
+ transports.DocumentUnderstandingServiceTransport, "_prep_wrapped_messages"
+ ) as prep:
+ client = DocumentUnderstandingServiceClient(
+ credentials=credentials.AnonymousCredentials(), client_info=client_info,
+ )
+ prep.assert_called_once_with(client_info)
+
+ with mock.patch.object(
+ transports.DocumentUnderstandingServiceTransport, "_prep_wrapped_messages"
+ ) as prep:
+ transport_class = DocumentUnderstandingServiceClient.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/documentai_v1beta3/__init__.py b/tests/unit/gapic/documentai_v1beta3/__init__.py
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/tests/unit/gapic/documentai_v1beta3/__init__.py
@@ -0,0 +1 @@
+
diff --git a/tests/unit/gapic/documentai_v1beta3/test_document_processor_service.py b/tests/unit/gapic/documentai_v1beta3/test_document_processor_service.py
new file mode 100644
index 00000000..4b17a5ed
--- /dev/null
+++ b/tests/unit/gapic/documentai_v1beta3/test_document_processor_service.py
@@ -0,0 +1,1454 @@
+# -*- 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 future
+from google.api_core import gapic_v1
+from google.api_core import grpc_helpers
+from google.api_core import grpc_helpers_async
+from google.api_core import operation_async # type: ignore
+from google.api_core import operations_v1
+from google.auth import credentials
+from google.auth.exceptions import MutualTLSChannelError
+from google.cloud.documentai_v1beta3.services.document_processor_service import (
+ DocumentProcessorServiceAsyncClient,
+)
+from google.cloud.documentai_v1beta3.services.document_processor_service import (
+ DocumentProcessorServiceClient,
+)
+from google.cloud.documentai_v1beta3.services.document_processor_service import (
+ transports,
+)
+from google.cloud.documentai_v1beta3.types import document
+from google.cloud.documentai_v1beta3.types import document_processor_service
+from google.cloud.documentai_v1beta3.types import geometry
+from google.longrunning import operations_pb2
+from google.oauth2 import service_account
+from google.protobuf import any_pb2 as gp_any # type: ignore
+from google.protobuf import duration_pb2 as duration # type: ignore
+from google.protobuf import timestamp_pb2 as timestamp # type: ignore
+from google.protobuf import wrappers_pb2 as wrappers # type: ignore
+from google.rpc import status_pb2 as status # type: ignore
+from google.type import color_pb2 as color # type: ignore
+from google.type import date_pb2 as date # type: ignore
+from google.type import datetime_pb2 as datetime # type: ignore
+from google.type import money_pb2 as money # type: ignore
+from google.type import postal_address_pb2 as postal_address # 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 DocumentProcessorServiceClient._get_default_mtls_endpoint(None) is None
+ assert (
+ DocumentProcessorServiceClient._get_default_mtls_endpoint(api_endpoint)
+ == api_mtls_endpoint
+ )
+ assert (
+ DocumentProcessorServiceClient._get_default_mtls_endpoint(api_mtls_endpoint)
+ == api_mtls_endpoint
+ )
+ assert (
+ DocumentProcessorServiceClient._get_default_mtls_endpoint(sandbox_endpoint)
+ == sandbox_mtls_endpoint
+ )
+ assert (
+ DocumentProcessorServiceClient._get_default_mtls_endpoint(sandbox_mtls_endpoint)
+ == sandbox_mtls_endpoint
+ )
+ assert (
+ DocumentProcessorServiceClient._get_default_mtls_endpoint(non_googleapi)
+ == non_googleapi
+ )
+
+
+@pytest.mark.parametrize(
+ "client_class",
+ [DocumentProcessorServiceClient, DocumentProcessorServiceAsyncClient],
+)
+def test_document_processor_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 == "us-documentai.googleapis.com:443"
+
+
+def test_document_processor_service_client_get_transport_class():
+ transport = DocumentProcessorServiceClient.get_transport_class()
+ assert transport == transports.DocumentProcessorServiceGrpcTransport
+
+ transport = DocumentProcessorServiceClient.get_transport_class("grpc")
+ assert transport == transports.DocumentProcessorServiceGrpcTransport
+
+
+@pytest.mark.parametrize(
+ "client_class,transport_class,transport_name",
+ [
+ (
+ DocumentProcessorServiceClient,
+ transports.DocumentProcessorServiceGrpcTransport,
+ "grpc",
+ ),
+ (
+ DocumentProcessorServiceAsyncClient,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ ),
+ ],
+)
+@mock.patch.object(
+ DocumentProcessorServiceClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(DocumentProcessorServiceClient),
+)
+@mock.patch.object(
+ DocumentProcessorServiceAsyncClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(DocumentProcessorServiceAsyncClient),
+)
+def test_document_processor_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(
+ DocumentProcessorServiceClient, "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(
+ DocumentProcessorServiceClient, "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",
+ [
+ (
+ DocumentProcessorServiceClient,
+ transports.DocumentProcessorServiceGrpcTransport,
+ "grpc",
+ "true",
+ ),
+ (
+ DocumentProcessorServiceAsyncClient,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ "true",
+ ),
+ (
+ DocumentProcessorServiceClient,
+ transports.DocumentProcessorServiceGrpcTransport,
+ "grpc",
+ "false",
+ ),
+ (
+ DocumentProcessorServiceAsyncClient,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ "false",
+ ),
+ ],
+)
+@mock.patch.object(
+ DocumentProcessorServiceClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(DocumentProcessorServiceClient),
+)
+@mock.patch.object(
+ DocumentProcessorServiceAsyncClient,
+ "DEFAULT_ENDPOINT",
+ modify_default_endpoint(DocumentProcessorServiceAsyncClient),
+)
+@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"})
+def test_document_processor_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",
+ [
+ (
+ DocumentProcessorServiceClient,
+ transports.DocumentProcessorServiceGrpcTransport,
+ "grpc",
+ ),
+ (
+ DocumentProcessorServiceAsyncClient,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ ),
+ ],
+)
+def test_document_processor_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",
+ [
+ (
+ DocumentProcessorServiceClient,
+ transports.DocumentProcessorServiceGrpcTransport,
+ "grpc",
+ ),
+ (
+ DocumentProcessorServiceAsyncClient,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ "grpc_asyncio",
+ ),
+ ],
+)
+def test_document_processor_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_document_processor_service_client_client_options_from_dict():
+ with mock.patch(
+ "google.cloud.documentai_v1beta3.services.document_processor_service.transports.DocumentProcessorServiceGrpcTransport.__init__"
+ ) as grpc_transport:
+ grpc_transport.return_value = None
+ client = DocumentProcessorServiceClient(
+ 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_process_document(
+ transport: str = "grpc", request_type=document_processor_service.ProcessRequest
+):
+ client = DocumentProcessorServiceClient(
+ 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.process_document), "__call__"
+ ) as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = document_processor_service.ProcessResponse(
+ human_review_operation="human_review_operation_value",
+ )
+
+ response = client.process_document(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_processor_service.ProcessRequest()
+
+ # Establish that the response is the type that we expect.
+ assert isinstance(response, document_processor_service.ProcessResponse)
+
+ assert response.human_review_operation == "human_review_operation_value"
+
+
+def test_process_document_from_dict():
+ test_process_document(request_type=dict)
+
+
+@pytest.mark.asyncio
+async def test_process_document_async(transport: str = "grpc_asyncio"):
+ client = DocumentProcessorServiceAsyncClient(
+ 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 = document_processor_service.ProcessRequest()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client._client._transport.process_document), "__call__"
+ ) as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ document_processor_service.ProcessResponse(
+ human_review_operation="human_review_operation_value",
+ )
+ )
+
+ response = await client.process_document(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 response is the type that we expect.
+ assert isinstance(response, document_processor_service.ProcessResponse)
+
+ assert response.human_review_operation == "human_review_operation_value"
+
+
+def test_process_document_field_headers():
+ client = DocumentProcessorServiceClient(
+ 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 = document_processor_service.ProcessRequest()
+ request.name = "name/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client._transport.process_document), "__call__"
+ ) as call:
+ call.return_value = document_processor_service.ProcessResponse()
+
+ client.process_document(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_process_document_field_headers_async():
+ client = DocumentProcessorServiceAsyncClient(
+ 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 = document_processor_service.ProcessRequest()
+ request.name = "name/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client._client._transport.process_document), "__call__"
+ ) as call:
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ document_processor_service.ProcessResponse()
+ )
+
+ await client.process_document(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_process_document_flattened():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client._transport.process_document), "__call__"
+ ) as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = document_processor_service.ProcessResponse()
+
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ client.process_document(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_process_document_flattened_error():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ client.process_document(
+ document_processor_service.ProcessRequest(), name="name_value",
+ )
+
+
+@pytest.mark.asyncio
+async def test_process_document_flattened_async():
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client._client._transport.process_document), "__call__"
+ ) as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = document_processor_service.ProcessResponse()
+
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ document_processor_service.ProcessResponse()
+ )
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ response = await client.process_document(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_process_document_flattened_error_async():
+ client = DocumentProcessorServiceAsyncClient(
+ 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.process_document(
+ document_processor_service.ProcessRequest(), name="name_value",
+ )
+
+
+def test_batch_process_documents(
+ transport: str = "grpc", request_type=document_processor_service.BatchProcessRequest
+):
+ client = DocumentProcessorServiceClient(
+ 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.batch_process_documents), "__call__"
+ ) as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = operations_pb2.Operation(name="operations/spam")
+
+ response = client.batch_process_documents(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_processor_service.BatchProcessRequest()
+
+ # Establish that the response is the type that we expect.
+ assert isinstance(response, future.Future)
+
+
+def test_batch_process_documents_from_dict():
+ test_batch_process_documents(request_type=dict)
+
+
+@pytest.mark.asyncio
+async def test_batch_process_documents_async(transport: str = "grpc_asyncio"):
+ client = DocumentProcessorServiceAsyncClient(
+ 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 = document_processor_service.BatchProcessRequest()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client._client._transport.batch_process_documents), "__call__"
+ ) as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ operations_pb2.Operation(name="operations/spam")
+ )
+
+ response = await client.batch_process_documents(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 response is the type that we expect.
+ assert isinstance(response, future.Future)
+
+
+def test_batch_process_documents_field_headers():
+ client = DocumentProcessorServiceClient(
+ 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 = document_processor_service.BatchProcessRequest()
+ request.name = "name/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client._transport.batch_process_documents), "__call__"
+ ) as call:
+ call.return_value = operations_pb2.Operation(name="operations/op")
+
+ client.batch_process_documents(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_batch_process_documents_field_headers_async():
+ client = DocumentProcessorServiceAsyncClient(
+ 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 = document_processor_service.BatchProcessRequest()
+ request.name = "name/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client._client._transport.batch_process_documents), "__call__"
+ ) as call:
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ operations_pb2.Operation(name="operations/op")
+ )
+
+ await client.batch_process_documents(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_batch_process_documents_flattened():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client._transport.batch_process_documents), "__call__"
+ ) as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = operations_pb2.Operation(name="operations/op")
+
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ client.batch_process_documents(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_batch_process_documents_flattened_error():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ client.batch_process_documents(
+ document_processor_service.BatchProcessRequest(), name="name_value",
+ )
+
+
+@pytest.mark.asyncio
+async def test_batch_process_documents_flattened_async():
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client._client._transport.batch_process_documents), "__call__"
+ ) as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = operations_pb2.Operation(name="operations/op")
+
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ operations_pb2.Operation(name="operations/spam")
+ )
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ response = await client.batch_process_documents(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_batch_process_documents_flattened_error_async():
+ client = DocumentProcessorServiceAsyncClient(
+ 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.batch_process_documents(
+ document_processor_service.BatchProcessRequest(), name="name_value",
+ )
+
+
+def test_review_document(
+ transport: str = "grpc",
+ request_type=document_processor_service.ReviewDocumentRequest,
+):
+ client = DocumentProcessorServiceClient(
+ 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.review_document), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = operations_pb2.Operation(name="operations/spam")
+
+ response = client.review_document(request)
+
+ # Establish that the underlying gRPC stub method was called.
+ assert len(call.mock_calls) == 1
+ _, args, _ = call.mock_calls[0]
+
+ assert args[0] == document_processor_service.ReviewDocumentRequest()
+
+ # Establish that the response is the type that we expect.
+ assert isinstance(response, future.Future)
+
+
+def test_review_document_from_dict():
+ test_review_document(request_type=dict)
+
+
+@pytest.mark.asyncio
+async def test_review_document_async(transport: str = "grpc_asyncio"):
+ client = DocumentProcessorServiceAsyncClient(
+ 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 = document_processor_service.ReviewDocumentRequest()
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client._client._transport.review_document), "__call__"
+ ) as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ operations_pb2.Operation(name="operations/spam")
+ )
+
+ response = await client.review_document(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 response is the type that we expect.
+ assert isinstance(response, future.Future)
+
+
+def test_review_document_field_headers():
+ client = DocumentProcessorServiceClient(
+ 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 = document_processor_service.ReviewDocumentRequest()
+ request.human_review_config = "human_review_config/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client._transport.review_document), "__call__") as call:
+ call.return_value = operations_pb2.Operation(name="operations/op")
+
+ client.review_document(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",
+ "human_review_config=human_review_config/value",
+ ) in kw["metadata"]
+
+
+@pytest.mark.asyncio
+async def test_review_document_field_headers_async():
+ client = DocumentProcessorServiceAsyncClient(
+ 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 = document_processor_service.ReviewDocumentRequest()
+ request.human_review_config = "human_review_config/value"
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client._client._transport.review_document), "__call__"
+ ) as call:
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ operations_pb2.Operation(name="operations/op")
+ )
+
+ await client.review_document(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",
+ "human_review_config=human_review_config/value",
+ ) in kw["metadata"]
+
+
+def test_review_document_flattened():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(type(client._transport.review_document), "__call__") as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = operations_pb2.Operation(name="operations/op")
+
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ client.review_document(human_review_config="human_review_config_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].human_review_config == "human_review_config_value"
+
+
+def test_review_document_flattened_error():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Attempting to call a method with both a request object and flattened
+ # fields is an error.
+ with pytest.raises(ValueError):
+ client.review_document(
+ document_processor_service.ReviewDocumentRequest(),
+ human_review_config="human_review_config_value",
+ )
+
+
+@pytest.mark.asyncio
+async def test_review_document_flattened_async():
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Mock the actual call within the gRPC stub, and fake the request.
+ with mock.patch.object(
+ type(client._client._transport.review_document), "__call__"
+ ) as call:
+ # Designate an appropriate return value for the call.
+ call.return_value = operations_pb2.Operation(name="operations/op")
+
+ call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(
+ operations_pb2.Operation(name="operations/spam")
+ )
+ # Call the method with a truthy value for each flattened field,
+ # using the keyword arguments to the method.
+ response = await client.review_document(
+ human_review_config="human_review_config_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].human_review_config == "human_review_config_value"
+
+
+@pytest.mark.asyncio
+async def test_review_document_flattened_error_async():
+ client = DocumentProcessorServiceAsyncClient(
+ 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.review_document(
+ document_processor_service.ReviewDocumentRequest(),
+ human_review_config="human_review_config_value",
+ )
+
+
+def test_credentials_transport_error():
+ # It is an error to provide credentials and a transport instance.
+ transport = transports.DocumentProcessorServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ with pytest.raises(ValueError):
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport=transport,
+ )
+
+ # It is an error to provide a credentials file and a transport instance.
+ transport = transports.DocumentProcessorServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ with pytest.raises(ValueError):
+ client = DocumentProcessorServiceClient(
+ client_options={"credentials_file": "credentials.json"},
+ transport=transport,
+ )
+
+ # It is an error to provide scopes and a transport instance.
+ transport = transports.DocumentProcessorServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ with pytest.raises(ValueError):
+ client = DocumentProcessorServiceClient(
+ client_options={"scopes": ["1", "2"]}, transport=transport,
+ )
+
+
+def test_transport_instance():
+ # A client may be instantiated with a custom transport instance.
+ transport = transports.DocumentProcessorServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ client = DocumentProcessorServiceClient(transport=transport)
+ assert client._transport is transport
+
+
+def test_transport_get_channel():
+ # A client may be instantiated with a custom transport instance.
+ transport = transports.DocumentProcessorServiceGrpcTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ channel = transport.grpc_channel
+ assert channel
+
+ transport = transports.DocumentProcessorServiceGrpcAsyncIOTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ channel = transport.grpc_channel
+ assert channel
+
+
+@pytest.mark.parametrize(
+ "transport_class",
+ [
+ transports.DocumentProcessorServiceGrpcTransport,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ ],
+)
+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 = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ )
+ assert isinstance(
+ client._transport, transports.DocumentProcessorServiceGrpcTransport,
+ )
+
+
+def test_document_processor_service_base_transport_error():
+ # Passing both a credentials object and credentials_file should raise an error
+ with pytest.raises(exceptions.DuplicateCredentialArgs):
+ transport = transports.DocumentProcessorServiceTransport(
+ credentials=credentials.AnonymousCredentials(),
+ credentials_file="credentials.json",
+ )
+
+
+def test_document_processor_service_base_transport():
+ # Instantiate the base transport.
+ with mock.patch(
+ "google.cloud.documentai_v1beta3.services.document_processor_service.transports.DocumentProcessorServiceTransport.__init__"
+ ) as Transport:
+ Transport.return_value = None
+ transport = transports.DocumentProcessorServiceTransport(
+ credentials=credentials.AnonymousCredentials(),
+ )
+
+ # Every method on the transport should just blindly
+ # raise NotImplementedError.
+ methods = (
+ "process_document",
+ "batch_process_documents",
+ "review_document",
+ )
+ for method in methods:
+ with pytest.raises(NotImplementedError):
+ getattr(transport, method)(request=object())
+
+ # Additionally, the LRO client (a property) should
+ # also raise NotImplementedError
+ with pytest.raises(NotImplementedError):
+ transport.operations_client
+
+
+def test_document_processor_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.documentai_v1beta3.services.document_processor_service.transports.DocumentProcessorServiceTransport._prep_wrapped_messages"
+ ) as Transport:
+ Transport.return_value = None
+ load_creds.return_value = (credentials.AnonymousCredentials(), None)
+ transport = transports.DocumentProcessorServiceTransport(
+ credentials_file="credentials.json", quota_project_id="octopus",
+ )
+ load_creds.assert_called_once_with(
+ "credentials.json",
+ scopes=("https://www.googleapis.com/auth/cloud-platform",),
+ quota_project_id="octopus",
+ )
+
+
+def test_document_processor_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.documentai_v1beta3.services.document_processor_service.transports.DocumentProcessorServiceTransport._prep_wrapped_messages"
+ ) as Transport:
+ Transport.return_value = None
+ adc.return_value = (credentials.AnonymousCredentials(), None)
+ transport = transports.DocumentProcessorServiceTransport()
+ adc.assert_called_once()
+
+
+def test_document_processor_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)
+ DocumentProcessorServiceClient()
+ adc.assert_called_once_with(
+ scopes=("https://www.googleapis.com/auth/cloud-platform",),
+ quota_project_id=None,
+ )
+
+
+def test_document_processor_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.DocumentProcessorServiceGrpcTransport(
+ host="squid.clam.whelk", quota_project_id="octopus"
+ )
+ adc.assert_called_once_with(
+ scopes=("https://www.googleapis.com/auth/cloud-platform",),
+ quota_project_id="octopus",
+ )
+
+
+def test_document_processor_service_host_no_port():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ client_options=client_options.ClientOptions(
+ api_endpoint="us-documentai.googleapis.com"
+ ),
+ )
+ assert client._transport._host == "us-documentai.googleapis.com:443"
+
+
+def test_document_processor_service_host_with_port():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(),
+ client_options=client_options.ClientOptions(
+ api_endpoint="us-documentai.googleapis.com:8000"
+ ),
+ )
+ assert client._transport._host == "us-documentai.googleapis.com:8000"
+
+
+def test_document_processor_service_grpc_transport_channel():
+ channel = grpc.insecure_channel("http://localhost/")
+
+ # Check that channel is used if provided.
+ transport = transports.DocumentProcessorServiceGrpcTransport(
+ host="squid.clam.whelk", channel=channel,
+ )
+ assert transport.grpc_channel == channel
+ assert transport._host == "squid.clam.whelk:443"
+
+
+def test_document_processor_service_grpc_asyncio_transport_channel():
+ channel = aio.insecure_channel("http://localhost/")
+
+ # Check that channel is used if provided.
+ transport = transports.DocumentProcessorServiceGrpcAsyncIOTransport(
+ host="squid.clam.whelk", channel=channel,
+ )
+ assert transport.grpc_channel == channel
+ assert transport._host == "squid.clam.whelk:443"
+
+
+@pytest.mark.parametrize(
+ "transport_class",
+ [
+ transports.DocumentProcessorServiceGrpcTransport,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ ],
+)
+def test_document_processor_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-platform",),
+ ssl_credentials=mock_ssl_cred,
+ quota_project_id=None,
+ )
+ assert transport.grpc_channel == mock_grpc_channel
+
+
+@pytest.mark.parametrize(
+ "transport_class",
+ [
+ transports.DocumentProcessorServiceGrpcTransport,
+ transports.DocumentProcessorServiceGrpcAsyncIOTransport,
+ ],
+)
+def test_document_processor_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-platform",),
+ ssl_credentials=mock_ssl_cred,
+ quota_project_id=None,
+ )
+ assert transport.grpc_channel == mock_grpc_channel
+
+
+def test_document_processor_service_grpc_lro_client():
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(), transport="grpc",
+ )
+ transport = client._transport
+
+ # Ensure that we have a api-core operations client.
+ assert isinstance(transport.operations_client, operations_v1.OperationsClient,)
+
+ # Ensure that subsequent calls to the property send the exact same object.
+ assert transport.operations_client is transport.operations_client
+
+
+def test_document_processor_service_grpc_lro_async_client():
+ client = DocumentProcessorServiceAsyncClient(
+ credentials=credentials.AnonymousCredentials(), transport="grpc_asyncio",
+ )
+ transport = client._client._transport
+
+ # Ensure that we have a api-core operations client.
+ assert isinstance(transport.operations_client, operations_v1.OperationsAsyncClient,)
+
+ # Ensure that subsequent calls to the property send the exact same object.
+ assert transport.operations_client is transport.operations_client
+
+
+def test_client_withDEFAULT_CLIENT_INFO():
+ client_info = gapic_v1.client_info.ClientInfo()
+
+ with mock.patch.object(
+ transports.DocumentProcessorServiceTransport, "_prep_wrapped_messages"
+ ) as prep:
+ client = DocumentProcessorServiceClient(
+ credentials=credentials.AnonymousCredentials(), client_info=client_info,
+ )
+ prep.assert_called_once_with(client_info)
+
+ with mock.patch.object(
+ transports.DocumentProcessorServiceTransport, "_prep_wrapped_messages"
+ ) as prep:
+ transport_class = DocumentProcessorServiceClient.get_transport_class()
+ transport = transport_class(
+ credentials=credentials.AnonymousCredentials(), client_info=client_info,
+ )
+ prep.assert_called_once_with(client_info)