diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 65116f60..f8d4cbae 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,6 +4,9 @@ # For syntax help see: # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax +# The @googleapis/yoshi-python is the default owner for changes in this repo +* @googleapis/yoshi-python + # The python-samples-owners team is the default owner for samples /samples/**/*.py @telpirion @sirtorry @googleapis/python-samples-owners \ No newline at end of file diff --git a/.github/snippet-bot.yml b/.github/snippet-bot.yml new file mode 100644 index 00000000..e69de29b diff --git a/.kokoro/docs/common.cfg b/.kokoro/docs/common.cfg index 95963d11..180702da 100644 --- a/.kokoro/docs/common.cfg +++ b/.kokoro/docs/common.cfg @@ -30,7 +30,7 @@ env_vars: { env_vars: { key: "V2_STAGING_BUCKET" - value: "docs-staging-v2-staging" + value: "docs-staging-v2" } # It will upload the docker image after successful builds. diff --git a/.kokoro/populate-secrets.sh b/.kokoro/populate-secrets.sh new file mode 100755 index 00000000..f5251425 --- /dev/null +++ b/.kokoro/populate-secrets.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Copyright 2020 Google LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eo pipefail + +function now { date +"%Y-%m-%d %H:%M:%S" | tr -d '\n' ;} +function msg { println "$*" >&2 ;} +function println { printf '%s\n' "$(now) $*" ;} + + +# Populates requested secrets set in SECRET_MANAGER_KEYS from service account: +# kokoro-trampoline@cloud-devrel-kokoro-resources.iam.gserviceaccount.com +SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager" +msg "Creating folder on disk for secrets: ${SECRET_LOCATION}" +mkdir -p ${SECRET_LOCATION} +for key in $(echo ${SECRET_MANAGER_KEYS} | sed "s/,/ /g") +do + msg "Retrieving secret ${key}" + docker run --entrypoint=gcloud \ + --volume=${KOKORO_GFILE_DIR}:${KOKORO_GFILE_DIR} \ + gcr.io/google.com/cloudsdktool/cloud-sdk \ + secrets versions access latest \ + --project cloud-devrel-kokoro-resources \ + --secret ${key} > \ + "${SECRET_LOCATION}/${key}" + if [[ $? == 0 ]]; then + msg "Secret written to ${SECRET_LOCATION}/${key}" + else + msg "Error retrieving secret ${key}" + fi +done diff --git a/.kokoro/release/common.cfg b/.kokoro/release/common.cfg index ef255b9f..14180c27 100644 --- a/.kokoro/release/common.cfg +++ b/.kokoro/release/common.cfg @@ -23,42 +23,18 @@ env_vars: { value: "github/python-translate/.kokoro/release.sh" } -# Fetch the token needed for reporting release status to GitHub -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "yoshi-automation-github-key" - } - } -} - -# Fetch PyPI password -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "google_cloud_pypi_password" - } - } -} - -# Fetch magictoken to use with Magic Github Proxy -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "releasetool-magictoken" - } - } +# Fetch PyPI password +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "google_cloud_pypi_password" + } + } } -# Fetch api key to use with Magic Github Proxy -before_action { - fetch_keystore { - keystore_resource { - keystore_config_id: 73713 - keyname: "magic-github-proxy-api-key" - } - } -} +# Tokens needed to report release status back to GitHub +env_vars: { + key: "SECRET_MANAGER_KEYS" + value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" +} \ No newline at end of file diff --git a/.kokoro/samples/python3.6/common.cfg b/.kokoro/samples/python3.6/common.cfg index 0d5ec9ab..0afe4cf9 100644 --- a/.kokoro/samples/python3.6/common.cfg +++ b/.kokoro/samples/python3.6/common.cfg @@ -13,6 +13,12 @@ env_vars: { value: "py-3.6" } +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-py36" +} + env_vars: { key: "TRAMPOLINE_BUILD_FILE" value: "github/python-translate/.kokoro/test-samples.sh" diff --git a/.kokoro/samples/python3.7/common.cfg b/.kokoro/samples/python3.7/common.cfg index 59a17837..b82e68ae 100644 --- a/.kokoro/samples/python3.7/common.cfg +++ b/.kokoro/samples/python3.7/common.cfg @@ -13,6 +13,12 @@ env_vars: { value: "py-3.7" } +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-py37" +} + env_vars: { key: "TRAMPOLINE_BUILD_FILE" value: "github/python-translate/.kokoro/test-samples.sh" diff --git a/.kokoro/samples/python3.8/common.cfg b/.kokoro/samples/python3.8/common.cfg index 6cff86d8..9a34eac6 100644 --- a/.kokoro/samples/python3.8/common.cfg +++ b/.kokoro/samples/python3.8/common.cfg @@ -13,6 +13,12 @@ env_vars: { value: "py-3.8" } +# Declare build specific Cloud project. +env_vars: { + key: "BUILD_SPECIFIC_GCLOUD_PROJECT" + value: "python-docs-samples-tests-py38" +} + env_vars: { key: "TRAMPOLINE_BUILD_FILE" value: "github/python-translate/.kokoro/test-samples.sh" diff --git a/.kokoro/test-samples.sh b/.kokoro/test-samples.sh index e121af3e..6409c76b 100755 --- a/.kokoro/test-samples.sh +++ b/.kokoro/test-samples.sh @@ -28,6 +28,12 @@ if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"periodic"* ]]; then git checkout $LATEST_RELEASE fi +# Exit early if samples directory doesn't exist +if [ ! -d "./samples" ]; then + echo "No tests run. `./samples` not found" + exit 0 +fi + # Disable buffering, so that the logs stream through. export PYTHONUNBUFFERED=1 @@ -101,4 +107,4 @@ cd "$ROOT" # Workaround for Kokoro permissions issue: delete secrets rm testing/{test-env.sh,client-secrets.json,service-account.json} -exit "$RTN" \ No newline at end of file +exit "$RTN" diff --git a/.kokoro/trampoline.sh b/.kokoro/trampoline.sh index 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/CHANGELOG.md b/CHANGELOG.md index 11aa7cfa..e0644800 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ [1]: https://pypi.org/project/google-cloud-translate/#history +### [3.0.2](https://www.github.com/googleapis/python-translate/compare/v3.0.1...v3.0.2) (2020-12-09) + + +### Documentation + +* add w/ glossary and model ([1e030d4](https://www.github.com/googleapis/python-translate/commit/1e030d4557ee1f67bad5e5b4759d0200efd27afd)) + ### [3.0.1](https://www.github.com/googleapis/python-translate/compare/v3.0.0...v3.0.1) (2020-08-08) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b3d1f602..039f4368 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,44 +1,95 @@ -# Contributor Code of Conduct +# Code of Conduct -As contributors and maintainers of this project, -and in the interest of fostering an open and welcoming community, -we pledge to respect all people who contribute through reporting issues, -posting feature requests, updating documentation, -submitting pull requests or patches, and other activities. +## Our Pledge -We are committed to making participation in this project -a harassment-free experience for everyone, -regardless of level of experience, gender, gender identity and expression, -sexual orientation, disability, personal appearance, -body size, race, ethnicity, age, religion, or nationality. +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of +experience, education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery -* Personal attacks -* Trolling or insulting/derogatory comments -* Public or private harassment -* Publishing other's private information, -such as physical or electronic -addresses, without explicit permission -* Other unethical or unprofessional conduct. +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct. -By adopting this Code of Conduct, -project maintainers commit themselves to fairly and consistently -applying these principles to every aspect of managing this project. -Project maintainers who do not follow or enforce the Code of Conduct -may be permanently removed from the project team. - -This code of conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. - -Instances of abusive, harassing, or otherwise unacceptable behavior -may be reported by opening an issue -or contacting one or more of the project maintainers. - -This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, -available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +This Code of Conduct also applies outside the project spaces when the Project +Steward has a reasonable belief that an individual's behavior may have a +negative impact on the project or its community. + +## Conflict Resolution + +We do not believe that all conflict is bad; healthy debate and disagreement +often yield positive results. However, it is never okay to be disrespectful or +to engage in behavior that violates the project’s code of conduct. + +If you see someone violating the code of conduct, you are encouraged to address +the behavior directly with those involved. Many issues can be resolved quickly +and easily, and this gives people more control over the outcome of their +dispute. If you are unable to resolve the matter for any reason, or if the +behavior is threatening or harassing, report it. We are dedicated to providing +an environment where participants feel welcome and safe. + + +Reports should be directed to *googleapis-stewards@google.com*, the +Project Steward(s) for *Google Cloud Client Libraries*. It is the Project Steward’s duty to +receive and address reported violations of the code of conduct. They will then +work with a committee consisting of representatives from the Open Source +Programs Office and the Google Open Source Strategy team. If for any reason you +are uncomfortable reaching out to the Project Steward, please email +opensource@google.com. + +We will investigate every complaint, but you may not receive a direct response. +We will use our discretion in determining when and how to follow up on reported +incidents, which may range from not taking action to permanent expulsion from +the project and project-sponsored spaces. We will notify the accused of the +report and provide them an opportunity to discuss it before any action is taken. +The identity of the reporter will be omitted from the details of the report +supplied to the accused. In potentially harmful situations, such as ongoing +harassment or threats to anyone's safety, we may take action without notice. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 1.4, +available at +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 57ed8f60..51457137 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,7 +29,7 @@ # -- 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 @@ -39,6 +39,7 @@ "sphinx.ext.autosummary", "sphinx.ext.intersphinx", "sphinx.ext.coverage", + "sphinx.ext.doctest", "sphinx.ext.napoleon", "sphinx.ext.todo", "sphinx.ext.viewcode", diff --git a/google/cloud/translate_v3/services/translation_service/async_client.py b/google/cloud/translate_v3/services/translation_service/async_client.py index 9cb9b9d8..8d2c52b9 100644 --- a/google/cloud/translate_v3/services/translation_service/async_client.py +++ b/google/cloud/translate_v3/services/translation_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.translate_v3.services.translation_service import pagers from google.cloud.translate_v3.types import translation_service from google.protobuf import timestamp_pb2 as timestamp # type: ignore -from .transports.base import TranslationServiceTransport +from .transports.base import TranslationServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc_asyncio import TranslationServiceGrpcAsyncIOTransport from .client import TranslationServiceClient @@ -48,6 +48,7 @@ class TranslationServiceAsyncClient: DEFAULT_MTLS_ENDPOINT = TranslationServiceClient.DEFAULT_MTLS_ENDPOINT glossary_path = staticmethod(TranslationServiceClient.glossary_path) + parse_glossary_path = staticmethod(TranslationServiceClient.parse_glossary_path) from_service_account_file = TranslationServiceClient.from_service_account_file from_service_account_json = from_service_account_file @@ -63,6 +64,7 @@ def __init__( credentials: credentials.Credentials = None, transport: Union[str, TranslationServiceTransport] = "grpc_asyncio", client_options: ClientOptions = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the translation service client. @@ -78,16 +80,19 @@ def __init__( client_options (ClientOptions): Custom options for the client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the - default endpoint provided by the client. GOOGLE_API_USE_MTLS + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT environment variable can also be used to override the endpoint: "always" (always use the default mTLS endpoint), "never" (always - use the default regular endpoint, this is the default value for - the environment variable) and "auto" (auto switch to the default - mTLS endpoint if client SSL credentials is present). However, - the ``api_endpoint`` property takes precedence if provided. - (2) The ``client_cert_source`` property is used to provide client - SSL credentials for mutual TLS transport. If not provided, the - default SSL credentials will be used if present. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -95,7 +100,10 @@ def __init__( """ self._client = TranslationServiceClient( - credentials=credentials, transport=transport, client_options=client_options, + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, ) async def translate_text( @@ -248,7 +256,7 @@ async def translate_text( rpc = gapic_v1.method_async.wrap_method( self._client._transport.translate_text, default_timeout=600.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -368,7 +376,7 @@ async def detect_language( rpc = gapic_v1.method_async.wrap_method( self._client._transport.detect_language, default_timeout=600.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -494,7 +502,7 @@ async def get_supported_languages( ), ), default_timeout=600.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -558,7 +566,7 @@ async def batch_translate_text( rpc = gapic_v1.method_async.wrap_method( self._client._transport.batch_translate_text, default_timeout=600.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -647,7 +655,7 @@ async def create_glossary( rpc = gapic_v1.method_async.wrap_method( self._client._transport.create_glossary, default_timeout=600.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -737,7 +745,7 @@ async def list_glossaries( ), ), default_timeout=600.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -822,7 +830,7 @@ async def get_glossary( ), ), default_timeout=600.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -907,7 +915,7 @@ async def delete_glossary( ), ), default_timeout=600.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -932,11 +940,11 @@ async def delete_glossary( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-translate",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("TranslationServiceAsyncClient",) diff --git a/google/cloud/translate_v3/services/translation_service/client.py b/google/cloud/translate_v3/services/translation_service/client.py index d00cd12d..af612268 100644 --- a/google/cloud/translate_v3/services/translation_service/client.py +++ b/google/cloud/translate_v3/services/translation_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.translate_v3.services.translation_service import pagers from google.cloud.translate_v3.types import translation_service from google.protobuf import timestamp_pb2 as timestamp # type: ignore -from .transports.base import TranslationServiceTransport +from .transports.base import TranslationServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc import TranslationServiceGrpcTransport from .transports.grpc_asyncio import TranslationServiceGrpcAsyncIOTransport @@ -152,9 +154,10 @@ def parse_glossary_path(path: str) -> Dict[str, str]: def __init__( self, *, - credentials: credentials.Credentials = None, - transport: Union[str, TranslationServiceTransport] = None, - client_options: ClientOptions = None, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, TranslationServiceTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the translation service client. @@ -167,48 +170,74 @@ def __init__( transport (Union[str, ~.TranslationServiceTransport]): 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. @@ -232,11 +261,11 @@ def __init__( self._transport = Transport( credentials=credentials, credentials_file=client_options.credentials_file, - host=client_options.api_endpoint, + host=api_endpoint, scopes=client_options.scopes, - api_mtls_endpoint=client_options.api_endpoint, - client_cert_source=client_options.client_cert_source, + ssl_channel_credentials=ssl_credentials, quota_project_id=client_options.quota_project_id, + client_info=client_info, ) def translate_text( @@ -1056,11 +1085,11 @@ def delete_glossary( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-translate",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("TranslationServiceClient",) diff --git a/google/cloud/translate_v3/services/translation_service/transports/base.py b/google/cloud/translate_v3/services/translation_service/transports/base.py index ad022171..204e32ec 100644 --- a/google/cloud/translate_v3/services/translation_service/transports/base.py +++ b/google/cloud/translate_v3/services/translation_service/transports/base.py @@ -19,7 +19,7 @@ import typing import pkg_resources -from google import auth +from google import auth # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore @@ -31,11 +31,11 @@ try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-translate",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() class TranslationServiceTransport(abc.ABC): @@ -54,6 +54,7 @@ def __init__( 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. @@ -71,6 +72,11 @@ def __init__( scope (Optional[Sequence[str]]): A list of scopes. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. if ":" not in host: @@ -98,16 +104,16 @@ def __init__( self._credentials = credentials # Lifted into its own function so it can be stubbed out during tests. - self._prep_wrapped_messages() + self._prep_wrapped_messages(client_info) - def _prep_wrapped_messages(self): + def _prep_wrapped_messages(self, client_info): # Precompute the wrapped methods. self._wrapped_methods = { self.translate_text: gapic_v1.method.wrap_method( - self.translate_text, default_timeout=600.0, client_info=_client_info, + self.translate_text, default_timeout=600.0, client_info=client_info, ), self.detect_language: gapic_v1.method.wrap_method( - self.detect_language, default_timeout=600.0, client_info=_client_info, + self.detect_language, default_timeout=600.0, client_info=client_info, ), self.get_supported_languages: gapic_v1.method.wrap_method( self.get_supported_languages, @@ -120,15 +126,15 @@ def _prep_wrapped_messages(self): ), ), default_timeout=600.0, - client_info=_client_info, + client_info=client_info, ), self.batch_translate_text: gapic_v1.method.wrap_method( self.batch_translate_text, default_timeout=600.0, - client_info=_client_info, + client_info=client_info, ), self.create_glossary: gapic_v1.method.wrap_method( - self.create_glossary, default_timeout=600.0, client_info=_client_info, + self.create_glossary, default_timeout=600.0, client_info=client_info, ), self.list_glossaries: gapic_v1.method.wrap_method( self.list_glossaries, @@ -141,7 +147,7 @@ def _prep_wrapped_messages(self): ), ), default_timeout=600.0, - client_info=_client_info, + client_info=client_info, ), self.get_glossary: gapic_v1.method.wrap_method( self.get_glossary, @@ -154,7 +160,7 @@ def _prep_wrapped_messages(self): ), ), default_timeout=600.0, - client_info=_client_info, + client_info=client_info, ), self.delete_glossary: gapic_v1.method.wrap_method( self.delete_glossary, @@ -167,7 +173,7 @@ def _prep_wrapped_messages(self): ), ), default_timeout=600.0, - client_info=_client_info, + client_info=client_info, ), } diff --git a/google/cloud/translate_v3/services/translation_service/transports/grpc.py b/google/cloud/translate_v3/services/translation_service/transports/grpc.py index f8ee5ef5..09e06595 100644 --- a/google/cloud/translate_v3/services/translation_service/transports/grpc.py +++ b/google/cloud/translate_v3/services/translation_service/transports/grpc.py @@ -15,21 +15,22 @@ # limitations under the License. # +import warnings from typing import Callable, Dict, Optional, Sequence, Tuple from google.api_core import grpc_helpers # type: ignore from google.api_core import 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.translate_v3.types import translation_service from google.longrunning import operations_pb2 as operations # type: ignore -from .base import TranslationServiceTransport +from .base import TranslationServiceTransport, DEFAULT_CLIENT_INFO class TranslationServiceGrpcTransport(TranslationServiceTransport): @@ -57,7 +58,9 @@ def __init__( channel: grpc.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, - quota_project_id: Optional[str] = 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. @@ -76,16 +79,23 @@ def __init__( ignored if ``channel`` is provided. channel (Optional[grpc.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport @@ -101,6 +111,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 @@ -131,6 +146,23 @@ def __init__( 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] @@ -141,6 +173,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) @classmethod @@ -151,7 +184,7 @@ def create_channel( credentials_file: str = None, scopes: Optional[Sequence[str]] = None, quota_project_id: Optional[str] = None, - **kwargs + **kwargs, ) -> grpc.Channel: """Create and return a gRPC channel object. Args: @@ -185,7 +218,7 @@ def create_channel( credentials_file=credentials_file, scopes=scopes, quota_project_id=quota_project_id, - **kwargs + **kwargs, ) @property @@ -195,13 +228,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 diff --git a/google/cloud/translate_v3/services/translation_service/transports/grpc_asyncio.py b/google/cloud/translate_v3/services/translation_service/transports/grpc_asyncio.py index 7fb7d890..e7e9c05c 100644 --- a/google/cloud/translate_v3/services/translation_service/transports/grpc_asyncio.py +++ b/google/cloud/translate_v3/services/translation_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 @@ -28,7 +31,7 @@ from google.cloud.translate_v3.types import translation_service from google.longrunning import operations_pb2 as operations # type: ignore -from .base import TranslationServiceTransport +from .base import TranslationServiceTransport, DEFAULT_CLIENT_INFO from .grpc import TranslationServiceGrpcTransport @@ -99,7 +102,9 @@ def __init__( channel: aio.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, quota_project_id=None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the transport. @@ -119,16 +124,23 @@ def __init__( are passed to :func:`google.auth.default`. channel (Optional[aio.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -144,12 +156,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: @@ -169,6 +191,23 @@ def __init__( 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__( @@ -177,6 +216,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) self._stubs = {} @@ -188,13 +228,6 @@ def grpc_channel(self) -> aio.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/translate_v3beta1/services/translation_service/async_client.py b/google/cloud/translate_v3beta1/services/translation_service/async_client.py index fe0e508b..9054a09b 100644 --- a/google/cloud/translate_v3beta1/services/translation_service/async_client.py +++ b/google/cloud/translate_v3beta1/services/translation_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.translate_v3beta1.services.translation_service import pagers from google.cloud.translate_v3beta1.types import translation_service from google.protobuf import timestamp_pb2 as timestamp # type: ignore -from .transports.base import TranslationServiceTransport +from .transports.base import TranslationServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc_asyncio import TranslationServiceGrpcAsyncIOTransport from .client import TranslationServiceClient @@ -48,6 +48,7 @@ class TranslationServiceAsyncClient: DEFAULT_MTLS_ENDPOINT = TranslationServiceClient.DEFAULT_MTLS_ENDPOINT glossary_path = staticmethod(TranslationServiceClient.glossary_path) + parse_glossary_path = staticmethod(TranslationServiceClient.parse_glossary_path) from_service_account_file = TranslationServiceClient.from_service_account_file from_service_account_json = from_service_account_file @@ -63,6 +64,7 @@ def __init__( credentials: credentials.Credentials = None, transport: Union[str, TranslationServiceTransport] = "grpc_asyncio", client_options: ClientOptions = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the translation service client. @@ -78,16 +80,19 @@ def __init__( client_options (ClientOptions): Custom options for the client. It won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the - default endpoint provided by the client. GOOGLE_API_USE_MTLS + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT environment variable can also be used to override the endpoint: "always" (always use the default mTLS endpoint), "never" (always - use the default regular endpoint, this is the default value for - the environment variable) and "auto" (auto switch to the default - mTLS endpoint if client SSL credentials is present). However, - the ``api_endpoint`` property takes precedence if provided. - (2) The ``client_cert_source`` property is used to provide client - SSL credentials for mutual TLS transport. If not provided, the - default SSL credentials will be used if present. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -95,7 +100,10 @@ def __init__( """ self._client = TranslationServiceClient( - credentials=credentials, transport=transport, client_options=client_options, + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, ) async def translate_text( @@ -132,7 +140,7 @@ async def translate_text( rpc = gapic_v1.method_async.wrap_method( self._client._transport.translate_text, default_timeout=600.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -243,7 +251,7 @@ async def detect_language( rpc = gapic_v1.method_async.wrap_method( self._client._transport.detect_language, default_timeout=600.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -365,11 +373,11 @@ async def get_supported_languages( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, ), ), default_timeout=600.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -433,7 +441,7 @@ async def batch_translate_text( rpc = gapic_v1.method_async.wrap_method( self._client._transport.batch_translate_text, default_timeout=600.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -522,7 +530,7 @@ async def create_glossary( rpc = gapic_v1.method_async.wrap_method( self._client._transport.create_glossary, default_timeout=600.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -620,11 +628,11 @@ async def list_glossaries( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, ), ), default_timeout=600.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -705,11 +713,11 @@ async def get_glossary( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, ), ), default_timeout=600.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -790,11 +798,11 @@ async def delete_glossary( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, ), ), default_timeout=600.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -819,11 +827,11 @@ async def delete_glossary( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-translate",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("TranslationServiceAsyncClient",) diff --git a/google/cloud/translate_v3beta1/services/translation_service/client.py b/google/cloud/translate_v3beta1/services/translation_service/client.py index 6d991415..47a82cca 100644 --- a/google/cloud/translate_v3beta1/services/translation_service/client.py +++ b/google/cloud/translate_v3beta1/services/translation_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.translate_v3beta1.services.translation_service import pagers from google.cloud.translate_v3beta1.types import translation_service from google.protobuf import timestamp_pb2 as timestamp # type: ignore -from .transports.base import TranslationServiceTransport +from .transports.base import TranslationServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc import TranslationServiceGrpcTransport from .transports.grpc_asyncio import TranslationServiceGrpcAsyncIOTransport @@ -152,9 +154,10 @@ def parse_glossary_path(path: str) -> Dict[str, str]: def __init__( self, *, - credentials: credentials.Credentials = None, - transport: Union[str, TranslationServiceTransport] = None, - client_options: ClientOptions = None, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, TranslationServiceTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the translation service client. @@ -167,48 +170,74 @@ def __init__( transport (Union[str, ~.TranslationServiceTransport]): 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. @@ -232,11 +261,11 @@ def __init__( self._transport = Transport( credentials=credentials, credentials_file=client_options.credentials_file, - host=client_options.api_endpoint, + host=api_endpoint, scopes=client_options.scopes, - api_mtls_endpoint=client_options.api_endpoint, - client_cert_source=client_options.client_cert_source, + ssl_channel_credentials=ssl_credentials, quota_project_id=client_options.quota_project_id, + client_info=client_info, ) def translate_text( @@ -942,11 +971,11 @@ def delete_glossary( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-translate",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("TranslationServiceClient",) diff --git a/google/cloud/translate_v3beta1/services/translation_service/transports/base.py b/google/cloud/translate_v3beta1/services/translation_service/transports/base.py index b532a056..7c9f6810 100644 --- a/google/cloud/translate_v3beta1/services/translation_service/transports/base.py +++ b/google/cloud/translate_v3beta1/services/translation_service/transports/base.py @@ -19,7 +19,7 @@ import typing import pkg_resources -from google import auth +from google import auth # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore @@ -31,11 +31,11 @@ try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-translate",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() class TranslationServiceTransport(abc.ABC): @@ -54,6 +54,7 @@ def __init__( 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. @@ -71,6 +72,11 @@ def __init__( scope (Optional[Sequence[str]]): A list of scopes. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. if ":" not in host: @@ -98,16 +104,16 @@ def __init__( self._credentials = credentials # Lifted into its own function so it can be stubbed out during tests. - self._prep_wrapped_messages() + self._prep_wrapped_messages(client_info) - def _prep_wrapped_messages(self): + def _prep_wrapped_messages(self, client_info): # Precompute the wrapped methods. self._wrapped_methods = { self.translate_text: gapic_v1.method.wrap_method( - self.translate_text, default_timeout=600.0, client_info=_client_info, + self.translate_text, default_timeout=600.0, client_info=client_info, ), self.detect_language: gapic_v1.method.wrap_method( - self.detect_language, default_timeout=600.0, client_info=_client_info, + self.detect_language, default_timeout=600.0, client_info=client_info, ), self.get_supported_languages: gapic_v1.method.wrap_method( self.get_supported_languages, @@ -116,19 +122,19 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, ), ), default_timeout=600.0, - client_info=_client_info, + client_info=client_info, ), self.batch_translate_text: gapic_v1.method.wrap_method( self.batch_translate_text, default_timeout=600.0, - client_info=_client_info, + client_info=client_info, ), self.create_glossary: gapic_v1.method.wrap_method( - self.create_glossary, default_timeout=600.0, client_info=_client_info, + self.create_glossary, default_timeout=600.0, client_info=client_info, ), self.list_glossaries: gapic_v1.method.wrap_method( self.list_glossaries, @@ -137,11 +143,11 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, ), ), default_timeout=600.0, - client_info=_client_info, + client_info=client_info, ), self.get_glossary: gapic_v1.method.wrap_method( self.get_glossary, @@ -150,11 +156,11 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, ), ), default_timeout=600.0, - client_info=_client_info, + client_info=client_info, ), self.delete_glossary: gapic_v1.method.wrap_method( self.delete_glossary, @@ -163,11 +169,11 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, ), ), default_timeout=600.0, - client_info=_client_info, + client_info=client_info, ), } diff --git a/google/cloud/translate_v3beta1/services/translation_service/transports/grpc.py b/google/cloud/translate_v3beta1/services/translation_service/transports/grpc.py index f6dadf28..88882482 100644 --- a/google/cloud/translate_v3beta1/services/translation_service/transports/grpc.py +++ b/google/cloud/translate_v3beta1/services/translation_service/transports/grpc.py @@ -15,21 +15,22 @@ # limitations under the License. # +import warnings from typing import Callable, Dict, Optional, Sequence, Tuple from google.api_core import grpc_helpers # type: ignore from google.api_core import 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.translate_v3beta1.types import translation_service from google.longrunning import operations_pb2 as operations # type: ignore -from .base import TranslationServiceTransport +from .base import TranslationServiceTransport, DEFAULT_CLIENT_INFO class TranslationServiceGrpcTransport(TranslationServiceTransport): @@ -57,7 +58,9 @@ def __init__( channel: grpc.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, - quota_project_id: Optional[str] = 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. @@ -76,16 +79,23 @@ def __init__( ignored if ``channel`` is provided. channel (Optional[grpc.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport @@ -101,6 +111,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 @@ -131,6 +146,23 @@ def __init__( 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] @@ -141,6 +173,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) @classmethod @@ -151,7 +184,7 @@ def create_channel( credentials_file: str = None, scopes: Optional[Sequence[str]] = None, quota_project_id: Optional[str] = None, - **kwargs + **kwargs, ) -> grpc.Channel: """Create and return a gRPC channel object. Args: @@ -185,7 +218,7 @@ def create_channel( credentials_file=credentials_file, scopes=scopes, quota_project_id=quota_project_id, - **kwargs + **kwargs, ) @property @@ -195,13 +228,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 diff --git a/google/cloud/translate_v3beta1/services/translation_service/transports/grpc_asyncio.py b/google/cloud/translate_v3beta1/services/translation_service/transports/grpc_asyncio.py index e7f63e99..883c06a6 100644 --- a/google/cloud/translate_v3beta1/services/translation_service/transports/grpc_asyncio.py +++ b/google/cloud/translate_v3beta1/services/translation_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 @@ -28,7 +31,7 @@ from google.cloud.translate_v3beta1.types import translation_service from google.longrunning import operations_pb2 as operations # type: ignore -from .base import TranslationServiceTransport +from .base import TranslationServiceTransport, DEFAULT_CLIENT_INFO from .grpc import TranslationServiceGrpcTransport @@ -99,7 +102,9 @@ def __init__( channel: aio.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, quota_project_id=None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the transport. @@ -119,16 +124,23 @@ def __init__( are passed to :func:`google.auth.default`. channel (Optional[aio.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -144,12 +156,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: @@ -169,6 +191,23 @@ def __init__( 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__( @@ -177,6 +216,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) self._stubs = {} @@ -188,13 +228,6 @@ def grpc_channel(self) -> aio.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/noxfile.py b/noxfile.py index 86bba293..845cd5f9 100644 --- a/noxfile.py +++ b/noxfile.py @@ -173,7 +173,9 @@ def docfx(session): """Build the docfx yaml files for this library.""" session.install("-e", ".") - session.install("sphinx", "alabaster", "recommonmark", "sphinx-docfx-yaml") + # 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( diff --git a/samples/snippets/beta_snippets.py b/samples/snippets/beta_snippets.py index 10ec2dc6..6b770e61 100644 --- a/samples/snippets/beta_snippets.py +++ b/samples/snippets/beta_snippets.py @@ -19,11 +19,12 @@ def translate_text(project_id, text): # [START translate_translate_text_beta] from google.cloud import translate_v3beta1 as translate + client = translate.TranslationServiceClient() # project_id = YOUR_PROJECT_ID # text = 'Text you wish to translate' - location = 'global' + location = "global" parent = f"projects/{project_id}/locations/{location}" @@ -33,38 +34,38 @@ def translate_text(project_id, text): "contents": [text], "mime_type": "text/plain", # mime types: text/plain, text/html "source_language_code": "en-US", - "target_language_code": "sr-Latn" + "target_language_code": "sr-Latn", } ) for translation in response.translations: - print(u'Translated Text: {}'.format(translation)) + print("Translated Text: {}".format(translation)) # [END translate_translate_text_beta] def batch_translate_text(project_id, input_uri, output_uri): # [START translate_batch_translate_text_beta] from google.cloud import translate_v3beta1 as translate + client = translate.TranslationServiceClient() # project_id = YOUR_PROJECT_ID # input_uri = 'gs://cloud-samples-data/translation/text.txt' # output_uri = 'gs://YOUR_BUCKET_ID/path_to_store_results/' - location = 'us-central1' + location = "us-central1" parent = f"projects/{project_id}/locations/{location}" gcs_source = translate.types.GcsSource(input_uri=input_uri) input_config = translate.types.InputConfig( - mime_type='text/plain', # mime types: text/plain, text/html - gcs_source=gcs_source) + mime_type="text/plain", # mime types: text/plain, text/html + gcs_source=gcs_source, + ) - gcs_destination = translate.types.GcsDestination( - output_uri_prefix=output_uri) + gcs_destination = translate.types.GcsDestination(output_uri_prefix=output_uri) - output_config = translate.types.OutputConfig( - gcs_destination=gcs_destination) + output_config = translate.types.OutputConfig(gcs_destination=gcs_destination) operation = client.batch_translate_text( request={ @@ -72,25 +73,26 @@ def batch_translate_text(project_id, input_uri, output_uri): "source_language_code": "en-US", "target_language_codes": ["sr-Latn"], "input_configs": [input_config], - "output_config": output_config + "output_config": output_config, } ) - result = operation.result(timeout=240) + result = operation.result(timeout=320) - print(u'Total Characters: {}'.format(result.total_characters)) - print(u'Translated Characters: {}'.format(result.translated_characters)) + print("Total Characters: {}".format(result.total_characters)) + print("Translated Characters: {}".format(result.translated_characters)) # [END translate_batch_translate_text_beta] def detect_language(project_id, text): # [START translate_detect_language_beta] from google.cloud import translate_v3beta1 as translate + client = translate.TranslationServiceClient() # project_id = YOUR_PROJECT_ID # text = 'Text you wish to translate' - location = 'global' + location = "global" parent = f"projects/{project_id}/locations/{location}" @@ -98,176 +100,178 @@ def detect_language(project_id, text): request={ "parent": parent, "content": text, - "mime_type": "text/plain" # mime types: text/plain, text/html + "mime_type": "text/plain", # mime types: text/plain, text/html } ) for language in response.languages: - print(u'Language Code: {} (Confidence: {})'.format( - language.language_code, - language.confidence)) + print( + "Language Code: {} (Confidence: {})".format( + language.language_code, language.confidence + ) + ) # [END translate_detect_language_beta] def list_languages(project_id): # [START translate_list_codes_beta] from google.cloud import translate_v3beta1 as translate + client = translate.TranslationServiceClient() # project_id = YOUR_PROJECT_ID - location = 'global' + location = "global" parent = f"projects/{project_id}/locations/{location}" response = client.get_supported_languages(parent=parent) - print('Supported Languages:') + print("Supported Languages:") for language in response.languages: - print(u'Language Code: {}'.format(language.language_code)) + print("Language Code: {}".format(language.language_code)) # [END translate_list_codes_beta] def list_languages_with_target(project_id, display_language_code): # [START translate_list_language_names_beta] from google.cloud import translate_v3beta1 as translate + client = translate.TranslationServiceClient() # project_id = YOUR_PROJECT_ID # display_language_code = 'is' - location = 'global' + location = "global" parent = f"projects/{project_id}/locations/{location}" response = client.get_supported_languages( - parent=parent, - display_language_code=display_language_code) + parent=parent, display_language_code=display_language_code + ) - print('Supported Languages:') + print("Supported Languages:") for language in response.languages: - print(u'Language Code: {}'.format(language.language_code)) - print(u'Display Name: {}\n'.format(language.display_name)) + print("Language Code: {}".format(language.language_code)) + print("Display Name: {}\n".format(language.display_name)) # [END translate_list_language_names_beta] def create_glossary(project_id, glossary_id): # [START translate_create_glossary_beta] from google.cloud import translate_v3beta1 as translate + client = translate.TranslationServiceClient() # project_id = 'YOUR_PROJECT_ID' # glossary_id = 'glossary-id' - location = 'us-central1' # The location of the glossary + location = "us-central1" # The location of the glossary - name = client.glossary_path( - project_id, - location, - glossary_id) + name = client.glossary_path(project_id, location, glossary_id) language_codes_set = translate.types.Glossary.LanguageCodesSet( - language_codes=['en', 'es']) + language_codes=["en", "es"] + ) gcs_source = translate.types.GcsSource( - input_uri='gs://cloud-samples-data/translation/glossary.csv') + input_uri="gs://cloud-samples-data/translation/glossary.csv" + ) - input_config = translate.types.GlossaryInputConfig( - gcs_source=gcs_source) + input_config = translate.types.GlossaryInputConfig(gcs_source=gcs_source) glossary = translate.types.Glossary( - name=name, - language_codes_set=language_codes_set, - input_config=input_config) + name=name, language_codes_set=language_codes_set, input_config=input_config + ) parent = f"projects/{project_id}/locations/{location}" operation = client.create_glossary(parent=parent, glossary=glossary) result = operation.result(timeout=90) - print(u'Created: {}'.format(result.name)) - print(u'Input Uri: {}'.format(result.input_config.gcs_source.input_uri)) + print("Created: {}".format(result.name)) + print("Input Uri: {}".format(result.input_config.gcs_source.input_uri)) # [END translate_create_glossary_beta] def list_glossaries(project_id): # [START translate_list_glossary_beta] from google.cloud import translate_v3beta1 as translate + client = translate.TranslationServiceClient() # project_id = 'YOUR_PROJECT_ID' - location = 'us-central1' # The location of the glossary + location = "us-central1" # The location of the glossary parent = f"projects/{project_id}/locations/{location}" for glossary in client.list_glossaries(parent=parent): - print(u'Name: {}'.format(glossary.name)) - print(u'Entry count: {}'.format(glossary.entry_count)) - print(u'Input uri: {}'.format( - glossary.input_config.gcs_source.input_uri)) + print("Name: {}".format(glossary.name)) + print("Entry count: {}".format(glossary.entry_count)) + print("Input uri: {}".format(glossary.input_config.gcs_source.input_uri)) for language_code in glossary.language_codes_set.language_codes: - print(u'Language code: {}'.format(language_code)) + print("Language code: {}".format(language_code)) # [END translate_list_glossary_beta] def get_glossary(project_id, glossary_id): # [START translate_get_glossary_beta] from google.cloud import translate_v3beta1 as translate + client = translate.TranslationServiceClient() # project_id = 'YOUR_PROJECT_ID' # glossary_id = 'GLOSSARY_ID' name = client.glossary_path( - project_id, - 'us-central1', # The location of the glossary - glossary_id) + project_id, "us-central1", glossary_id # The location of the glossary + ) response = client.get_glossary(name=name) - print(u'Name: {}'.format(response.name)) - print(u'Language Pair:') - print(u'\tSource Language Code: {}'.format( - response.language_pair.source_language_code)) - print(u'\tTarget Language Code: {}'.format( - response.language_pair.target_language_code)) - print(u'Input Uri: {}'.format( - response.input_config.gcs_source.input_uri)) + print("Name: {}".format(response.name)) + print("Language Pair:") + print( + "\tSource Language Code: {}".format(response.language_pair.source_language_code) + ) + print( + "\tTarget Language Code: {}".format(response.language_pair.target_language_code) + ) + print("Input Uri: {}".format(response.input_config.gcs_source.input_uri)) # [END translate_get_glossary_beta] def delete_glossary(project_id, glossary_id): # [START translate_delete_glossary_beta] from google.cloud import translate_v3beta1 as translate + client = translate.TranslationServiceClient() # project_id = 'YOUR_PROJECT_ID' # glossary_id = 'GLOSSARY_ID' name = client.glossary_path( - project_id, - 'us-central1', # The location of the glossary - glossary_id) + project_id, "us-central1", glossary_id # The location of the glossary + ) operation = client.delete_glossary(name=name) result = operation.result(timeout=90) - print(u'Deleted: {}'.format(result.name)) + print("Deleted: {}".format(result.name)) # [END translate_delete_glossary_beta] def translate_text_with_glossary(project_id, glossary_id, text): # [START translate_translate_text_with_glossary_beta] from google.cloud import translate_v3beta1 as translate + client = translate.TranslationServiceClient() # project_id = 'YOUR_PROJECT_ID' # glossary_id = 'GLOSSARY_ID' # text = 'Text you wish to translate' - location = 'us-central1' # The location of the glossary + location = "us-central1" # The location of the glossary glossary = client.glossary_path( - project_id, - 'us-central1', # The location of the glossary - glossary_id) + project_id, "us-central1", glossary_id # The location of the glossary + ) - glossary_config = translate.types.TranslateTextGlossaryConfig( - glossary=glossary) + glossary_config = translate.types.TranslateTextGlossaryConfig(glossary=glossary) parent = f"projects/{project_id}/locations/{location}" @@ -278,7 +282,7 @@ def translate_text_with_glossary(project_id, glossary_id, text): "mime_type": "text/plain", "source_language_code": "en", "target_language_code": "es", - "glossary_config": glossary_config + "glossary_config": glossary_config, } ) @@ -287,84 +291,92 @@ def translate_text_with_glossary(project_id, glossary_id, text): # [END translate_translate_text_with_glossary_beta] -if __name__ == '__main__': +if __name__ == "__main__": parser = argparse.ArgumentParser( - description=__doc__, - formatter_class=argparse.RawDescriptionHelpFormatter) + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) - subparsers = parser.add_subparsers(dest='command') + subparsers = parser.add_subparsers(dest="command") translate_text_parser = subparsers.add_parser( - 'translate-text', help=translate_text.__doc__) - translate_text_parser.add_argument('project_id') - translate_text_parser.add_argument('text') + "translate-text", help=translate_text.__doc__ + ) + translate_text_parser.add_argument("project_id") + translate_text_parser.add_argument("text") batch_translate_text_parser = subparsers.add_parser( - 'batch-translate-text', help=translate_text.__doc__) - batch_translate_text_parser.add_argument('project_id') - batch_translate_text_parser.add_argument('gcs_source') - batch_translate_text_parser.add_argument('gcs_destination') + "batch-translate-text", help=translate_text.__doc__ + ) + batch_translate_text_parser.add_argument("project_id") + batch_translate_text_parser.add_argument("gcs_source") + batch_translate_text_parser.add_argument("gcs_destination") detect_langage_parser = subparsers.add_parser( - 'detect-language', help=detect_language.__doc__) - detect_langage_parser.add_argument('project_id') - detect_langage_parser.add_argument('text') + "detect-language", help=detect_language.__doc__ + ) + detect_langage_parser.add_argument("project_id") + detect_langage_parser.add_argument("text") list_languages_parser = subparsers.add_parser( - 'list-languages', help=list_languages.__doc__) - list_languages_parser.add_argument('project_id') + "list-languages", help=list_languages.__doc__ + ) + list_languages_parser.add_argument("project_id") list_languages_with_target_parser = subparsers.add_parser( - 'list-languages-with-target', help=list_languages_with_target.__doc__) - list_languages_with_target_parser.add_argument('project_id') - list_languages_with_target_parser.add_argument('display_language_code') + "list-languages-with-target", help=list_languages_with_target.__doc__ + ) + list_languages_with_target_parser.add_argument("project_id") + list_languages_with_target_parser.add_argument("display_language_code") create_glossary_parser = subparsers.add_parser( - 'create-glossary', help=create_glossary.__doc__) - create_glossary_parser.add_argument('project_id') - create_glossary_parser.add_argument('glossary_id') + "create-glossary", help=create_glossary.__doc__ + ) + create_glossary_parser.add_argument("project_id") + create_glossary_parser.add_argument("glossary_id") get_glossary_parser = subparsers.add_parser( - 'get-glossary', help=get_glossary.__doc__) - get_glossary_parser.add_argument('project_id') - get_glossary_parser.add_argument('glossary_id') + "get-glossary", help=get_glossary.__doc__ + ) + get_glossary_parser.add_argument("project_id") + get_glossary_parser.add_argument("glossary_id") list_glossary_parser = subparsers.add_parser( - 'list-glossaries', help=list_glossaries.__doc__) - list_glossary_parser.add_argument('project_id') + "list-glossaries", help=list_glossaries.__doc__ + ) + list_glossary_parser.add_argument("project_id") delete_glossary_parser = subparsers.add_parser( - 'delete-glossary', help=delete_glossary.__doc__) - delete_glossary_parser.add_argument('project_id') - delete_glossary_parser.add_argument('glossary_id') + "delete-glossary", help=delete_glossary.__doc__ + ) + delete_glossary_parser.add_argument("project_id") + delete_glossary_parser.add_argument("glossary_id") translate_with_glossary_parser = subparsers.add_parser( - 'translate-with-glossary', help=translate_text_with_glossary.__doc__) - translate_with_glossary_parser.add_argument('project_id') - translate_with_glossary_parser.add_argument('glossary_id') - translate_with_glossary_parser.add_argument('text') + "translate-with-glossary", help=translate_text_with_glossary.__doc__ + ) + translate_with_glossary_parser.add_argument("project_id") + translate_with_glossary_parser.add_argument("glossary_id") + translate_with_glossary_parser.add_argument("text") args = parser.parse_args() - if args.command == 'translate-text': + if args.command == "translate-text": translate_text(args.project_id, args.text) - elif args.command == 'batch-translate-text': - batch_translate_text( - args.project_id, args.gcs_source, args.gcs_destination) - elif args.command == 'detect-language': + elif args.command == "batch-translate-text": + batch_translate_text(args.project_id, args.gcs_source, args.gcs_destination) + elif args.command == "detect-language": detect_language(args.project_id, args.text) - elif args.command == 'list-languages': + elif args.command == "list-languages": list_languages(args.project_id) - elif args.command == 'list-languages-with-target': + elif args.command == "list-languages-with-target": list_languages_with_target(args.project_id, args.display_language_code) - elif args.command == 'create-glossary': + elif args.command == "create-glossary": create_glossary(args.project_id, args.glossary_id) - elif args.command == 'get-glossary': + elif args.command == "get-glossary": get_glossary(args.project_id, args.glossary_id) - elif args.command == 'list-glossaries': + elif args.command == "list-glossaries": list_glossaries(args.project_id) - elif args.command == 'delete-glossary': + elif args.command == "delete-glossary": delete_glossary(args.project_id, args.glossary_id) - elif args.command == 'translate-with-glossary': - translate_text_with_glossary( - args.project_id, args.glossary_id, args.text) + elif args.command == "translate-with-glossary": + translate_text_with_glossary(args.project_id, args.glossary_id, args.text) diff --git a/samples/snippets/beta_snippets_test.py b/samples/snippets/beta_snippets_test.py index a42ab08c..7e0c2dc6 100644 --- a/samples/snippets/beta_snippets_test.py +++ b/samples/snippets/beta_snippets_test.py @@ -22,13 +22,13 @@ import beta_snippets -PROJECT_ID = os.environ['GOOGLE_CLOUD_PROJECT'] +PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"] -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def bucket(): """Create a temporary bucket to store annotation output.""" - bucket_name = f'tmp-{uuid.uuid4().hex}' + bucket_name = f"tmp-{uuid.uuid4().hex}" storage_client = storage.Client() bucket = storage_client.create_bucket(bucket_name) @@ -37,10 +37,10 @@ def bucket(): bucket.delete(force=True) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def glossary(): """Get the ID of a glossary available to session (do not mutate/delete).""" - glossary_id = 'must-start-with-letters-' + str(uuid.uuid1()) + glossary_id = "must-start-with-letters-" + str(uuid.uuid1()) beta_snippets.create_glossary(PROJECT_ID, glossary_id) yield glossary_id @@ -51,10 +51,10 @@ def glossary(): pass -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def unique_glossary_id(): """Get a unique ID. Attempts to delete glossary with this ID after test.""" - glossary_id = 'must-start-with-letters-' + str(uuid.uuid1()) + glossary_id = "must-start-with-letters-" + str(uuid.uuid1()) yield glossary_id @@ -65,69 +65,69 @@ def unique_glossary_id(): def test_translate_text(capsys): - beta_snippets.translate_text(PROJECT_ID, 'Hello world') + beta_snippets.translate_text(PROJECT_ID, "Hello world") out, _ = capsys.readouterr() - assert 'Translated Text:' in out + assert "Translated Text:" in out @pytest.mark.flaky(max_runs=3, min_passes=1) def test_batch_translate_text(capsys, bucket): beta_snippets.batch_translate_text( PROJECT_ID, - 'gs://cloud-samples-data/translation/text.txt', - 'gs://{}/translation/BATCH_TRANSLATION_OUTPUT/'.format(bucket.name)) + "gs://cloud-samples-data/translation/text.txt", + "gs://{}/translation/BATCH_TRANSLATION_OUTPUT/".format(bucket.name), + ) out, _ = capsys.readouterr() - assert 'Total Characters: 13' in out - assert 'Translated Characters: 13' in out + assert "Total Characters: 13" in out + assert "Translated Characters: 13" in out def test_detect_language(capsys): - beta_snippets.detect_language(PROJECT_ID, u'Hæ sæta') + beta_snippets.detect_language(PROJECT_ID, "Hæ sæta") out, _ = capsys.readouterr() - assert 'is' in out + assert "is" in out def test_list_languages(capsys): beta_snippets.list_languages(PROJECT_ID) out, _ = capsys.readouterr() - assert 'zh-CN' in out + assert "zh-CN" in out def test_list_languages_with_target(capsys): - beta_snippets.list_languages_with_target(PROJECT_ID, 'is') + beta_snippets.list_languages_with_target(PROJECT_ID, "is") out, _ = capsys.readouterr() - assert u'Language Code: sq' in out - assert u'Display Name: albanska' in out + assert "Language Code: sq" in out + assert "Display Name: albanska" in out @pytest.mark.flaky(max_runs=3, min_passes=1) def test_create_glossary(capsys, unique_glossary_id): beta_snippets.create_glossary(PROJECT_ID, unique_glossary_id) out, _ = capsys.readouterr() - assert 'Created' in out + assert "Created" in out assert unique_glossary_id in out - assert 'gs://cloud-samples-data/translation/glossary.csv' in out + assert "gs://cloud-samples-data/translation/glossary.csv" in out def test_get_glossary(capsys, glossary): beta_snippets.get_glossary(PROJECT_ID, glossary) out, _ = capsys.readouterr() assert glossary in out - assert 'gs://cloud-samples-data/translation/glossary.csv' in out + assert "gs://cloud-samples-data/translation/glossary.csv" in out def test_list_glossary(capsys, glossary): beta_snippets.list_glossaries(PROJECT_ID) out, _ = capsys.readouterr() assert glossary in out - assert 'gs://cloud-samples-data/translation/glossary.csv' in out + assert "gs://cloud-samples-data/translation/glossary.csv" in out def test_translate_text_with_glossary(capsys, glossary): - beta_snippets.translate_text_with_glossary( - PROJECT_ID, glossary, 'account') + beta_snippets.translate_text_with_glossary(PROJECT_ID, glossary, "account") out, _ = capsys.readouterr() - assert 'cuenta' in out + assert "cuenta" in out @pytest.mark.flaky(max_runs=3, min_passes=1) @@ -135,5 +135,5 @@ def test_delete_glossary(capsys, unique_glossary_id): beta_snippets.create_glossary(PROJECT_ID, unique_glossary_id) beta_snippets.delete_glossary(PROJECT_ID, unique_glossary_id) out, _ = capsys.readouterr() - assert 'us-central1' in out + assert "us-central1" in out assert unique_glossary_id in out diff --git a/samples/snippets/hybrid_glossaries/hybrid_tutorial.py b/samples/snippets/hybrid_glossaries/hybrid_tutorial.py index c4a84345..9eae168a 100644 --- a/samples/snippets/hybrid_glossaries/hybrid_tutorial.py +++ b/samples/snippets/hybrid_glossaries/hybrid_tutorial.py @@ -23,12 +23,13 @@ from google.cloud import texttospeech from google.cloud import translate_v3beta1 as translate from google.cloud import vision + # [END translate_hybrid_imports] # [START translate_hybrid_project_id] # extract GCP project id -PROJECT_ID = os.environ['GOOGLE_CLOUD_PROJECT'] +PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"] # [END translate_hybrid_project_id] @@ -47,15 +48,16 @@ def pic_to_text(infile): client = vision.ImageAnnotatorClient() # Opens the input image file - with io.open(infile, 'rb') as image_file: + with io.open(infile, "rb") as image_file: content = image_file.read() - image = vision.types.Image(content=content) + image = vision.Image(content=content) # For dense text, use document_text_detection # For less dense text, use text_detection response = client.document_text_detection(image=image) text = response.full_text_annotation.text + print("Detected text: {}".format(text)) return text # [END translate_hybrid_vision] @@ -80,29 +82,24 @@ def create_glossary(languages, project_id, glossary_name, glossary_uri): client = translate.TranslationServiceClient() # Designates the data center location that you want to use - location = 'us-central1' + location = "us-central1" # Set glossary resource name - name = client.glossary_path( - project_id, - location, - glossary_name) + name = client.glossary_path(project_id, location, glossary_name) # Set language codes - language_codes_set = translate.types.Glossary.LanguageCodesSet( - language_codes=languages) + language_codes_set = translate.Glossary.LanguageCodesSet( + language_codes=languages + ) - gcs_source = translate.types.GcsSource( - input_uri=glossary_uri) + gcs_source = translate.GcsSource(input_uri=glossary_uri) - input_config = translate.types.GlossaryInputConfig( - gcs_source=gcs_source) + input_config = translate.GlossaryInputConfig(gcs_source=gcs_source) # Set glossary resource information - glossary = translate.types.Glossary( - name=name, - language_codes_set=language_codes_set, - input_config=input_config) + glossary = translate.Glossary( + name=name, language_codes_set=language_codes_set, input_config=input_config + ) parent = f"projects/{project_id}/locations/{location}" @@ -112,16 +109,20 @@ def create_glossary(languages, project_id, glossary_name, glossary_uri): try: operation = client.create_glossary(parent=parent, glossary=glossary) operation.result(timeout=90) - print('Created glossary ' + glossary_name + '.') + print("Created glossary " + glossary_name + ".") except AlreadyExists: - print('The glossary ' + glossary_name + - ' already exists. No new glossary was created.') + print( + "The glossary " + + glossary_name + + " already exists. No new glossary was created." + ) # [END translate_hybrid_create_glossary] # [START translate_hybrid_translate] -def translate_text(text, source_language_code, target_language_code, - project_id, glossary_name): +def translate_text( + text, source_language_code, target_language_code, project_id, glossary_name +): """Translates text to a given language using a glossary ARGS @@ -140,15 +141,11 @@ def translate_text(text, source_language_code, target_language_code, client = translate.TranslationServiceClient() # Designates the data center location that you want to use - location = 'us-central1' + location = "us-central1" - glossary = client.glossary_path( - project_id, - location, - glossary_name) + glossary = client.glossary_path(project_id, location, glossary_name) - glossary_config = translate.types.TranslateTextGlossaryConfig( - glossary=glossary) + glossary_config = translate.TranslateTextGlossaryConfig(glossary=glossary) parent = f"projects/{project_id}/locations/{location}" @@ -159,7 +156,7 @@ def translate_text(text, source_language_code, target_language_code, "mime_type": "text/plain", # mime types: text/plain, text/html "source_language_code": source_language_code, "target_language_code": target_language_code, - "glossary_config": glossary_config + "glossary_config": glossary_config, } ) @@ -189,8 +186,9 @@ def text_to_speech(text, outfile): # Convert plaintext to SSML in order to wait two seconds # between each line in synthetic speech - ssml = '{}'.format( - escaped_lines.replace('\n', '\n')) + ssml = "{}".format( + escaped_lines.replace("\n", '\n') + ) # Instantiates a client client = texttospeech.TextToSpeechClient() @@ -201,28 +199,27 @@ def text_to_speech(text, outfile): # Builds the voice request, selects the language code ("en-US") and # the SSML voice gender ("MALE") voice = texttospeech.VoiceSelectionParams( - language_code='en-US', - ssml_gender=texttospeech.SsmlVoiceGender.MALE) + language_code="en-US", ssml_gender=texttospeech.SsmlVoiceGender.MALE + ) # Selects the type of audio file to return audio_config = texttospeech.AudioConfig( - audio_encoding=texttospeech.AudioEncoding.MP3) + audio_encoding=texttospeech.AudioEncoding.MP3 + ) # Performs the text-to-speech request on the text input with the selected # voice parameters and audio file type request = texttospeech.SynthesizeSpeechRequest( - input=synthesis_input, - voice=voice, - audio_config=audio_config + input=synthesis_input, voice=voice, audio_config=audio_config ) response = client.synthesize_speech(request=request) # Writes the synthetic audio to the output file. - with open(outfile, 'wb') as out: + with open(outfile, "wb") as out: out.write(response.audio_content) - print('Audio content written to file ' + outfile) + print("Audio content written to file " + outfile) # [END translate_hybrid_tts] @@ -230,30 +227,31 @@ def text_to_speech(text, outfile): def main(): # Photo from which to extract text - infile = 'resources/example.png' + infile = "resources/example.png" # Name of file that will hold synthetic speech - outfile = 'resources/example.mp3' + outfile = "resources/example.mp3" # Defines the languages in the glossary # This list must match the languages in the glossary # Here, the glossary includes French and English - glossary_langs = ['fr', 'en'] + glossary_langs = ["fr", "en"] # Name that will be assigned to your project's glossary resource - glossary_name = 'bistro-glossary' + glossary_name = "bistro-glossary" # uri of .csv file uploaded to Cloud Storage - glossary_uri = 'gs://cloud-samples-data/translation/bistro_glossary.csv' + glossary_uri = "gs://cloud-samples-data/translation/bistro_glossary.csv" create_glossary(glossary_langs, PROJECT_ID, glossary_name, glossary_uri) # photo -> detected text text_to_translate = pic_to_text(infile) # detected text -> translated text - text_to_speak = translate_text(text_to_translate, 'fr', 'en', - PROJECT_ID, glossary_name) + text_to_speak = translate_text( + text_to_translate, "fr", "en", PROJECT_ID, glossary_name + ) # translated text -> synthetic audio text_to_speech(text_to_speak, outfile) # [END translate_hybrid_integration] -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/samples/snippets/hybrid_glossaries/hybrid_tutorial_test.py b/samples/snippets/hybrid_glossaries/hybrid_tutorial_test.py index 8df7cc35..2b4f6559 100644 --- a/samples/snippets/hybrid_glossaries/hybrid_tutorial_test.py +++ b/samples/snippets/hybrid_glossaries/hybrid_tutorial_test.py @@ -13,7 +13,6 @@ # limitations under the License. import os -import re import sys import uuid @@ -23,7 +22,7 @@ from hybrid_tutorial import translate_text -PROJECT_ID = os.environ['GOOGLE_CLOUD_PROJECT'] +PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"] # VISION TESTS @@ -33,35 +32,22 @@ def test_vision_standard_format(): # Generate text using Vision API text = pic_to_text('resources/standard_format.jpeg') - assert re.match(r"This\s?is\s?a\s?test!\s?", text) - - -def test_vision_non_standard_format(): - - # Generate text - text = pic_to_text('resources/non_standard_format.png') - - # Read expected text - with open('resources/non_standard_format.txt') as f: - expected_text = f.read() - - assert text == expected_text + assert len(text) > 0 # TRANSLATE TESTS def test_create_and_delete_glossary(): - sys.path.insert(1, '../') + sys.path.insert(1, "../") from beta_snippets import delete_glossary - languages = ['fr', 'en'] - glossary_name = f'test-glossary-{uuid.uuid4()}' - glossary_uri = 'gs://cloud-samples-data/translation/bistro_glossary.csv' + languages = ["fr", "en"] + glossary_name = f"test-glossary-{uuid.uuid4()}" + glossary_uri = "gs://cloud-samples-data/translation/bistro_glossary.csv" # create_glossary will raise an exception if creation fails - create_glossary(languages, PROJECT_ID, glossary_name, - glossary_uri) + create_glossary(languages, PROJECT_ID, glossary_name, glossary_uri) # Delete glossary so that future tests will pass # delete_glossary will raise an exception if deletion fails @@ -70,21 +56,19 @@ def test_create_and_delete_glossary(): def test_translate_standard(): - expected_text = 'Hello' + expected_text = "Hello" - text = translate_text('Bonjour', 'fr', 'en', PROJECT_ID, - 'bistro-glossary') + text = translate_text("Bonjour", "fr", "en", PROJECT_ID, "bistro-glossary") assert text == expected_text def test_translate_glossary(): - expected_text = 'I eat goat cheese' - input_text = 'Je mange du chevre' + expected_text = "I eat goat cheese" + input_text = "Je mange du chevre" - text = translate_text(input_text, 'fr', 'en', PROJECT_ID, - 'bistro-glossary') + text = translate_text(input_text, "fr", "en", PROJECT_ID, "bistro-glossary") assert text == expected_text @@ -93,10 +77,10 @@ def test_translate_glossary(): def test_tts_standard(capsys): - outfile = 'resources/test_standard_text.mp3' - textfile = 'resources/standard_format.txt' + outfile = "resources/test_standard_text.mp3" + textfile = "resources/standard_format.txt" - with open(textfile, 'r') as f: + with open(textfile, "r") as f: text = f.read() text_to_speech(text, outfile) @@ -106,7 +90,7 @@ def test_tts_standard(capsys): out, err = capsys.readouterr() # Assert success message printed - assert 'Audio content written to file ' + outfile in out + assert "Audio content written to file " + outfile in out # Delete test file os.remove(outfile) diff --git a/samples/snippets/hybrid_glossaries/noxfile.py b/samples/snippets/hybrid_glossaries/noxfile.py index ba55d7ce..f3a90583 100644 --- a/samples/snippets/hybrid_glossaries/noxfile.py +++ b/samples/snippets/hybrid_glossaries/noxfile.py @@ -37,24 +37,22 @@ TEST_CONFIG = { # You can opt out from the test for specific Python versions. - 'ignored_versions': ["2.7"], - + "ignored_versions": ["2.7"], # An envvar key for determining the project id to use. Change it # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a # build specific Cloud project. You can also use your own string # to use your own Cloud project. - 'gcloud_project_env': 'GOOGLE_CLOUD_PROJECT', + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any # secrets here. These values will override predefined values. - 'envs': {}, + "envs": {}, } try: # Ensure we can import noxfile_config in the project's directory. - sys.path.append('.') + sys.path.append(".") from noxfile_config import TEST_CONFIG_OVERRIDE except ImportError as e: print("No user noxfile_config found: detail: {}".format(e)) @@ -69,12 +67,12 @@ def get_pytest_env_vars(): ret = {} # Override the GCLOUD_PROJECT and the alias. - env_key = TEST_CONFIG['gcloud_project_env'] + env_key = TEST_CONFIG["gcloud_project_env"] # This should error out if not set. - ret['GOOGLE_CLOUD_PROJECT'] = os.environ[env_key] + ret["GOOGLE_CLOUD_PROJECT"] = os.environ[env_key] # Apply user supplied envs. - ret.update(TEST_CONFIG['envs']) + ret.update(TEST_CONFIG["envs"]) return ret @@ -83,7 +81,7 @@ def get_pytest_env_vars(): ALL_VERSIONS = ["2.7", "3.6", "3.7", "3.8"] # Any default versions that should be ignored. -IGNORED_VERSIONS = TEST_CONFIG['ignored_versions'] +IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"] TESTED_VERSIONS = sorted([v for v in ALL_VERSIONS if v not in IGNORED_VERSIONS]) @@ -138,7 +136,7 @@ def lint(session): args = FLAKE8_COMMON_ARGS + [ "--application-import-names", ",".join(local_names), - "." + ".", ] session.run("flake8", *args) @@ -182,9 +180,9 @@ def py(session): if session.python in TESTED_VERSIONS: _session_tests(session) else: - session.skip("SKIPPED: {} tests are disabled for this sample.".format( - session.python - )) + session.skip( + "SKIPPED: {} tests are disabled for this sample.".format(session.python) + ) # @@ -201,6 +199,11 @@ def _get_repo_root(): break if Path(p / ".git").exists(): return str(p) + # .git is not available in repos cloned via Cloud Build + # setup.py is always in the library's root, so use that instead + # https://github.com/googleapis/synthtool/issues/792 + if Path(p / "setup.py").exists(): + return str(p) p = p.parent raise Exception("Unable to detect repository root.") diff --git a/samples/snippets/hybrid_glossaries/requirements.txt b/samples/snippets/hybrid_glossaries/requirements.txt index af2fa067..ff0077c1 100644 --- a/samples/snippets/hybrid_glossaries/requirements.txt +++ b/samples/snippets/hybrid_glossaries/requirements.txt @@ -1,3 +1,3 @@ -google-cloud-translate==3.0.0 -google-cloud-vision==1.0.0 -google-cloud-texttospeech==2.1.0 +google-cloud-translate==3.0.1 +google-cloud-vision==2.0.0 +google-cloud-texttospeech==2.2.0 diff --git a/samples/snippets/hybrid_glossaries/resources/non_standard_format.png b/samples/snippets/hybrid_glossaries/resources/non_standard_format.png deleted file mode 100644 index eeee9c7f..00000000 Binary files a/samples/snippets/hybrid_glossaries/resources/non_standard_format.png and /dev/null differ diff --git a/samples/snippets/hybrid_glossaries/resources/non_standard_format.txt b/samples/snippets/hybrid_glossaries/resources/non_standard_format.txt deleted file mode 100644 index 529799ee..00000000 --- a/samples/snippets/hybrid_glossaries/resources/non_standard_format.txt +++ /dev/null @@ -1,30 +0,0 @@ -MENU -Google Cloud Bistro -SALADS -SANDWICHES -GCP Green Salad -Fresh Greens -$5 -Kubernetes Sandwich -ham and cheese sandwich -$10 -Cloud Caprese -Mozzarella, tomatoes, basil, -balsamic reduction -$8 -Dialogflow Panini -chicken, pesto, and -mozzarella panini -$10 -Firebase Fruit Salad -watermelon, honeydew melon, -and pineapple -$6 -Compute Engine Burger -quarter-pound burger with -cheddar cheese -$10 -BigQuery BLT -bacon, lettuce, and tomato -sandwich -$10 diff --git a/samples/snippets/noxfile.py b/samples/snippets/noxfile.py index ba55d7ce..27d948d6 100644 --- a/samples/snippets/noxfile.py +++ b/samples/snippets/noxfile.py @@ -37,24 +37,22 @@ TEST_CONFIG = { # You can opt out from the test for specific Python versions. - 'ignored_versions': ["2.7"], - + "ignored_versions": ["2.7"], # An envvar key for determining the project id to use. Change it # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a # build specific Cloud project. You can also use your own string # to use your own Cloud project. - 'gcloud_project_env': 'GOOGLE_CLOUD_PROJECT', + "gcloud_project_env": "GOOGLE_CLOUD_PROJECT", # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', - # A dictionary you want to inject into your test. Don't put any # secrets here. These values will override predefined values. - 'envs': {}, + "envs": {}, } try: # Ensure we can import noxfile_config in the project's directory. - sys.path.append('.') + sys.path.append(".") from noxfile_config import TEST_CONFIG_OVERRIDE except ImportError as e: print("No user noxfile_config found: detail: {}".format(e)) @@ -69,12 +67,12 @@ def get_pytest_env_vars(): ret = {} # Override the GCLOUD_PROJECT and the alias. - env_key = TEST_CONFIG['gcloud_project_env'] + env_key = TEST_CONFIG["gcloud_project_env"] # This should error out if not set. - ret['GOOGLE_CLOUD_PROJECT'] = os.environ[env_key] + ret["GOOGLE_CLOUD_PROJECT"] = os.environ[env_key] # Apply user supplied envs. - ret.update(TEST_CONFIG['envs']) + ret.update(TEST_CONFIG["envs"]) return ret @@ -83,7 +81,7 @@ def get_pytest_env_vars(): ALL_VERSIONS = ["2.7", "3.6", "3.7", "3.8"] # Any default versions that should be ignored. -IGNORED_VERSIONS = TEST_CONFIG['ignored_versions'] +IGNORED_VERSIONS = TEST_CONFIG["ignored_versions"] TESTED_VERSIONS = sorted([v for v in ALL_VERSIONS if v not in IGNORED_VERSIONS]) @@ -138,7 +136,7 @@ def lint(session): args = FLAKE8_COMMON_ARGS + [ "--application-import-names", ",".join(local_names), - "." + ".", ] session.run("flake8", *args) @@ -201,6 +199,11 @@ def _get_repo_root(): break if Path(p / ".git").exists(): return str(p) + # .git is not available in repos cloned via Cloud Build + # setup.py is always in the library's root, so use that instead + # https://github.com/googleapis/synthtool/issues/792 + if Path(p / "setup.py").exists(): + return str(p) p = p.parent raise Exception("Unable to detect repository root.") diff --git a/samples/snippets/quickstart.py b/samples/snippets/quickstart.py index 888b6691..9fc4027a 100644 --- a/samples/snippets/quickstart.py +++ b/samples/snippets/quickstart.py @@ -24,19 +24,17 @@ def run_quickstart(): translate_client = translate.Client() # The text to translate - text = u'Hello, world!' + text = u"Hello, world!" # The target language - target = 'ru' + target = "ru" # Translates some text into Russian - translation = translate_client.translate( - text, - target_language=target) + translation = translate_client.translate(text, target_language=target) - print(u'Text: {}'.format(text)) - print(u'Translation: {}'.format(translation['translatedText'])) + print(u"Text: {}".format(text)) + print(u"Translation: {}".format(translation["translatedText"])) # [END translate_quickstart] -if __name__ == '__main__': +if __name__ == "__main__": run_quickstart() diff --git a/samples/snippets/quickstart_test.py b/samples/snippets/quickstart_test.py index 8def7f3e..e4dc4886 100644 --- a/samples/snippets/quickstart_test.py +++ b/samples/snippets/quickstart_test.py @@ -20,4 +20,4 @@ def test_quickstart(capsys): quickstart.run_quickstart() out, _ = capsys.readouterr() - assert u'Translation' in out + assert u"Translation" in out diff --git a/samples/snippets/requirements-test.txt b/samples/snippets/requirements-test.txt index b267f58b..bd6c7999 100644 --- a/samples/snippets/requirements-test.txt +++ b/samples/snippets/requirements-test.txt @@ -1,3 +1,3 @@ backoff==1.10.0 flaky==3.7.0 -pytest==5.4.3 \ No newline at end of file +pytest==5.4.3 diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index 4c26010b..4f26184f 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,2 +1,3 @@ -google-cloud-translate==3.0.0 -google-cloud-storage==1.30.0 +google-cloud-translate==3.0.1 +google-cloud-storage==1.33.0 +google-cloud-automl==2.1.0 diff --git a/samples/snippets/snippets.py b/samples/snippets/snippets.py index 24b45716..5700969c 100644 --- a/samples/snippets/snippets.py +++ b/samples/snippets/snippets.py @@ -23,22 +23,21 @@ import argparse -import six - def detect_language(text): # [START translate_detect_language] """Detects the text's language.""" from google.cloud import translate_v2 as translate + translate_client = translate.Client() # Text can also be a sequence of strings, in which case this method # will return a sequence of results for each text. result = translate_client.detect_language(text) - print('Text: {}'.format(text)) - print('Confidence: {}'.format(result['confidence'])) - print('Language: {}'.format(result['language'])) + print("Text: {}".format(text)) + print("Confidence: {}".format(result["confidence"])) + print("Language: {}".format(result["language"])) # [END translate_detect_language] @@ -46,12 +45,13 @@ def list_languages(): # [START translate_list_codes] """Lists all available languages.""" from google.cloud import translate_v2 as translate + translate_client = translate.Client() results = translate_client.get_languages() for language in results: - print(u'{name} ({language})'.format(**language)) + print(u"{name} ({language})".format(**language)) # [END translate_list_codes] @@ -63,16 +63,17 @@ def list_languages_with_target(target): See https://g.co/cloud/translate/v2/translate-reference#supported_languages """ from google.cloud import translate_v2 as translate + translate_client = translate.Client() results = translate_client.get_languages(target_language=target) for language in results: - print(u'{name} ({language})'.format(**language)) + print(u"{name} ({language})".format(**language)) # [END translate_list_language_names] -def translate_text_with_model(target, text, model='nmt'): +def translate_text_with_model(target, text, model="nmt"): # [START translate_text_with_model] """Translates text into the target language. @@ -81,21 +82,21 @@ def translate_text_with_model(target, text, model='nmt'): Target must be an ISO 639-1 language code. See https://g.co/cloud/translate/v2/translate-reference#supported_languages """ + import six from google.cloud import translate_v2 as translate + translate_client = translate.Client() if isinstance(text, six.binary_type): - text = text.decode('utf-8') + text = text.decode("utf-8") # Text can also be a sequence of strings, in which case this method # will return a sequence of results for each text. - result = translate_client.translate( - text, target_language=target, model=model) + result = translate_client.translate(text, target_language=target, model=model) - print(u'Text: {}'.format(result['input'])) - print(u'Translation: {}'.format(result['translatedText'])) - print(u'Detected source language: {}'.format( - result['detectedSourceLanguage'])) + print(u"Text: {}".format(result["input"])) + print(u"Translation: {}".format(result["translatedText"])) + print(u"Detected source language: {}".format(result["detectedSourceLanguage"])) # [END translate_text_with_model] @@ -106,53 +107,57 @@ def translate_text(target, text): Target must be an ISO 639-1 language code. See https://g.co/cloud/translate/v2/translate-reference#supported_languages """ + import six from google.cloud import translate_v2 as translate + translate_client = translate.Client() if isinstance(text, six.binary_type): - text = text.decode('utf-8') + text = text.decode("utf-8") # Text can also be a sequence of strings, in which case this method # will return a sequence of results for each text. - result = translate_client.translate( - text, target_language=target) + result = translate_client.translate(text, target_language=target) - print(u'Text: {}'.format(result['input'])) - print(u'Translation: {}'.format(result['translatedText'])) - print(u'Detected source language: {}'.format( - result['detectedSourceLanguage'])) + print(u"Text: {}".format(result["input"])) + print(u"Translation: {}".format(result["translatedText"])) + print(u"Detected source language: {}".format(result["detectedSourceLanguage"])) # [END translate_translate_text] -if __name__ == '__main__': +if __name__ == "__main__": parser = argparse.ArgumentParser( - description=__doc__, - formatter_class=argparse.RawDescriptionHelpFormatter) - subparsers = parser.add_subparsers(dest='command') + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + subparsers = parser.add_subparsers(dest="command") detect_langage_parser = subparsers.add_parser( - 'detect-language', help=detect_language.__doc__) - detect_langage_parser.add_argument('text') + "detect-language", help=detect_language.__doc__ + ) + detect_langage_parser.add_argument("text") list_languages_parser = subparsers.add_parser( - 'list-languages', help=list_languages.__doc__) + "list-languages", help=list_languages.__doc__ + ) list_languages_with_target_parser = subparsers.add_parser( - 'list-languages-with-target', help=list_languages_with_target.__doc__) - list_languages_with_target_parser.add_argument('target') + "list-languages-with-target", help=list_languages_with_target.__doc__ + ) + list_languages_with_target_parser.add_argument("target") translate_text_parser = subparsers.add_parser( - 'translate-text', help=translate_text.__doc__) - translate_text_parser.add_argument('target') - translate_text_parser.add_argument('text') + "translate-text", help=translate_text.__doc__ + ) + translate_text_parser.add_argument("target") + translate_text_parser.add_argument("text") args = parser.parse_args() - if args.command == 'detect-language': + if args.command == "detect-language": detect_language(args.text) - elif args.command == 'list-languages': + elif args.command == "list-languages": list_languages() - elif args.command == 'list-languages-with-target': + elif args.command == "list-languages-with-target": list_languages_with_target(args.target) - elif args.command == 'translate-text': + elif args.command == "translate-text": translate_text(args.target, args.text) diff --git a/samples/snippets/snippets_test.py b/samples/snippets/snippets_test.py index 6d63759d..b5fe362c 100644 --- a/samples/snippets/snippets_test.py +++ b/samples/snippets/snippets_test.py @@ -19,31 +19,31 @@ def test_detect_language(capsys): - snippets.detect_language('Hæ sæta') + snippets.detect_language("Hæ sæta") out, _ = capsys.readouterr() - assert 'is' in out + assert "is" in out def test_list_languages(capsys): snippets.list_languages() out, _ = capsys.readouterr() - assert 'Icelandic (is)' in out + assert "Icelandic (is)" in out def test_list_languages_with_target(capsys): - snippets.list_languages_with_target('is') + snippets.list_languages_with_target("is") out, _ = capsys.readouterr() - assert u'íslenska (is)' in out + assert u"íslenska (is)" in out def test_translate_text(capsys): - snippets.translate_text('is', 'Hello world') + snippets.translate_text("is", "Hello world") out, _ = capsys.readouterr() - assert u'Halló heimur' in out + assert u"Halló heimur" in out def test_translate_utf8(capsys): - text = u'나는 파인애플을 좋아한다.' - snippets.translate_text('en', text) + text = u"파인애플 13개" + snippets.translate_text("en", text) out, _ = capsys.readouterr() - assert u'I like pineapple' in out + assert u"13 pineapples" in out diff --git a/samples/snippets/translate_v3_batch_translate_text.py b/samples/snippets/translate_v3_batch_translate_text.py index 487b0965..0f4161c9 100644 --- a/samples/snippets/translate_v3_batch_translate_text.py +++ b/samples/snippets/translate_v3_batch_translate_text.py @@ -17,10 +17,10 @@ def batch_translate_text( - input_uri="gs://YOUR_BUCKET_ID/path/to/your/file.txt", - output_uri="gs://YOUR_BUCKET_ID/path/to/save/results/", - project_id="YOUR_PROJECT_ID", - timeout=180, + input_uri="gs://YOUR_BUCKET_ID/path/to/your/file.txt", + output_uri="gs://YOUR_BUCKET_ID/path/to/save/results/", + project_id="YOUR_PROJECT_ID", + timeout=180, ): """Translates a batch of texts on GCS and stores the result in a GCS location.""" @@ -32,7 +32,7 @@ def batch_translate_text( input_configs_element = { "gcs_source": gcs_source, - "mime_type": "text/plain" # Can be "text/plain" or "text/html". + "mime_type": "text/plain", # Can be "text/plain" or "text/html". } gcs_destination = {"output_uri_prefix": output_uri} output_config = {"gcs_destination": gcs_destination} @@ -45,15 +45,15 @@ def batch_translate_text( "source_language_code": "en", "target_language_codes": ["ja"], # Up to 10 language codes here. "input_configs": [input_configs_element], - "output_config": output_config + "output_config": output_config, } ) - print(u"Waiting for operation to complete...") + print("Waiting for operation to complete...") response = operation.result(timeout) - print(u"Total Characters: {}".format(response.total_characters)) - print(u"Translated Characters: {}".format(response.translated_characters)) + print("Total Characters: {}".format(response.total_characters)) + print("Translated Characters: {}".format(response.translated_characters)) # [END translate_v3_batch_translate_text] diff --git a/samples/snippets/translate_v3_batch_translate_text_test.py b/samples/snippets/translate_v3_batch_translate_text_test.py index f604a3e1..ad637cd8 100644 --- a/samples/snippets/translate_v3_batch_translate_text_test.py +++ b/samples/snippets/translate_v3_batch_translate_text_test.py @@ -42,7 +42,7 @@ def test_batch_translate_text(capsys, bucket): "gs://cloud-samples-data/translation/text.txt", "gs://{}/translation/BATCH_TRANSLATION_OUTPUT/".format(bucket.name), PROJECT_ID, - timeout=240 + timeout=320, ) out, _ = capsys.readouterr() assert "Total Characters" in out diff --git a/samples/snippets/translate_v3_batch_translate_text_with_glossary.py b/samples/snippets/translate_v3_batch_translate_text_with_glossary.py index e9c4a7c2..574b001c 100644 --- a/samples/snippets/translate_v3_batch_translate_text_with_glossary.py +++ b/samples/snippets/translate_v3_batch_translate_text_with_glossary.py @@ -18,11 +18,11 @@ def batch_translate_text_with_glossary( - input_uri="gs://YOUR_BUCKET_ID/path/to/your/file.txt", - output_uri="gs://YOUR_BUCKET_ID/path/to/save/results/", - project_id="YOUR_PROJECT_ID", - glossary_id="YOUR_GLOSSARY_ID", - timeout=180, + input_uri="gs://YOUR_BUCKET_ID/path/to/your/file.txt", + output_uri="gs://YOUR_BUCKET_ID/path/to/save/results/", + project_id="YOUR_PROJECT_ID", + glossary_id="YOUR_GLOSSARY_ID", + timeout=320, ): """Translates a batch of texts on GCS and stores the result in a GCS location. Glossary is applied for translation.""" @@ -37,7 +37,7 @@ def batch_translate_text_with_glossary( input_configs_element = { "gcs_source": gcs_source, - "mime_type": "text/plain" # Can be "text/plain" or "text/html". + "mime_type": "text/plain", # Can be "text/plain" or "text/html". } gcs_destination = {"output_uri_prefix": output_uri} output_config = {"gcs_destination": gcs_destination} @@ -50,9 +50,7 @@ def batch_translate_text_with_glossary( project_id, "us-central1", glossary_id # The location of the glossary ) - glossary_config = translate.TranslateTextGlossaryConfig( - glossary=glossary_path - ) + glossary_config = translate.TranslateTextGlossaryConfig(glossary=glossary_path) glossaries = {"ja": glossary_config} # target lang as key @@ -63,15 +61,15 @@ def batch_translate_text_with_glossary( "target_language_codes": ["ja"], # Up to 10 language codes here. "input_configs": [input_configs_element], "glossaries": glossaries, - "output_config": output_config + "output_config": output_config, } ) - print(u"Waiting for operation to complete...") + print("Waiting for operation to complete...") response = operation.result(timeout) - print(u"Total Characters: {}".format(response.total_characters)) - print(u"Translated Characters: {}".format(response.translated_characters)) + print("Total Characters: {}".format(response.total_characters)) + print("Translated Characters: {}".format(response.translated_characters)) # [END translate_v3_batch_translate_text_with_glossary] diff --git a/samples/snippets/translate_v3_batch_translate_text_with_glossary_and_model.py b/samples/snippets/translate_v3_batch_translate_text_with_glossary_and_model.py new file mode 100644 index 00000000..3110dcee --- /dev/null +++ b/samples/snippets/translate_v3_batch_translate_text_with_glossary_and_model.py @@ -0,0 +1,77 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. + +# [START translate_v3_batch_translate_text_with_glossary_and_model] +from google.cloud import translate + + +def batch_translate_text_with_glossary_and_model( + input_uri="gs://YOUR_BUCKET_ID/path/to/your/file.txt", + output_uri="gs://YOUR_BUCKET_ID/path/to/save/results/", + project_id="YOUR_PROJECT_ID", + model_id="YOUR_MODEL_ID", + glossary_id="YOUR_GLOSSARY_ID", +): + """ + Batch translate text with Glossary and Translation model + """ + + client = translate.TranslationServiceClient() + + # Supported language codes: https://cloud.google.com/translate/docs/languages + location = "us-central1" + + target_language_codes = ["ja"] + gcs_source = {"input_uri": input_uri} + + # Optional. Can be "text/plain" or "text/html". + mime_type = "text/plain" + input_configs_element = {"gcs_source": gcs_source, "mime_type": mime_type} + input_configs = [input_configs_element] + gcs_destination = {"output_uri_prefix": output_uri} + output_config = {"gcs_destination": gcs_destination} + parent = f"projects/{project_id}/locations/{location}" + model_path = "projects/{}/locations/{}/models/{}".format( + project_id, "us-central1", model_id + ) + models = {"ja": model_path} + + glossary_path = client.glossary_path( + project_id, "us-central1", glossary_id # The location of the glossary + ) + + glossary_config = translate.TranslateTextGlossaryConfig(glossary=glossary_path) + glossaries = {"ja": glossary_config} # target lang as key + + operation = client.batch_translate_text( + request={ + "parent": parent, + "source_language_code": "en", + "target_language_codes": target_language_codes, + "input_configs": input_configs, + "output_config": output_config, + "models": models, + "glossaries": glossaries, + } + ) + + print("Waiting for operation to complete...") + response = operation.result() + + # Display the translation for each input text provided + print("Total Characters: {}".format(response.total_characters)) + print("Translated Characters: {}".format(response.translated_characters)) + + +# [END translate_v3_batch_translate_text_with_glossary_and_model] diff --git a/samples/snippets/translate_v3_batch_translate_text_with_glossary_and_model_test.py b/samples/snippets/translate_v3_batch_translate_text_with_glossary_and_model_test.py new file mode 100644 index 00000000..6579831a --- /dev/null +++ b/samples/snippets/translate_v3_batch_translate_text_with_glossary_and_model_test.py @@ -0,0 +1,68 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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 uuid + +from google.cloud import storage +import pytest + +import translate_v3_batch_translate_text_with_glossary_and_model +import translate_v3_create_glossary +import translate_v3_delete_glossary + +PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"] +GLOSSARY_INPUT_URI = "gs://cloud-samples-data/translation/glossary_ja.csv" +MODEL_ID = "TRL3128559826197068699" + + +@pytest.fixture(scope="session") +def glossary(): + """Get the ID of a glossary available to session (do not mutate/delete).""" + glossary_id = "must-start-with-letters-" + str(uuid.uuid1()) + translate_v3_create_glossary.create_glossary( + project_id=PROJECT_ID, input_uri=GLOSSARY_INPUT_URI, glossary_id=glossary_id + ) + + yield glossary_id + + try: + translate_v3_delete_glossary.sample_delete_glossary(PROJECT_ID, glossary_id) + except Exception: + pass + + +@pytest.fixture(scope="function") +def bucket(): + """Create a temporary bucket to store annotation output.""" + bucket_name = "mike-test-delete-" + str(uuid.uuid1()) + storage_client = storage.Client() + bucket = storage_client.create_bucket(bucket_name) + + yield bucket + + bucket.delete(force=True) + + +def test_batch_translate_text_with_glossary_and_model(capsys, bucket, glossary): + translate_v3_batch_translate_text_with_glossary_and_model.batch_translate_text_with_glossary_and_model( + "gs://cloud-samples-data/translation/text_with_custom_model_and_glossary.txt", + "gs://{}/translation/BATCH_TRANSLATION_OUTPUT/".format(bucket.name), + PROJECT_ID, + MODEL_ID, + glossary, + ) + + out, _ = capsys.readouterr() + assert "Total Characters: 25" in out diff --git a/samples/snippets/translate_v3_batch_translate_text_with_glossary_test.py b/samples/snippets/translate_v3_batch_translate_text_with_glossary_test.py index 23aa6691..33a1f829 100644 --- a/samples/snippets/translate_v3_batch_translate_text_with_glossary_test.py +++ b/samples/snippets/translate_v3_batch_translate_text_with_glossary_test.py @@ -46,18 +46,18 @@ def glossary(): ) def delete_glossary(): try: - translate_v3_delete_glossary.delete_glossary( - PROJECT_ID, glossary_id) + translate_v3_delete_glossary.delete_glossary(PROJECT_ID, glossary_id) except NotFound as e: # Ignoring this case. print("Got NotFound, detail: {}".format(str(e))) + delete_glossary() @pytest.fixture(scope="function") def bucket(): """Create a temporary bucket to store annotation output.""" - bucket_name = f'tmp-{uuid.uuid4().hex}' + bucket_name = f"tmp-{uuid.uuid4().hex}" storage_client = storage.Client() bucket = storage_client.create_bucket(bucket_name) @@ -73,7 +73,7 @@ def test_batch_translate_text_with_glossary(capsys, bucket, glossary): "gs://{}/translation/BATCH_TRANSLATION_OUTPUT/".format(bucket.name), PROJECT_ID, glossary, - 240 + 320, ) out, _ = capsys.readouterr() diff --git a/samples/snippets/translate_v3_batch_translate_text_with_model.py b/samples/snippets/translate_v3_batch_translate_text_with_model.py new file mode 100644 index 00000000..07d967d7 --- /dev/null +++ b/samples/snippets/translate_v3_batch_translate_text_with_model.py @@ -0,0 +1,69 @@ +# 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. + + +# [START translate_v3_batch_translate_text_with_model] +from google.cloud import translate + + +def batch_translate_text_with_model( + input_uri="gs://YOUR_BUCKET_ID/path/to/your/file.txt", + output_uri="gs://YOUR_BUCKET_ID/path/to/save/results/", + project_id="YOUR_PROJECT_ID", + model_id="YOUR_MODEL_ID", +): + """Batch translate text using Translation model. + Model can be AutoML or General[built-in] model. """ + + client = translate.TranslationServiceClient() + + # Supported file types: https://cloud.google.com/translate/docs/supported-formats + gcs_source = {"input_uri": input_uri} + location = "us-central1" + + input_configs_element = { + "gcs_source": gcs_source, + "mime_type": "text/plain", # Can be "text/plain" or "text/html". + } + gcs_destination = {"output_uri_prefix": output_uri} + output_config = {"gcs_destination": gcs_destination} + parent = f"projects/{project_id}/locations/{location}" + + model_path = "projects/{}/locations/{}/models/{}".format( + project_id, location, model_id # The location of AutoML model. + ) + + # Supported language codes: https://cloud.google.com/translate/docs/languages + models = {"ja": model_path} # takes a target lang as key. + + operation = client.batch_translate_text( + request={ + "parent": parent, + "source_language_code": "en", + "target_language_codes": ["ja"], # Up to 10 language codes here. + "input_configs": [input_configs_element], + "output_config": output_config, + "models": models, + } + ) + + print("Waiting for operation to complete...") + response = operation.result() + + # Display the translation for each input text provided. + print("Total Characters: {}".format(response.total_characters)) + print("Translated Characters: {}".format(response.translated_characters)) + + +# [END translate_v3_batch_translate_text_with_model] diff --git a/samples/snippets/translate_v3_batch_translate_text_with_model_test.py b/samples/snippets/translate_v3_batch_translate_text_with_model_test.py new file mode 100644 index 00000000..f6ad1007 --- /dev/null +++ b/samples/snippets/translate_v3_batch_translate_text_with_model_test.py @@ -0,0 +1,49 @@ +# 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 uuid + +from google.cloud import storage +import pytest + +import translate_v3_batch_translate_text_with_model + + +PROJECT_ID = os.environ["GOOGLE_CLOUD_PROJECT"] +MODEL_ID = "TRL3128559826197068699" + + +@pytest.fixture(scope="function") +def bucket(): + """Create a temporary bucket to store annotation output.""" + bucket_name = f"tmp-{uuid.uuid4().hex}" + storage_client = storage.Client() + bucket = storage_client.create_bucket(bucket_name) + + yield bucket + + bucket.delete(force=True) + + +def test_batch_translate_text_with_model(capsys, bucket): + translate_v3_batch_translate_text_with_model.batch_translate_text_with_model( + "gs://cloud-samples-data/translation/custom_model_text.txt", + "gs://{}/translation/BATCH_TRANSLATION_OUTPUT/".format(bucket.name), + PROJECT_ID, + MODEL_ID, + ) + out, _ = capsys.readouterr() + assert "Total Characters: 15" in out + assert "Translated Characters: 15" in out diff --git a/samples/snippets/translate_v3_create_glossary.py b/samples/snippets/translate_v3_create_glossary.py index 2955acaa..b2fd3863 100644 --- a/samples/snippets/translate_v3_create_glossary.py +++ b/samples/snippets/translate_v3_create_glossary.py @@ -17,10 +17,10 @@ def create_glossary( - project_id="YOUR_PROJECT_ID", - input_uri="YOUR_INPUT_URI", - glossary_id="YOUR_GLOSSARY_ID", - timeout=180, + project_id="YOUR_PROJECT_ID", + input_uri="YOUR_INPUT_URI", + glossary_id="YOUR_GLOSSARY_ID", + timeout=180, ): """ Create a equivalent term sets glossary. Glossary can be words or diff --git a/samples/snippets/translate_v3_create_glossary_test.py b/samples/snippets/translate_v3_create_glossary_test.py index a24f461e..3e4b61cf 100644 --- a/samples/snippets/translate_v3_create_glossary_test.py +++ b/samples/snippets/translate_v3_create_glossary_test.py @@ -46,9 +46,9 @@ def test_create_glossary(capsys): ) def delete_glossary(): try: - translate_v3_delete_glossary.delete_glossary( - PROJECT_ID, glossary_id) + translate_v3_delete_glossary.delete_glossary(PROJECT_ID, glossary_id) except NotFound as e: # Ignoring this case. print("Got NotFound, detail: {}".format(str(e))) + delete_glossary() diff --git a/samples/snippets/translate_v3_delete_glossary.py b/samples/snippets/translate_v3_delete_glossary.py index fb9f86af..336b7a06 100644 --- a/samples/snippets/translate_v3_delete_glossary.py +++ b/samples/snippets/translate_v3_delete_glossary.py @@ -17,9 +17,7 @@ def delete_glossary( - project_id="YOUR_PROJECT_ID", - glossary_id="YOUR_GLOSSARY_ID", - timeout=180, + project_id="YOUR_PROJECT_ID", glossary_id="YOUR_GLOSSARY_ID", timeout=180, ): """Delete a specific glossary based on the glossary ID.""" client = translate.TranslationServiceClient() diff --git a/samples/snippets/translate_v3_detect_language.py b/samples/snippets/translate_v3_detect_language.py index 9e92b607..eae985c3 100644 --- a/samples/snippets/translate_v3_detect_language.py +++ b/samples/snippets/translate_v3_detect_language.py @@ -37,9 +37,9 @@ def detect_language(project_id="YOUR_PROJECT_ID"): # The most probable language is first. for language in response.languages: # The language detected - print(u"Language code: {}".format(language.language_code)) + print("Language code: {}".format(language.language_code)) # Confidence of detection result for this language - print(u"Confidence: {}".format(language.confidence)) + print("Confidence: {}".format(language.confidence)) # [END translate_v3_detect_language] diff --git a/samples/snippets/translate_v3_get_glossary_test.py b/samples/snippets/translate_v3_get_glossary_test.py index 96ea6b78..a4fc3231 100644 --- a/samples/snippets/translate_v3_get_glossary_test.py +++ b/samples/snippets/translate_v3_get_glossary_test.py @@ -45,11 +45,11 @@ def glossary(): ) def delete_glossary(): try: - translate_v3_delete_glossary.delete_glossary( - PROJECT_ID, glossary_id) + translate_v3_delete_glossary.delete_glossary(PROJECT_ID, glossary_id) except NotFound as e: # Ignoring this case. print("Got NotFound, detail: {}".format(str(e))) + delete_glossary() diff --git a/samples/snippets/translate_v3_get_supported_languages.py b/samples/snippets/translate_v3_get_supported_languages.py index db779278..3802a7e1 100644 --- a/samples/snippets/translate_v3_get_supported_languages.py +++ b/samples/snippets/translate_v3_get_supported_languages.py @@ -29,7 +29,7 @@ def get_supported_languages(project_id="YOUR_PROJECT_ID"): # List language codes of supported languages. print("Supported Languages:") for language in response.languages: - print(u"Language Code: {}".format(language.language_code)) + print("Language Code: {}".format(language.language_code)) # [END translate_v3_get_supported_languages] diff --git a/samples/snippets/translate_v3_get_supported_languages_with_target.py b/samples/snippets/translate_v3_get_supported_languages_with_target.py index 3a164dcb..856b3178 100644 --- a/samples/snippets/translate_v3_get_supported_languages_with_target.py +++ b/samples/snippets/translate_v3_get_supported_languages_with_target.py @@ -27,12 +27,12 @@ def get_supported_languages_with_target(project_id="YOUR_PROJECT_ID"): # Supported language codes: https://cloud.google.com/translate/docs/languages response = client.get_supported_languages( - display_language_code="is", # target language code - parent=parent + display_language_code="is", parent=parent # target language code ) # List language codes of supported languages for language in response.languages: - print(u"Language Code: {}".format(language.language_code)) - print(u"Display Name: {}".format(language.display_name)) + print("Language Code: {}".format(language.language_code)) + print("Display Name: {}".format(language.display_name)) + # [END translate_v3_get_supported_languages_for_target] diff --git a/samples/snippets/translate_v3_get_supported_languages_with_target_test.py b/samples/snippets/translate_v3_get_supported_languages_with_target_test.py index 8f3f52cf..9688efee 100644 --- a/samples/snippets/translate_v3_get_supported_languages_with_target_test.py +++ b/samples/snippets/translate_v3_get_supported_languages_with_target_test.py @@ -21,9 +21,7 @@ def test_list_languages_with_target(capsys): - get_supported_langs.get_supported_languages_with_target( - PROJECT_ID - ) + get_supported_langs.get_supported_languages_with_target(PROJECT_ID) out, _ = capsys.readouterr() assert u"Language Code: sq" in out assert u"Display Name: albanska" in out diff --git a/samples/snippets/translate_v3_list_glossary_test.py b/samples/snippets/translate_v3_list_glossary_test.py index 8f4eaa1a..ed2a4754 100644 --- a/samples/snippets/translate_v3_list_glossary_test.py +++ b/samples/snippets/translate_v3_list_glossary_test.py @@ -45,11 +45,11 @@ def glossary(): ) def delete_glossary(): try: - translate_v3_delete_glossary.delete_glossary( - PROJECT_ID, glossary_id) + translate_v3_delete_glossary.delete_glossary(PROJECT_ID, glossary_id) except NotFound as e: # Ignoring this case. print("Got NotFound, detail: {}".format(str(e))) + delete_glossary() diff --git a/samples/snippets/translate_v3_translate_text.py b/samples/snippets/translate_v3_translate_text.py index 78a79b4e..cdfe819f 100644 --- a/samples/snippets/translate_v3_translate_text.py +++ b/samples/snippets/translate_v3_translate_text.py @@ -33,13 +33,13 @@ def translate_text(text="YOUR_TEXT_TO_TRANSLATE", project_id="YOUR_PROJECT_ID"): "contents": [text], "mime_type": "text/plain", # mime types: text/plain, text/html "source_language_code": "en-US", - "target_language_code": "fr" + "target_language_code": "fr", } ) # Display the translation for each input text provided for translation in response.translations: - print(u"Translated text: {}".format(translation.translated_text)) + print("Translated text: {}".format(translation.translated_text)) # [END translate_v3_translate_text] diff --git a/samples/snippets/translate_v3_translate_text_test.py b/samples/snippets/translate_v3_translate_text_test.py index c93cb91f..fee6e771 100644 --- a/samples/snippets/translate_v3_translate_text_test.py +++ b/samples/snippets/translate_v3_translate_text_test.py @@ -21,7 +21,6 @@ def test_translate_text(capsys): - translate_v3_translate_text.translate_text( - "Hello World!", PROJECT_ID) + translate_v3_translate_text.translate_text("Hello World!", PROJECT_ID) out, _ = capsys.readouterr() assert "Bonjour le monde" in out diff --git a/samples/snippets/translate_v3_translate_text_with_glossary.py b/samples/snippets/translate_v3_translate_text_with_glossary.py index b62d5413..addd737f 100644 --- a/samples/snippets/translate_v3_translate_text_with_glossary.py +++ b/samples/snippets/translate_v3_translate_text_with_glossary.py @@ -32,8 +32,7 @@ def translate_text_with_glossary( project_id, "us-central1", glossary_id # The location of the glossary ) - glossary_config = translate.TranslateTextGlossaryConfig( - glossary=glossary) + glossary_config = translate.TranslateTextGlossaryConfig(glossary=glossary) # Supported language codes: https://cloud.google.com/translate/docs/languages response = client.translate_text( @@ -42,13 +41,13 @@ def translate_text_with_glossary( "target_language_code": "ja", "source_language_code": "en", "parent": parent, - "glossary_config": glossary_config + "glossary_config": glossary_config, } ) print("Translated text: \n") for translation in response.glossary_translations: - print(u"\t {}".format(translation.translated_text)) + print("\t {}".format(translation.translated_text)) # [END translate_v3_translate_text_with_glossary] diff --git a/samples/snippets/translate_v3_translate_text_with_glossary_test.py b/samples/snippets/translate_v3_translate_text_with_glossary_test.py index 1caa9e6e..46724dde 100644 --- a/samples/snippets/translate_v3_translate_text_with_glossary_test.py +++ b/samples/snippets/translate_v3_translate_text_with_glossary_test.py @@ -46,11 +46,11 @@ def glossary(): ) def delete_glossary(): try: - translate_v3_delete_glossary.delete_glossary( - PROJECT_ID, glossary_id) + translate_v3_delete_glossary.delete_glossary(PROJECT_ID, glossary_id) except NotFound as e: # Ignoring this case. print("Got NotFound, detail: {}".format(str(e))) + delete_glossary() diff --git a/samples/snippets/translate_v3_translate_text_with_model.py b/samples/snippets/translate_v3_translate_text_with_model.py index 44c92f96..bd61bb76 100644 --- a/samples/snippets/translate_v3_translate_text_with_model.py +++ b/samples/snippets/translate_v3_translate_text_with_model.py @@ -40,11 +40,10 @@ def translate_text_with_model( "parent": parent, "mime_type": "text/plain", # mime types: text/plain, text/html } - ) # Display the translation for each input text provided for translation in response.translations: - print(u"Translated text: {}".format(translation.translated_text)) + print("Translated text: {}".format(translation.translated_text)) # [END translate_v3_translate_text_with_model] 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/setup.py b/setup.py index 047a6707..9045fdcf 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ name = "google-cloud-translate" description = "Google Cloud Translation API client library" -version = "3.0.1" +version = "3.0.2" # Should be one of: # 'Development Status :: 3 - Alpha' # 'Development Status :: 4 - Beta' diff --git a/synth.metadata b/synth.metadata index 32b3b56a..85cf89ed 100644 --- a/synth.metadata +++ b/synth.metadata @@ -3,22 +3,22 @@ { "git": { "name": ".", - "remote": "git@github.com:danoscarmike/python-translate", - "sha": "2a80f87c6ff441fb81161db8cda1049aa6851cc4" + "remote": "https://github.com/googleapis/python-translate.git", + "sha": "2bc6296122e54bc93804d37411ff7554e2808626" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "5f2f711c91199ba2f609d3f06a2fe22aee4e5be3" + "sha": "f68649c5f26bcff6817c6d21e90dac0fc71fef8e" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "5f2f711c91199ba2f609d3f06a2fe22aee4e5be3" + "sha": "f68649c5f26bcff6817c6d21e90dac0fc71fef8e" } } ], diff --git a/tests/system.py b/tests/system.py index 56b16091..0cf45215 100644 --- a/tests/system.py +++ b/tests/system.py @@ -15,6 +15,7 @@ import os +import pytest import unittest from google.cloud import translate_v2 @@ -32,6 +33,7 @@ class Config(object): CLIENT_V3 = None location = "global" project_id = os.environ["PROJECT_ID"] + use_mtls = os.environ.get("GOOGLE_API_USE_MTLS_ENDPOINT", "never") def setUpModule(): @@ -39,7 +41,15 @@ def setUpModule(): Config.CLIENT_V3 = translate.TranslationServiceClient() +# Only v3/v3beta1 clients have mTLS support, so we need to skip all the +# v2 client tests for mTLS testing. +skip_for_mtls = pytest.mark.skipif( + Config.use_mtls == "always", reason="Skip the v2 client test for mTLS testing" +) + + class TestTranslate(unittest.TestCase): + @skip_for_mtls def test_get_languages(self): result = Config.CLIENT_V2.get_languages() # There are **many** more than 10 languages. @@ -51,6 +61,7 @@ def test_get_languages(self): self.assertEqual(lang_map["lv"], "Latvian") self.assertEqual(lang_map["zu"], "Zulu") + @skip_for_mtls def test_detect_language(self): values = ["takoy", "fa\xe7ade", "s'il vous plait"] detections = Config.CLIENT_V2.detect_language(values) @@ -59,6 +70,7 @@ def test_detect_language(self): self.assertEqual(detections[1]["language"], "fr") self.assertEqual(detections[2]["language"], "fr") + @skip_for_mtls def test_translate(self): values = ["petnaest", "dek kvin", "Me llamo Jeff", "My name is Jeff"] translations = Config.CLIENT_V2.translate( diff --git a/tests/system/test_vpcsc.py b/tests/system/test_vpcsc.py index 814ab875..e3bf098b 100644 --- a/tests/system/test_vpcsc.py +++ b/tests/system/test_vpcsc.py @@ -31,12 +31,12 @@ def client(): @pytest.fixture(scope="module") def parent_inside(client): - return client.location_path(vpcsc_config.project_inside, "us-central1") + return f"projects/{vpcsc_config.project_inside}/locations/us-central1" @pytest.fixture(scope="module") def parent_outside(client): - return client.location_path(vpcsc_config.project_outside, "us-central1") + return f"projects/{vpcsc_config.project_outside}/locations/us-central1" @pytest.fixture(scope="module") @@ -75,26 +75,26 @@ def glossary_outside(glossary_name_outside): @vpcsc_config.skip_unless_inside_vpcsc def test_create_glossary_w_inside(client, parent_inside, glossary_inside): - client.create_glossary(parent_inside, glossary_inside) + client.create_glossary(parent=parent_inside, glossary=glossary_inside) @vpcsc_config.skip_unless_inside_vpcsc def test_create_glossary_w_outside(client, parent_outside, glossary_outside): with pytest.raises(exceptions.PermissionDenied) as exc: - client.create_glossary(parent_outside, glossary_outside) + client.create_glossary(parent=parent_outside, glossary=glossary_outside) assert exc.value.message.startswith(_VPCSC_PROHIBITED_MESSAGE) @vpcsc_config.skip_unless_inside_vpcsc def test_list_glossaries_w_inside(client, parent_inside): - list(client.list_glossaries(parent_inside)) + list(client.list_glossaries(parent=parent_inside)) @vpcsc_config.skip_unless_inside_vpcsc def test_list_glossaries_w_outside(client, parent_outside): with pytest.raises(exceptions.PermissionDenied) as exc: - list(client.list_glossaries(parent_outside)) + list(client.list_glossaries(parent=parent_outside)) assert exc.value.message.startswith(_VPCSC_PROHIBITED_MESSAGE) @@ -102,7 +102,7 @@ def test_list_glossaries_w_outside(client, parent_outside): @vpcsc_config.skip_unless_inside_vpcsc def test_get_glossary_w_inside(client, glossary_name_inside): try: - client.get_glossary(glossary_name_inside) + client.get_glossary(name=glossary_name_inside) except exceptions.NotFound: # no perms issue pass @@ -110,7 +110,7 @@ def test_get_glossary_w_inside(client, glossary_name_inside): @vpcsc_config.skip_unless_inside_vpcsc def test_get_glossary_w_outside(client, glossary_name_outside): with pytest.raises(exceptions.PermissionDenied) as exc: - client.get_glossary(glossary_name_outside) + client.get_glossary(name=glossary_name_outside) assert exc.value.message.startswith(_VPCSC_PROHIBITED_MESSAGE) @@ -118,7 +118,7 @@ def test_get_glossary_w_outside(client, glossary_name_outside): @vpcsc_config.skip_unless_inside_vpcsc def test_delete_glossary_w_inside(client, glossary_name_inside): try: - client.delete_glossary(glossary_name_inside) + client.delete_glossary(name=glossary_name_inside) except exceptions.NotFound: # no perms issue pass @@ -126,7 +126,7 @@ def test_delete_glossary_w_inside(client, glossary_name_inside): @vpcsc_config.skip_unless_inside_vpcsc def test_delete_glossary_w_outside(client, glossary_name_outside): with pytest.raises(exceptions.PermissionDenied) as exc: - client.delete_glossary(glossary_name_outside) + client.delete_glossary(name=glossary_name_outside) assert exc.value.message.startswith(_VPCSC_PROHIBITED_MESSAGE) @@ -140,11 +140,13 @@ def test_batch_translate_text_w_inside(client, parent_inside): "gcs_destination": {"output_uri_prefix": "gs://fake-bucket/output/"} } client.batch_translate_text( # no perms issue - parent_inside, - source_language_code, - target_language_codes, - input_configs, - output_config, + request={ + "parent": parent_inside, + "source_language_code": source_language_code, + "target_language_codes": target_language_codes, + "input_configs": input_configs, + "output_config": output_config, + } ) @@ -158,11 +160,13 @@ def test_batch_translate_text_w_outside(client, parent_outside): } with pytest.raises(exceptions.PermissionDenied) as exc: client.batch_translate_text( - parent_outside, - source_language_code, - target_language_codes, - input_configs, - output_config, + request={ + "parent": parent_outside, + "source_language_code": source_language_code, + "target_language_codes": target_language_codes, + "input_configs": input_configs, + "output_config": output_config, + } ) assert exc.value.message.startswith(_VPCSC_PROHIBITED_MESSAGE) diff --git a/tests/unit/gapic/translate_v3/test_translation_service.py b/tests/unit/gapic/translate_v3/test_translation_service.py index 556a93a2..c296227c 100644 --- a/tests/unit/gapic/translate_v3/test_translation_service.py +++ b/tests/unit/gapic/translate_v3/test_translation_service.py @@ -31,7 +31,7 @@ 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 @@ -165,14 +165,14 @@ def test_translation_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "never". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "never"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -181,14 +181,14 @@ def test_translation_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "always". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "always"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -197,90 +197,185 @@ def test_translation_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + 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. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + 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", + [ + ( + TranslationServiceClient, + transports.TranslationServiceGrpcTransport, + "grpc", + "true", + ), + ( + TranslationServiceAsyncClient, + transports.TranslationServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + ( + TranslationServiceClient, + transports.TranslationServiceGrpcTransport, + "grpc", + "false", + ), + ( + TranslationServiceAsyncClient, + transports.TranslationServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + TranslationServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TranslationServiceClient), +) +@mock.patch.object( + TranslationServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TranslationServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_translation_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): options = client_options.ClientOptions( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=client_cert_source_callback, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and default_client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): - with mock.patch.object(transport_class, "__init__") as patched: + ssl_channel_creds = mock.Mock() with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=True, + "grpc.ssl_channel_credentials", return_value=ssl_channel_creds ): patched.return_value = None - client = client_class() + 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=client.DEFAULT_MTLS_ENDPOINT, + host=expected_host, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=expected_ssl_channel_creds, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", but client_cert_source and default_client_cert_source are None. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=False, + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None ): - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has - # unsupported value. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "Unsupported"}): - with pytest.raises(MutualTLSChannelError): - client = client_class() - - # Check the case quota_project_id is provided - options = client_options.ClientOptions(quota_project_id="octopus") - with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id="octopus", - ) + 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( @@ -307,9 +402,9 @@ def test_translation_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -337,9 +432,9 @@ def test_translation_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -356,9 +451,9 @@ def test_translation_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -1639,8 +1734,8 @@ def test_list_glossaries_pages(): RuntimeError, ) pages = list(client.list_glossaries(request={}).pages) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.asyncio @@ -1726,10 +1821,10 @@ async def test_list_glossaries_async_pages(): RuntimeError, ) pages = [] - async for page in (await client.list_glossaries(request={})).pages: - pages.append(page) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + async for page_ in (await client.list_glossaries(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token def test_get_glossary( @@ -2182,6 +2277,21 @@ def test_transport_get_channel(): assert channel +@pytest.mark.parametrize( + "transport_class", + [ + transports.TranslationServiceGrpcTransport, + transports.TranslationServiceGrpcAsyncIOTransport, + ], +) +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 = TranslationServiceClient(credentials=credentials.AnonymousCredentials(),) @@ -2251,6 +2361,17 @@ def test_translation_service_base_transport_with_credentials_file(): ) +def test_translation_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.translate_v3.services.translation_service.transports.TranslationServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.TranslationServiceTransport() + adc.assert_called_once() + + def test_translation_service_auth_adc(): # If no credentials are provided, we should use ADC credentials. with mock.patch.object(auth, "default") as adc: @@ -2305,191 +2426,116 @@ def test_translation_service_host_with_port(): def test_translation_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.TranslationServiceGrpcTransport( - 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_translation_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.TranslationServiceGrpcAsyncIOTransport( - 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_translation_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.TranslationServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-translation", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_translation_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.TranslationServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-translation", - ), - 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.TranslationServiceGrpcTransport, + transports.TranslationServiceGrpcAsyncIOTransport, + ], ) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_translation_service_grpc_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint +def test_translation_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.TranslationServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-translation", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - 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", + "https://www.googleapis.com/auth/cloud-translation", + ), + 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.TranslationServiceGrpcTransport, + transports.TranslationServiceGrpcAsyncIOTransport, + ], ) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_translation_service_grpc_asyncio_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. +def test_translation_service_transport_channel_mtls_with_adc(transport_class): mock_ssl_cred = mock.Mock() with mock.patch.multiple( "google.auth.transport.grpc.SslCredentials", __init__=mock.Mock(return_value=None), ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): - mock_cred = mock.Mock() - transport = transports.TranslationServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-translation", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - 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", + "https://www.googleapis.com/auth/cloud-translation", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel def test_translation_service_grpc_lro_client(): @@ -2541,3 +2587,24 @@ def test_parse_glossary_path(): # Check that the path construction is reversible. actual = TranslationServiceClient.parse_glossary_path(path) assert expected == actual + + +def test_client_withDEFAULT_CLIENT_INFO(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.TranslationServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = TranslationServiceClient( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.TranslationServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = TranslationServiceClient.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/translate_v3beta1/test_translation_service.py b/tests/unit/gapic/translate_v3beta1/test_translation_service.py index 1dd49ee1..5af7b5c2 100644 --- a/tests/unit/gapic/translate_v3beta1/test_translation_service.py +++ b/tests/unit/gapic/translate_v3beta1/test_translation_service.py @@ -31,7 +31,7 @@ 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 @@ -165,14 +165,14 @@ def test_translation_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "never". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "never"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -181,14 +181,14 @@ def test_translation_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "always". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "always"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -197,90 +197,185 @@ def test_translation_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + 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. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + 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", + [ + ( + TranslationServiceClient, + transports.TranslationServiceGrpcTransport, + "grpc", + "true", + ), + ( + TranslationServiceAsyncClient, + transports.TranslationServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + ( + TranslationServiceClient, + transports.TranslationServiceGrpcTransport, + "grpc", + "false", + ), + ( + TranslationServiceAsyncClient, + transports.TranslationServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + TranslationServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TranslationServiceClient), +) +@mock.patch.object( + TranslationServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TranslationServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_translation_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): options = client_options.ClientOptions( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=client_cert_source_callback, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and default_client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): - with mock.patch.object(transport_class, "__init__") as patched: + ssl_channel_creds = mock.Mock() with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=True, + "grpc.ssl_channel_credentials", return_value=ssl_channel_creds ): patched.return_value = None - client = client_class() + 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=client.DEFAULT_MTLS_ENDPOINT, + host=expected_host, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=expected_ssl_channel_creds, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", but client_cert_source and default_client_cert_source are None. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=False, + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None ): - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has - # unsupported value. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "Unsupported"}): - with pytest.raises(MutualTLSChannelError): - client = client_class() - - # Check the case quota_project_id is provided - options = client_options.ClientOptions(quota_project_id="octopus") - with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id="octopus", - ) + 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( @@ -307,9 +402,9 @@ def test_translation_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -337,9 +432,9 @@ def test_translation_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -356,9 +451,9 @@ def test_translation_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -1520,8 +1615,8 @@ def test_list_glossaries_pages(): RuntimeError, ) pages = list(client.list_glossaries(request={}).pages) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.asyncio @@ -1607,10 +1702,10 @@ async def test_list_glossaries_async_pages(): RuntimeError, ) pages = [] - async for page in (await client.list_glossaries(request={})).pages: - pages.append(page) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + async for page_ in (await client.list_glossaries(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token def test_get_glossary( @@ -2063,6 +2158,21 @@ def test_transport_get_channel(): assert channel +@pytest.mark.parametrize( + "transport_class", + [ + transports.TranslationServiceGrpcTransport, + transports.TranslationServiceGrpcAsyncIOTransport, + ], +) +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 = TranslationServiceClient(credentials=credentials.AnonymousCredentials(),) @@ -2132,6 +2242,17 @@ def test_translation_service_base_transport_with_credentials_file(): ) +def test_translation_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.translate_v3beta1.services.translation_service.transports.TranslationServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.TranslationServiceTransport() + adc.assert_called_once() + + def test_translation_service_auth_adc(): # If no credentials are provided, we should use ADC credentials. with mock.patch.object(auth, "default") as adc: @@ -2186,191 +2307,116 @@ def test_translation_service_host_with_port(): def test_translation_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.TranslationServiceGrpcTransport( - 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_translation_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.TranslationServiceGrpcAsyncIOTransport( - 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_translation_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.TranslationServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-translation", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_translation_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.TranslationServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-translation", - ), - 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.TranslationServiceGrpcTransport, + transports.TranslationServiceGrpcAsyncIOTransport, + ], ) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_translation_service_grpc_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint +def test_translation_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.TranslationServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-translation", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - 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", + "https://www.googleapis.com/auth/cloud-translation", + ), + 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.TranslationServiceGrpcTransport, + transports.TranslationServiceGrpcAsyncIOTransport, + ], ) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_translation_service_grpc_asyncio_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. +def test_translation_service_transport_channel_mtls_with_adc(transport_class): mock_ssl_cred = mock.Mock() with mock.patch.multiple( "google.auth.transport.grpc.SslCredentials", __init__=mock.Mock(return_value=None), ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): - mock_cred = mock.Mock() - transport = transports.TranslationServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/cloud-translation", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - 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", + "https://www.googleapis.com/auth/cloud-translation", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel def test_translation_service_grpc_lro_client(): @@ -2422,3 +2468,24 @@ def test_parse_glossary_path(): # Check that the path construction is reversible. actual = TranslationServiceClient.parse_glossary_path(path) assert expected == actual + + +def test_client_withDEFAULT_CLIENT_INFO(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.TranslationServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = TranslationServiceClient( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.TranslationServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = TranslationServiceClient.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/v2/test__http.py b/tests/unit/v2/test__http.py index 4830c0c8..c48cc4bb 100644 --- a/tests/unit/v2/test__http.py +++ b/tests/unit/v2/test__http.py @@ -28,30 +28,56 @@ def _make_one(self, *args, **kw): return self._get_target_class()(*args, **kw) def test_build_api_url_no_extra_query_params(self): + from six.moves.urllib.parse import parse_qsl + from six.moves.urllib.parse import urlsplit + conn = self._make_one(object()) - URI = "/".join( - [ - conn.DEFAULT_API_ENDPOINT, - "language", - "translate", - conn.API_VERSION, - "foo", - ] + uri = conn.build_api_url("/foo") + scheme, netloc, path, qs, _ = urlsplit(uri) + self.assertEqual("%s://%s" % (scheme, netloc), conn.API_BASE_URL) + self.assertEqual( + path, "/".join(["", "language", "translate", conn.API_VERSION, "foo"]) ) - self.assertEqual(conn.build_api_url("/foo"), URI) + parms = dict(parse_qsl(qs)) + pretty_print = parms.pop("prettyPrint", "false") + self.assertEqual(pretty_print, "false") + self.assertEqual(parms, {}) def test_build_api_url_w_custom_endpoint(self): + from six.moves.urllib.parse import parse_qsl + from six.moves.urllib.parse import urlsplit + custom_endpoint = "https://foo-translation.googleapis.com" conn = self._make_one(object(), api_endpoint=custom_endpoint) - URI = "/".join( - [custom_endpoint, "language", "translate", conn.API_VERSION, "foo"] + uri = conn.build_api_url("/foo") + scheme, netloc, path, qs, _ = urlsplit(uri) + self.assertEqual("%s://%s" % (scheme, netloc), custom_endpoint) + self.assertEqual( + path, "/".join(["", "language", "translate", conn.API_VERSION, "foo"]) ) - self.assertEqual(conn.build_api_url("/foo"), URI) + parms = dict(parse_qsl(qs)) + pretty_print = parms.pop("prettyPrint", "false") + self.assertEqual(pretty_print, "false") + self.assertEqual(parms, {}) def test_build_api_url_w_extra_query_params(self): from six.moves.urllib.parse import parse_qsl from six.moves.urllib.parse import urlsplit + conn = self._make_one(object()) + uri = conn.build_api_url("/foo", {"bar": "baz"}) + scheme, netloc, path, qs, _ = urlsplit(uri) + self.assertEqual("%s://%s" % (scheme, netloc), conn.API_BASE_URL) + self.assertEqual( + path, "/".join(["", "language", "translate", conn.API_VERSION, "foo"]) + ) + parms = dict(parse_qsl(qs)) + self.assertEqual(parms["bar"], "baz") + + def test_build_api_url_w_extra_query_params_tuple(self): + from six.moves.urllib.parse import parse_qsl + from six.moves.urllib.parse import urlsplit + conn = self._make_one(object()) query_params = [("q", "val1"), ("q", "val2")] uri = conn.build_api_url("/foo", query_params=query_params) @@ -59,8 +85,11 @@ def test_build_api_url_w_extra_query_params(self): self.assertEqual("%s://%s" % (scheme, netloc), conn.API_BASE_URL) expected_path = "/".join(["", "language", "translate", conn.API_VERSION, "foo"]) self.assertEqual(path, expected_path) - params = parse_qsl(qs) - self.assertEqual(params, query_params) + params = list( + sorted(param for param in parse_qsl(qs) if param[0] != "prettyPrint") + ) + expected_params = [("q", "val1"), ("q", "val2")] + self.assertEqual(params, expected_params) def test_extra_headers(self): import requests