diff --git a/.coveragerc b/.coveragerc index b08e5d0b..081506dd 100644 --- a/.coveragerc +++ b/.coveragerc @@ -4,6 +4,8 @@ branch = True [report] show_missing = True omit = + google/__init__.py + google/cloud/__init__.py google/cloud/translate/__init__.py exclude_lines = # Re-enable the standard pragma diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index cb89b2e3..b668c04d 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -1,3 +1,17 @@ +# Copyright 2022 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. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:ec49167c606648a063d1222220b48119c912562849a0528f35bfb592a9f72737 + digest: sha256:ed1f9983d5a935a89fe8085e8bb97d94e41015252c5b6c9771257cf8624367e6 + diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cdf66106..62aced93 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,12 +3,10 @@ # # For syntax help see: # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax +# Note: This file is autogenerated. To make changes to the codeowner team, please update .repo-metadata.json. -# The @googleapis/yoshi-python is the default owner for changes in this repo -* @googleapis/yoshi-python +# @googleapis/yoshi-python @googleapis/cdpe-cloudai are the default owners for changes in this repo +* @googleapis/yoshi-python @googleapis/cdpe-cloudai -# The @googleapis/cdpe-cloudai own the samples -/samples/**/*.py @googleapis/cdpe-cloudai - -# The python-samples-owners team is the default owner for samples -/samples/**/*.py @aribray @googleapis/python-samples-owners \ No newline at end of file +# @googleapis/python-samples-reviewers @googleapis/cdpe-cloudai are the default owners for samples changes +/samples/ @googleapis/python-samples-reviewers @googleapis/cdpe-cloudai diff --git a/.github/release-please.yml b/.github/release-please.yml index 4507ad05..466597e5 100644 --- a/.github/release-please.yml +++ b/.github/release-please.yml @@ -1 +1,2 @@ releaseType: python +handleGHRelease: true diff --git a/.github/release-trigger.yml b/.github/release-trigger.yml new file mode 100644 index 00000000..d4ca9418 --- /dev/null +++ b/.github/release-trigger.yml @@ -0,0 +1 @@ +enabled: true diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index 3e98ae70..37438d33 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -10,6 +10,5 @@ branchProtectionRules: - 'Kokoro' - 'cla/google' - 'Samples - Lint' - - 'Samples - Python 3.6' - 'Samples - Python 3.7' - 'Samples - Python 3.8' diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..f7b8344c --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,38 @@ +on: + pull_request: + branches: + - main +name: docs +jobs: + docs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: "3.10" + - name: Install nox + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install nox + - name: Run docs + run: | + nox -s docs + docfx: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: "3.10" + - name: Install nox + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install nox + - name: Run docfx + run: | + nox -s docfx diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..1e8b05c3 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,25 @@ +on: + pull_request: + branches: + - main +name: lint +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: "3.10" + - name: Install nox + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install nox + - name: Run lint + run: | + nox -s lint + - name: Run lint_setup_py + run: | + nox -s lint_setup_py diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml new file mode 100644 index 00000000..074ee250 --- /dev/null +++ b/.github/workflows/unittest.yml @@ -0,0 +1,57 @@ +on: + pull_request: + branches: + - main +name: unittest +jobs: + unit: + runs-on: ubuntu-latest + strategy: + matrix: + python: ['3.6', '3.7', '3.8', '3.9', '3.10'] + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + - name: Install nox + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install nox + - name: Run unit tests + env: + COVERAGE_FILE: .coverage-${{ matrix.python }} + run: | + nox -s unit-${{ matrix.python }} + - name: Upload coverage results + uses: actions/upload-artifact@v2 + with: + name: coverage-artifacts + path: .coverage-${{ matrix.python }} + + cover: + runs-on: ubuntu-latest + needs: + - unit + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: "3.10" + - name: Install coverage + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install coverage + - name: Download coverage results + uses: actions/download-artifact@v2 + with: + name: coverage-artifacts + path: .coverage-results/ + - name: Report coverage results + run: | + coverage combine .coverage-results/.coverage* + coverage report --show-missing --fail-under=100 diff --git a/.kokoro/release.sh b/.kokoro/release.sh index a47e93f4..64f8b6a3 100755 --- a/.kokoro/release.sh +++ b/.kokoro/release.sh @@ -26,7 +26,7 @@ python3 -m pip install --upgrade twine wheel setuptools export PYTHONUNBUFFERED=1 # Move into the package, build the distribution and upload. -TWINE_PASSWORD=$(cat "${KOKORO_GFILE_DIR}/secret_manager/google-cloud-pypi-token") +TWINE_PASSWORD=$(cat "${KOKORO_KEYSTORE_DIR}/73713_google-cloud-pypi-token-keystore-1") cd github/python-translate python3 setup.py sdist bdist_wheel twine upload --username __token__ --password "${TWINE_PASSWORD}" dist/* diff --git a/.kokoro/release/common.cfg b/.kokoro/release/common.cfg index 11473f0a..9c7e0965 100644 --- a/.kokoro/release/common.cfg +++ b/.kokoro/release/common.cfg @@ -23,8 +23,18 @@ env_vars: { value: "github/python-translate/.kokoro/release.sh" } +# Fetch PyPI password +before_action { + fetch_keystore { + keystore_resource { + keystore_config_id: 73713 + keyname: "google-cloud-pypi-token-keystore-1" + } + } +} + # 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,google-cloud-pypi-token" + value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" } diff --git a/.repo-metadata.json b/.repo-metadata.json index ba8cb1ea..58ab0eba 100644 --- a/.repo-metadata.json +++ b/.repo-metadata.json @@ -2,9 +2,9 @@ "name": "translation", "name_pretty": "Cloud Translation", "product_documentation": "https://cloud.google.com/translate/docs/", - "client_documentation": "https://googleapis.dev/python/translation/latest", + "client_documentation": "https://cloud.google.com/python/docs/reference/translation/latest", "issue_tracker": "https://issuetracker.google.com/savedsearches/559749", - "release_level": "ga", + "release_level": "stable", "language": "python", "library_type": "GAPIC_COMBO", "repo": "googleapis/python-translate", @@ -12,5 +12,6 @@ "api_id": "translation.googleapis.com", "requires_billing": true, "default_version": "v3", - "codeowner_team": "@googleapis/cdpe-cloudai" + "codeowner_team": "@googleapis/cdpe-cloudai", + "api_shortname": "translation" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 02481558..887411b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ [1]: https://pypi.org/project/google-cloud-translate/#history +## [3.7.0](https://github.com/googleapis/python-translate/compare/v3.6.1...v3.7.0) (2022-02-03) + + +### Features + +* add api key support ([#329](https://github.com/googleapis/python-translate/issues/329)) ([4b08cd5](https://github.com/googleapis/python-translate/commit/4b08cd56ce230b843ced78a3f81c2e6511ac2a4f)) + + +### Bug Fixes + +* 290 added a create glossary line before each call using bistro-glossary ([#302](https://github.com/googleapis/python-translate/issues/302)) ([742e414](https://github.com/googleapis/python-translate/commit/742e414ad8ac83e4116c67740a42e264a63e3287)) +* resolve DuplicateCredentialArgs error when using credentials_file ([26791c2](https://github.com/googleapis/python-translate/commit/26791c251e851f921d23316e6ca5adab648c63c0)) + ### [3.6.1](https://www.github.com/googleapis/python-translate/compare/v3.6.0...v3.6.1) (2021-11-04) diff --git a/README.rst b/README.rst index 5fba1db0..6fd7df02 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ Cloud Translation is available as a paid service. See the `Pricing`_ and .. _Google Cloud Translation: https://cloud.google.com/translate/ .. _Pricing: https://cloud.google.com/translate/pricing .. _FAQ: https://cloud.google.com/translate/faq -.. _Client Library Documentation: https://googleapis.dev/python/translation/latest +.. _Client Library Documentation: https://cloud.google.com/python/docs/reference/translation/latest .. _Product Documentation: https://cloud.google.com/translate/docs Quick Start 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 834a7e34..f1fa93b9 100644 --- a/google/cloud/translate_v3/services/translation_service/async_client.py +++ b/google/cloud/translate_v3/services/translation_service/async_client.py @@ -16,17 +16,20 @@ from collections import OrderedDict import functools import re -from typing import Dict, Sequence, Tuple, Type, Union +from typing import Dict, Optional, Sequence, Tuple, Type, Union import pkg_resources -from google.api_core.client_options import ClientOptions # type: ignore -from google.api_core import exceptions as core_exceptions # type: ignore -from google.api_core import gapic_v1 # type: ignore -from google.api_core import retry as retries # type: ignore +from google.api_core.client_options import ClientOptions +from google.api_core import exceptions as core_exceptions +from google.api_core import gapic_v1 +from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore from google.oauth2 import service_account # type: ignore -OptionalRetry = Union[retries.Retry, object] +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore from google.api_core import operation # type: ignore from google.api_core import operation_async # type: ignore @@ -106,6 +109,42 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): from_service_account_json = from_service_account_file + @classmethod + def get_mtls_endpoint_and_cert_source( + cls, client_options: Optional[ClientOptions] = None + ): + """Return the API endpoint and client cert source for mutual TLS. + + The client cert source is determined in the following order: + (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the + client cert source is None. + (2) if `client_options.client_cert_source` is provided, use the provided one; if the + default client cert source exists, use the default one; otherwise the client cert + source is None. + + The API endpoint is determined in the following order: + (1) if `client_options.api_endpoint` if provided, use the provided one. + (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the + default mTLS endpoint; if the environment variabel is "never", use the default API + endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise + use the default API endpoint. + + More details can be found at https://google.aip.dev/auth/4114. + + Args: + client_options (google.api_core.client_options.ClientOptions): Custom options for the + client. Only the `api_endpoint` and `client_cert_source` properties may be used + in this method. + + Returns: + Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the + client cert source to use. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If any errors happen. + """ + return TranslationServiceClient.get_mtls_endpoint_and_cert_source(client_options) # type: ignore + @property def transport(self) -> TranslationServiceTransport: """Returns the transport used by the client instance. @@ -282,7 +321,7 @@ async def translate_text( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any( [ @@ -418,7 +457,7 @@ async def detect_language( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([parent, model, mime_type, content]) if request is not None and has_flattened_params: @@ -539,7 +578,7 @@ async def get_supported_languages( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([parent, model, display_language_code]) if request is not None and has_flattened_params: @@ -805,7 +844,7 @@ async def batch_translate_document( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any( [ @@ -907,7 +946,7 @@ async def create_glossary( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([parent, glossary]) if request is not None and has_flattened_params: @@ -991,7 +1030,7 @@ async def list_glossaries( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([parent]) if request is not None and has_flattened_params: @@ -1078,7 +1117,7 @@ async def get_glossary( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([name]) if request is not None and has_flattened_params: @@ -1163,7 +1202,7 @@ async def delete_glossary( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([name]) if request is not None and has_flattened_params: diff --git a/google/cloud/translate_v3/services/translation_service/client.py b/google/cloud/translate_v3/services/translation_service/client.py index 625b4ea0..420622d7 100644 --- a/google/cloud/translate_v3/services/translation_service/client.py +++ b/google/cloud/translate_v3/services/translation_service/client.py @@ -14,23 +14,25 @@ # limitations under the License. # from collections import OrderedDict -from distutils import util import os import re from typing import Dict, Optional, Sequence, Tuple, Type, Union import pkg_resources -from google.api_core import client_options as client_options_lib # type: ignore -from google.api_core import exceptions as core_exceptions # type: ignore -from google.api_core import gapic_v1 # type: ignore -from google.api_core import retry as retries # type: ignore +from google.api_core import client_options as client_options_lib +from google.api_core import exceptions as core_exceptions +from google.api_core import gapic_v1 +from google.api_core import retry as retries from google.auth import credentials as ga_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 -OptionalRetry = Union[retries.Retry, object] +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore from google.api_core import operation # type: ignore from google.api_core import operation_async # type: ignore @@ -237,6 +239,73 @@ def parse_common_location_path(path: str) -> Dict[str, str]: m = re.match(r"^projects/(?P.+?)/locations/(?P.+?)$", path) return m.groupdict() if m else {} + @classmethod + def get_mtls_endpoint_and_cert_source( + cls, client_options: Optional[client_options_lib.ClientOptions] = None + ): + """Return the API endpoint and client cert source for mutual TLS. + + The client cert source is determined in the following order: + (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the + client cert source is None. + (2) if `client_options.client_cert_source` is provided, use the provided one; if the + default client cert source exists, use the default one; otherwise the client cert + source is None. + + The API endpoint is determined in the following order: + (1) if `client_options.api_endpoint` if provided, use the provided one. + (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the + default mTLS endpoint; if the environment variabel is "never", use the default API + endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise + use the default API endpoint. + + More details can be found at https://google.aip.dev/auth/4114. + + Args: + client_options (google.api_core.client_options.ClientOptions): Custom options for the + client. Only the `api_endpoint` and `client_cert_source` properties may be used + in this method. + + Returns: + Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the + client cert source to use. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If any errors happen. + """ + if client_options is None: + client_options = client_options_lib.ClientOptions() + use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") + if use_client_cert not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + if use_mtls_endpoint not in ("auto", "never", "always"): + raise MutualTLSChannelError( + "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" + ) + + # Figure out the client cert source to use. + client_cert_source = None + if use_client_cert == "true": + if client_options.client_cert_source: + client_cert_source = client_options.client_cert_source + elif mtls.has_default_client_cert_source(): + client_cert_source = mtls.default_client_cert_source() + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + elif use_mtls_endpoint == "always" or ( + use_mtls_endpoint == "auto" and client_cert_source + ): + api_endpoint = cls.DEFAULT_MTLS_ENDPOINT + else: + api_endpoint = cls.DEFAULT_ENDPOINT + + return api_endpoint, client_cert_source + def __init__( self, *, @@ -287,50 +356,22 @@ def __init__( if client_options is None: client_options = client_options_lib.ClientOptions() - # Create SSL credentials for mutual TLS if needed. - use_client_cert = bool( - util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source( + client_options ) - client_cert_source_func = None - is_mtls = False - if use_client_cert: - if client_options.client_cert_source: - is_mtls = True - client_cert_source_func = client_options.client_cert_source - else: - is_mtls = mtls.has_default_client_cert_source() - if is_mtls: - client_cert_source_func = mtls.default_client_cert_source() - else: - client_cert_source_func = None - - # Figure out which api endpoint to use. - if client_options.api_endpoint is not None: - api_endpoint = client_options.api_endpoint - else: - use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") - if use_mtls_env == "never": - api_endpoint = self.DEFAULT_ENDPOINT - elif use_mtls_env == "always": - api_endpoint = self.DEFAULT_MTLS_ENDPOINT - elif use_mtls_env == "auto": - if is_mtls: - api_endpoint = self.DEFAULT_MTLS_ENDPOINT - else: - api_endpoint = self.DEFAULT_ENDPOINT - else: - raise MutualTLSChannelError( - "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted " - "values: never, auto, always" - ) + api_key_value = getattr(client_options, "api_key", None) + if api_key_value and credentials: + raise ValueError( + "client_options.api_key and credentials are mutually exclusive" + ) # Save or instantiate the transport. # Ordinarily, we provide the transport, but allowing a custom transport # instance provides an extensibility point for unusual situations. if isinstance(transport, TranslationServiceTransport): # transport is a TranslationServiceTransport instance. - if credentials or client_options.credentials_file: + if credentials or client_options.credentials_file or api_key_value: raise ValueError( "When providing a transport instance, " "provide its credentials directly." @@ -342,6 +383,15 @@ def __init__( ) self._transport = transport else: + import google.auth._default # type: ignore + + if api_key_value and hasattr( + google.auth._default, "get_api_key_credentials" + ): + credentials = google.auth._default.get_api_key_credentials( + api_key_value + ) + Transport = type(self).get_transport_class(transport) self._transport = Transport( credentials=credentials, @@ -469,7 +519,7 @@ def translate_text( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any( [ @@ -605,7 +655,7 @@ def detect_language( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([parent, model, mime_type, content]) if request is not None and has_flattened_params: @@ -726,7 +776,7 @@ def get_supported_languages( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([parent, model, display_language_code]) if request is not None and has_flattened_params: @@ -984,7 +1034,7 @@ def batch_translate_document( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any( [ @@ -1086,7 +1136,7 @@ def create_glossary( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([parent, glossary]) if request is not None and has_flattened_params: @@ -1170,7 +1220,7 @@ def list_glossaries( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([parent]) if request is not None and has_flattened_params: @@ -1247,7 +1297,7 @@ def get_glossary( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([name]) if request is not None and has_flattened_params: @@ -1322,7 +1372,7 @@ def delete_glossary( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([name]) if request is not None and has_flattened_params: 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 759772df..2ab577dc 100644 --- a/google/cloud/translate_v3/services/translation_service/transports/base.py +++ b/google/cloud/translate_v3/services/translation_service/transports/base.py @@ -18,11 +18,11 @@ import pkg_resources import google.auth # type: ignore -import google.api_core # type: ignore -from google.api_core import exceptions as core_exceptions # type: ignore -from google.api_core import gapic_v1 # type: ignore -from google.api_core import retry as retries # type: ignore -from google.api_core import operations_v1 # type: ignore +import google.api_core +from google.api_core import exceptions as core_exceptions +from google.api_core import gapic_v1 +from google.api_core import retry as retries +from google.api_core import operations_v1 from google.auth import credentials as ga_credentials # type: ignore from google.oauth2 import service_account # type: ignore @@ -104,7 +104,6 @@ def __init__( credentials, _ = google.auth.load_credentials_from_file( credentials_file, **scopes_kwargs, quota_project_id=quota_project_id ) - elif credentials is None: credentials, _ = google.auth.default( **scopes_kwargs, quota_project_id=quota_project_id 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 15d9988f..9beb6d58 100644 --- a/google/cloud/translate_v3/services/translation_service/transports/grpc.py +++ b/google/cloud/translate_v3/services/translation_service/transports/grpc.py @@ -16,9 +16,9 @@ import warnings from typing import Callable, Dict, Optional, Sequence, Tuple, Union -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.api_core import grpc_helpers +from google.api_core import operations_v1 +from google.api_core import gapic_v1 import google.auth # type: ignore from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -161,8 +161,11 @@ def __init__( if not self._grpc_channel: self._grpc_channel = type(self).create_channel( self._host, + # use the credentials which are saved credentials=self._credentials, - credentials_file=credentials_file, + # Set ``credentials_file`` to ``None`` here as + # the credentials that we saved earlier should be used. + credentials_file=None, scopes=self._scopes, ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, @@ -235,7 +238,7 @@ def operations_client(self) -> operations_v1.OperationsClient: This property caches on the instance; repeated calls return the same client. """ - # Sanity check: Only create a new client if we do not already have one. + # Quick check: Only create a new client if we do not already have one. if self._operations_client is None: self._operations_client = operations_v1.OperationsClient(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 5f418f68..c875f91f 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 @@ -16,9 +16,9 @@ import warnings from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union -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.api_core import gapic_v1 +from google.api_core import grpc_helpers_async +from google.api_core import operations_v1 from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -206,8 +206,11 @@ def __init__( if not self._grpc_channel: self._grpc_channel = type(self).create_channel( self._host, + # use the credentials which are saved credentials=self._credentials, - credentials_file=credentials_file, + # Set ``credentials_file`` to ``None`` here as + # the credentials that we saved earlier should be used. + credentials_file=None, scopes=self._scopes, ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, @@ -237,7 +240,7 @@ def operations_client(self) -> operations_v1.OperationsAsyncClient: This property caches on the instance; repeated calls return the same client. """ - # Sanity check: Only create a new client if we do not already have one. + # Quick check: Only create a new client if we do not already have one. if self._operations_client is None: self._operations_client = operations_v1.OperationsAsyncClient( self.grpc_channel diff --git a/google/cloud/translate_v3/types/translation_service.py b/google/cloud/translate_v3/types/translation_service.py index 4e97c94f..8e52d14a 100644 --- a/google/cloud/translate_v3/types/translation_service.py +++ b/google/cloud/translate_v3/types/translation_service.py @@ -76,8 +76,9 @@ class TranslateTextGlossaryConfig(proto.Message): - User provided custom glossary: ``projects/{project-number-or-id}/locations/{location-id}/glossaries/{glossary-id}`` ignore_case (bool): - Optional. Indicates match is case- - nsensitive. Default value is false if missing. + Optional. Indicates match is + case-insensitive. Default value is false if + missing. """ glossary = proto.Field(proto.STRING, number=1,) @@ -565,6 +566,7 @@ class OutputConfig(proto.Message): glossary_error_file will be generated that contains error details. glossary_error_file has format of gs://translation_test/a_b_c\_'trg'_glossary_errors.[extension] + This field is a member of `oneof`_ ``destination``. """ @@ -592,6 +594,7 @@ class DocumentInputConfig(proto.Message): gcs_source (google.cloud.translate_v3.types.GcsSource): Google Cloud Storage location. This must be a single file. For example: gs://example_bucket/example_file.pdf + This field is a member of `oneof`_ ``source``. mime_type (str): Specifies the input document's mime_type. 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 dcd1cc69..8c8c2eeb 100644 --- a/google/cloud/translate_v3beta1/services/translation_service/async_client.py +++ b/google/cloud/translate_v3beta1/services/translation_service/async_client.py @@ -16,17 +16,20 @@ from collections import OrderedDict import functools import re -from typing import Dict, Sequence, Tuple, Type, Union +from typing import Dict, Optional, Sequence, Tuple, Type, Union import pkg_resources -from google.api_core.client_options import ClientOptions # type: ignore -from google.api_core import exceptions as core_exceptions # type: ignore -from google.api_core import gapic_v1 # type: ignore -from google.api_core import retry as retries # type: ignore +from google.api_core.client_options import ClientOptions +from google.api_core import exceptions as core_exceptions +from google.api_core import gapic_v1 +from google.api_core import retry as retries from google.auth import credentials as ga_credentials # type: ignore from google.oauth2 import service_account # type: ignore -OptionalRetry = Union[retries.Retry, object] +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore from google.api_core import operation # type: ignore from google.api_core import operation_async # type: ignore @@ -106,6 +109,42 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): from_service_account_json = from_service_account_file + @classmethod + def get_mtls_endpoint_and_cert_source( + cls, client_options: Optional[ClientOptions] = None + ): + """Return the API endpoint and client cert source for mutual TLS. + + The client cert source is determined in the following order: + (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the + client cert source is None. + (2) if `client_options.client_cert_source` is provided, use the provided one; if the + default client cert source exists, use the default one; otherwise the client cert + source is None. + + The API endpoint is determined in the following order: + (1) if `client_options.api_endpoint` if provided, use the provided one. + (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the + default mTLS endpoint; if the environment variabel is "never", use the default API + endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise + use the default API endpoint. + + More details can be found at https://google.aip.dev/auth/4114. + + Args: + client_options (google.api_core.client_options.ClientOptions): Custom options for the + client. Only the `api_endpoint` and `client_cert_source` properties may be used + in this method. + + Returns: + Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the + client cert source to use. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If any errors happen. + """ + return TranslationServiceClient.get_mtls_endpoint_and_cert_source(client_options) # type: ignore + @property def transport(self) -> TranslationServiceTransport: """Returns the transport used by the client instance. @@ -287,7 +326,7 @@ async def detect_language( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([parent, model, mime_type]) if request is not None and has_flattened_params: @@ -406,7 +445,7 @@ async def get_supported_languages( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([parent, display_language_code, model]) if request is not None and has_flattened_params: @@ -586,11 +625,11 @@ async def batch_translate_document( metadata: Sequence[Tuple[str, str]] = (), ) -> operation_async.AsyncOperation: r"""Translates a large volume of documents in - asynchronous batch mode. This function provides real- - time output as the inputs are being processed. If caller - cancels a request, the partial results (for an input - file, it's all or nothing) may still be available on the - specified output location. + asynchronous batch mode. This function provides + real-time output as the inputs are being processed. If + caller cancels a request, the partial results (for an + input file, it's all or nothing) may still be available + on the specified output location. This call returns immediately and you can use google.longrunning.Operation.name to poll the status of the call. @@ -672,7 +711,7 @@ async def batch_translate_document( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any( [ @@ -774,7 +813,7 @@ async def create_glossary( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([parent, glossary]) if request is not None and has_flattened_params: @@ -893,7 +932,7 @@ async def list_glossaries( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([parent, filter]) if request is not None and has_flattened_params: @@ -982,7 +1021,7 @@ async def get_glossary( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([name]) if request is not None and has_flattened_params: @@ -1067,7 +1106,7 @@ async def delete_glossary( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([name]) if request is not None and has_flattened_params: diff --git a/google/cloud/translate_v3beta1/services/translation_service/client.py b/google/cloud/translate_v3beta1/services/translation_service/client.py index fd17473e..2fb395cb 100644 --- a/google/cloud/translate_v3beta1/services/translation_service/client.py +++ b/google/cloud/translate_v3beta1/services/translation_service/client.py @@ -14,23 +14,25 @@ # limitations under the License. # from collections import OrderedDict -from distutils import util import os import re from typing import Dict, Optional, Sequence, Tuple, Type, Union import pkg_resources -from google.api_core import client_options as client_options_lib # type: ignore -from google.api_core import exceptions as core_exceptions # type: ignore -from google.api_core import gapic_v1 # type: ignore -from google.api_core import retry as retries # type: ignore +from google.api_core import client_options as client_options_lib +from google.api_core import exceptions as core_exceptions +from google.api_core import gapic_v1 +from google.api_core import retry as retries from google.auth import credentials as ga_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 -OptionalRetry = Union[retries.Retry, object] +try: + OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.Retry, object] # type: ignore from google.api_core import operation # type: ignore from google.api_core import operation_async # type: ignore @@ -237,6 +239,73 @@ def parse_common_location_path(path: str) -> Dict[str, str]: m = re.match(r"^projects/(?P.+?)/locations/(?P.+?)$", path) return m.groupdict() if m else {} + @classmethod + def get_mtls_endpoint_and_cert_source( + cls, client_options: Optional[client_options_lib.ClientOptions] = None + ): + """Return the API endpoint and client cert source for mutual TLS. + + The client cert source is determined in the following order: + (1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the + client cert source is None. + (2) if `client_options.client_cert_source` is provided, use the provided one; if the + default client cert source exists, use the default one; otherwise the client cert + source is None. + + The API endpoint is determined in the following order: + (1) if `client_options.api_endpoint` if provided, use the provided one. + (2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the + default mTLS endpoint; if the environment variabel is "never", use the default API + endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise + use the default API endpoint. + + More details can be found at https://google.aip.dev/auth/4114. + + Args: + client_options (google.api_core.client_options.ClientOptions): Custom options for the + client. Only the `api_endpoint` and `client_cert_source` properties may be used + in this method. + + Returns: + Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the + client cert source to use. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If any errors happen. + """ + if client_options is None: + client_options = client_options_lib.ClientOptions() + use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") + use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") + if use_client_cert not in ("true", "false"): + raise ValueError( + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" + ) + if use_mtls_endpoint not in ("auto", "never", "always"): + raise MutualTLSChannelError( + "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" + ) + + # Figure out the client cert source to use. + client_cert_source = None + if use_client_cert == "true": + if client_options.client_cert_source: + client_cert_source = client_options.client_cert_source + elif mtls.has_default_client_cert_source(): + client_cert_source = mtls.default_client_cert_source() + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + elif use_mtls_endpoint == "always" or ( + use_mtls_endpoint == "auto" and client_cert_source + ): + api_endpoint = cls.DEFAULT_MTLS_ENDPOINT + else: + api_endpoint = cls.DEFAULT_ENDPOINT + + return api_endpoint, client_cert_source + def __init__( self, *, @@ -287,50 +356,22 @@ def __init__( if client_options is None: client_options = client_options_lib.ClientOptions() - # Create SSL credentials for mutual TLS if needed. - use_client_cert = bool( - util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source( + client_options ) - client_cert_source_func = None - is_mtls = False - if use_client_cert: - if client_options.client_cert_source: - is_mtls = True - client_cert_source_func = client_options.client_cert_source - else: - is_mtls = mtls.has_default_client_cert_source() - if is_mtls: - client_cert_source_func = mtls.default_client_cert_source() - else: - client_cert_source_func = None - - # Figure out which api endpoint to use. - if client_options.api_endpoint is not None: - api_endpoint = client_options.api_endpoint - else: - use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") - if use_mtls_env == "never": - api_endpoint = self.DEFAULT_ENDPOINT - elif use_mtls_env == "always": - api_endpoint = self.DEFAULT_MTLS_ENDPOINT - elif use_mtls_env == "auto": - if is_mtls: - api_endpoint = self.DEFAULT_MTLS_ENDPOINT - else: - api_endpoint = self.DEFAULT_ENDPOINT - else: - raise MutualTLSChannelError( - "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted " - "values: never, auto, always" - ) + api_key_value = getattr(client_options, "api_key", None) + if api_key_value and credentials: + raise ValueError( + "client_options.api_key and credentials are mutually exclusive" + ) # Save or instantiate the transport. # Ordinarily, we provide the transport, but allowing a custom transport # instance provides an extensibility point for unusual situations. if isinstance(transport, TranslationServiceTransport): # transport is a TranslationServiceTransport instance. - if credentials or client_options.credentials_file: + if credentials or client_options.credentials_file or api_key_value: raise ValueError( "When providing a transport instance, " "provide its credentials directly." @@ -342,6 +383,15 @@ def __init__( ) self._transport = transport else: + import google.auth._default # type: ignore + + if api_key_value and hasattr( + google.auth._default, "get_api_key_credentials" + ): + credentials = google.auth._default.get_api_key_credentials( + api_key_value + ) + Transport = type(self).get_transport_class(transport) self._transport = Transport( credentials=credentials, @@ -475,7 +525,7 @@ def detect_language( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([parent, model, mime_type]) if request is not None and has_flattened_params: @@ -594,7 +644,7 @@ def get_supported_languages( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([parent, display_language_code, model]) if request is not None and has_flattened_params: @@ -766,11 +816,11 @@ def batch_translate_document( metadata: Sequence[Tuple[str, str]] = (), ) -> operation.Operation: r"""Translates a large volume of documents in - asynchronous batch mode. This function provides real- - time output as the inputs are being processed. If caller - cancels a request, the partial results (for an input - file, it's all or nothing) may still be available on the - specified output location. + asynchronous batch mode. This function provides + real-time output as the inputs are being processed. If + caller cancels a request, the partial results (for an + input file, it's all or nothing) may still be available + on the specified output location. This call returns immediately and you can use google.longrunning.Operation.name to poll the status of the call. @@ -852,7 +902,7 @@ def batch_translate_document( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any( [ @@ -954,7 +1004,7 @@ def create_glossary( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([parent, glossary]) if request is not None and has_flattened_params: @@ -1073,7 +1123,7 @@ def list_glossaries( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([parent, filter]) if request is not None and has_flattened_params: @@ -1152,7 +1202,7 @@ def get_glossary( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([name]) if request is not None and has_flattened_params: @@ -1227,7 +1277,7 @@ def delete_glossary( """ # Create or coerce a protobuf request object. - # Sanity check: If we got a request object, we should *not* have + # Quick check: If we got a request object, we should *not* have # gotten any keyword arguments that map to the request. has_flattened_params = any([name]) if request is not None and has_flattened_params: 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 60c51f39..214105b0 100644 --- a/google/cloud/translate_v3beta1/services/translation_service/transports/base.py +++ b/google/cloud/translate_v3beta1/services/translation_service/transports/base.py @@ -18,11 +18,11 @@ import pkg_resources import google.auth # type: ignore -import google.api_core # type: ignore -from google.api_core import exceptions as core_exceptions # type: ignore -from google.api_core import gapic_v1 # type: ignore -from google.api_core import retry as retries # type: ignore -from google.api_core import operations_v1 # type: ignore +import google.api_core +from google.api_core import exceptions as core_exceptions +from google.api_core import gapic_v1 +from google.api_core import retry as retries +from google.api_core import operations_v1 from google.auth import credentials as ga_credentials # type: ignore from google.oauth2 import service_account # type: ignore @@ -104,7 +104,6 @@ def __init__( credentials, _ = google.auth.load_credentials_from_file( credentials_file, **scopes_kwargs, quota_project_id=quota_project_id ) - elif credentials is None: credentials, _ = google.auth.default( **scopes_kwargs, quota_project_id=quota_project_id 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 f1c5b4bf..d11df630 100644 --- a/google/cloud/translate_v3beta1/services/translation_service/transports/grpc.py +++ b/google/cloud/translate_v3beta1/services/translation_service/transports/grpc.py @@ -16,9 +16,9 @@ import warnings from typing import Callable, Dict, Optional, Sequence, Tuple, Union -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.api_core import grpc_helpers +from google.api_core import operations_v1 +from google.api_core import gapic_v1 import google.auth # type: ignore from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -161,8 +161,11 @@ def __init__( if not self._grpc_channel: self._grpc_channel = type(self).create_channel( self._host, + # use the credentials which are saved credentials=self._credentials, - credentials_file=credentials_file, + # Set ``credentials_file`` to ``None`` here as + # the credentials that we saved earlier should be used. + credentials_file=None, scopes=self._scopes, ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, @@ -235,7 +238,7 @@ def operations_client(self) -> operations_v1.OperationsClient: This property caches on the instance; repeated calls return the same client. """ - # Sanity check: Only create a new client if we do not already have one. + # Quick check: Only create a new client if we do not already have one. if self._operations_client is None: self._operations_client = operations_v1.OperationsClient(self.grpc_channel) @@ -404,11 +407,11 @@ def batch_translate_document( r"""Return a callable for the batch translate document method over gRPC. Translates a large volume of documents in - asynchronous batch mode. This function provides real- - time output as the inputs are being processed. If caller - cancels a request, the partial results (for an input - file, it's all or nothing) may still be available on the - specified output location. + asynchronous batch mode. This function provides + real-time output as the inputs are being processed. If + caller cancels a request, the partial results (for an + input file, it's all or nothing) may still be available + on the specified output location. This call returns immediately and you can use google.longrunning.Operation.name to poll the status of the call. 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 b5b4feb9..3aec3e8b 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 @@ -16,9 +16,9 @@ import warnings from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union -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.api_core import gapic_v1 +from google.api_core import grpc_helpers_async +from google.api_core import operations_v1 from google.auth import credentials as ga_credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -206,8 +206,11 @@ def __init__( if not self._grpc_channel: self._grpc_channel = type(self).create_channel( self._host, + # use the credentials which are saved credentials=self._credentials, - credentials_file=credentials_file, + # Set ``credentials_file`` to ``None`` here as + # the credentials that we saved earlier should be used. + credentials_file=None, scopes=self._scopes, ssl_credentials=self._ssl_channel_credentials, quota_project_id=quota_project_id, @@ -237,7 +240,7 @@ def operations_client(self) -> operations_v1.OperationsAsyncClient: This property caches on the instance; repeated calls return the same client. """ - # Sanity check: Only create a new client if we do not already have one. + # Quick check: Only create a new client if we do not already have one. if self._operations_client is None: self._operations_client = operations_v1.OperationsAsyncClient( self.grpc_channel @@ -410,11 +413,11 @@ def batch_translate_document( r"""Return a callable for the batch translate document method over gRPC. Translates a large volume of documents in - asynchronous batch mode. This function provides real- - time output as the inputs are being processed. If caller - cancels a request, the partial results (for an input - file, it's all or nothing) may still be available on the - specified output location. + asynchronous batch mode. This function provides + real-time output as the inputs are being processed. If + caller cancels a request, the partial results (for an + input file, it's all or nothing) may still be available + on the specified output location. This call returns immediately and you can use google.longrunning.Operation.name to poll the status of the call. diff --git a/google/cloud/translate_v3beta1/types/translation_service.py b/google/cloud/translate_v3beta1/types/translation_service.py index 272b82a6..ac51e013 100644 --- a/google/cloud/translate_v3beta1/types/translation_service.py +++ b/google/cloud/translate_v3beta1/types/translation_service.py @@ -71,8 +71,9 @@ class TranslateTextGlossaryConfig(proto.Message): Required. Specifies the glossary used for this translation. Use this format: projects/\ */locations/*/glossaries/\* ignore_case (bool): - Optional. Indicates match is case- - nsensitive. Default value is false if missing. + Optional. Indicates match is + case-insensitive. Default value is false if + missing. """ glossary = proto.Field(proto.STRING, number=1,) @@ -556,6 +557,7 @@ class OutputConfig(proto.Message): glossary_error_file will be generated that contains error details. glossary_error_file has format of gs://translation_test/a_b_c\_'trg'_glossary_errors.[extension] + This field is a member of `oneof`_ ``destination``. """ @@ -583,6 +585,7 @@ class DocumentInputConfig(proto.Message): gcs_source (google.cloud.translate_v3beta1.types.GcsSource): Google Cloud Storage location. This must be a single file. For example: gs://example_bucket/example_file.pdf + This field is a member of `oneof`_ ``source``. mime_type (str): Specifies the input document's mime_type. diff --git a/owlbot.py b/owlbot.py index d960cf99..5114aaf7 100644 --- a/owlbot.py +++ b/owlbot.py @@ -45,15 +45,13 @@ s.remove_staging_dirs() -# work around gapic generator bug -# https://github.com/googleapis/gapic-generator-python/pull/1071 -s.replace( - "google/cloud/**/types/*.py", - """\. - This field is a member of `oneof`_""", - """. - - This field is a member of `oneof`_""" +s.replace(".coveragerc", + """omit = + google/cloud/translate/__init__.py""", + """omit = + google/__init__.py + google/cloud/__init__.py + google/cloud/translate/__init__.py""", ) # ---------------------------------------------------------------------------- diff --git a/samples/AUTHORING_GUIDE.md b/samples/AUTHORING_GUIDE.md index 55c97b32..8249522f 100644 --- a/samples/AUTHORING_GUIDE.md +++ b/samples/AUTHORING_GUIDE.md @@ -1 +1 @@ -See https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/AUTHORING_GUIDE.md \ No newline at end of file +See https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/AUTHORING_GUIDE.md \ No newline at end of file diff --git a/samples/CONTRIBUTING.md b/samples/CONTRIBUTING.md index 34c882b6..f5fe2e6b 100644 --- a/samples/CONTRIBUTING.md +++ b/samples/CONTRIBUTING.md @@ -1 +1 @@ -See https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/CONTRIBUTING.md \ No newline at end of file +See https://github.com/GoogleCloudPlatform/python-docs-samples/blob/main/CONTRIBUTING.md \ No newline at end of file diff --git a/samples/cloud-nebulous-serverless-python/README.md b/samples/cloud-nebulous-serverless-python/README.md deleted file mode 100644 index 9dfab667..00000000 --- a/samples/cloud-nebulous-serverless-python/README.md +++ /dev/null @@ -1 +0,0 @@ -This sample can be found at . diff --git a/samples/cloud-nebulous-serverless/README.md b/samples/cloud-nebulous-serverless/README.md new file mode 100644 index 00000000..e2c82823 --- /dev/null +++ b/samples/cloud-nebulous-serverless/README.md @@ -0,0 +1 @@ +This sample, demonstrating how to access the [Cloud Translation API](https://cloud.google.com/translate) from [Google Cloud serverless platforms](https://cloud.google.com/serverless) (App Engine, Cloud Functions, Cloud Run) can be found at . Versions in Python (2.7 and 3.7+) and Node.js (10+) are available along with hands-on tutorials. diff --git a/samples/snippets/hybrid_glossaries/hybrid_tutorial_test.py b/samples/snippets/hybrid_glossaries/hybrid_tutorial_test.py index 2b4f6559..8f81f325 100644 --- a/samples/snippets/hybrid_glossaries/hybrid_tutorial_test.py +++ b/samples/snippets/hybrid_glossaries/hybrid_tutorial_test.py @@ -58,6 +58,12 @@ def test_translate_standard(): expected_text = "Hello" + # attempt to create glossary, fails if it already exists + languages = ["fr", "en"] + glossary_name = "bistro-glossary" + glossary_uri = f"gs://cloud-samples-data/translation/{glossary_name}.csv" + create_glossary(languages, PROJECT_ID, glossary_name, glossary_uri) + text = translate_text("Bonjour", "fr", "en", PROJECT_ID, "bistro-glossary") assert text == expected_text @@ -68,6 +74,12 @@ def test_translate_glossary(): expected_text = "I eat goat cheese" input_text = "Je mange du chevre" + # attempt to create glossary, fails if it already exists + languages = ["fr", "en"] + glossary_name = "bistro-glossary" + glossary_uri = f"gs://cloud-samples-data/translation/{glossary_name}.csv" + create_glossary(languages, PROJECT_ID, glossary_name, glossary_uri) + text = translate_text(input_text, "fr", "en", PROJECT_ID, "bistro-glossary") assert text == expected_text diff --git a/samples/snippets/hybrid_glossaries/noxfile.py b/samples/snippets/hybrid_glossaries/noxfile.py index 93a9122c..20cdfc62 100644 --- a/samples/snippets/hybrid_glossaries/noxfile.py +++ b/samples/snippets/hybrid_glossaries/noxfile.py @@ -14,6 +14,7 @@ from __future__ import print_function +import glob import os from pathlib import Path import sys @@ -184,37 +185,45 @@ def blacken(session: nox.sessions.Session) -> None: def _session_tests( session: nox.sessions.Session, post_install: Callable = None ) -> None: - if TEST_CONFIG["pip_version_override"]: - pip_version = TEST_CONFIG["pip_version_override"] - session.install(f"pip=={pip_version}") - """Runs py.test for a particular project.""" - if os.path.exists("requirements.txt"): - if os.path.exists("constraints.txt"): - session.install("-r", "requirements.txt", "-c", "constraints.txt") - else: - session.install("-r", "requirements.txt") - - if os.path.exists("requirements-test.txt"): - if os.path.exists("constraints-test.txt"): - session.install("-r", "requirements-test.txt", "-c", "constraints-test.txt") - else: - session.install("-r", "requirements-test.txt") - - if INSTALL_LIBRARY_FROM_SOURCE: - session.install("-e", _get_repo_root()) - - if post_install: - post_install(session) - - session.run( - "pytest", - *(PYTEST_COMMON_ARGS + session.posargs), - # Pytest will return 5 when no tests are collected. This can happen - # on travis where slow and flaky tests are excluded. - # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html - success_codes=[0, 5], - env=get_pytest_env_vars(), - ) + # check for presence of tests + test_list = glob.glob("*_test.py") + glob.glob("test_*.py") + test_list.extend(glob.glob("tests")) + if len(test_list) == 0: + print("No tests found, skipping directory.") + else: + if TEST_CONFIG["pip_version_override"]: + pip_version = TEST_CONFIG["pip_version_override"] + session.install(f"pip=={pip_version}") + """Runs py.test for a particular project.""" + if os.path.exists("requirements.txt"): + if os.path.exists("constraints.txt"): + session.install("-r", "requirements.txt", "-c", "constraints.txt") + else: + session.install("-r", "requirements.txt") + + if os.path.exists("requirements-test.txt"): + if os.path.exists("constraints-test.txt"): + session.install( + "-r", "requirements-test.txt", "-c", "constraints-test.txt" + ) + else: + session.install("-r", "requirements-test.txt") + + if INSTALL_LIBRARY_FROM_SOURCE: + session.install("-e", _get_repo_root()) + + if post_install: + post_install(session) + + session.run( + "pytest", + *(PYTEST_COMMON_ARGS + session.posargs), + # Pytest will return 5 when no tests are collected. This can happen + # on travis where slow and flaky tests are excluded. + # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html + success_codes=[0, 5], + env=get_pytest_env_vars(), + ) @nox.session(python=ALL_VERSIONS) diff --git a/samples/snippets/hybrid_glossaries/requirements.txt b/samples/snippets/hybrid_glossaries/requirements.txt index 5acda81c..c6718477 100644 --- a/samples/snippets/hybrid_glossaries/requirements.txt +++ b/samples/snippets/hybrid_glossaries/requirements.txt @@ -1,3 +1,3 @@ -google-cloud-translate==3.6.0 -google-cloud-vision==2.6.2 -google-cloud-texttospeech==2.7.1 +google-cloud-translate==3.6.1 +google-cloud-vision==2.6.3 +google-cloud-texttospeech==2.10.0 diff --git a/samples/snippets/noxfile.py b/samples/snippets/noxfile.py index 93a9122c..20cdfc62 100644 --- a/samples/snippets/noxfile.py +++ b/samples/snippets/noxfile.py @@ -14,6 +14,7 @@ from __future__ import print_function +import glob import os from pathlib import Path import sys @@ -184,37 +185,45 @@ def blacken(session: nox.sessions.Session) -> None: def _session_tests( session: nox.sessions.Session, post_install: Callable = None ) -> None: - if TEST_CONFIG["pip_version_override"]: - pip_version = TEST_CONFIG["pip_version_override"] - session.install(f"pip=={pip_version}") - """Runs py.test for a particular project.""" - if os.path.exists("requirements.txt"): - if os.path.exists("constraints.txt"): - session.install("-r", "requirements.txt", "-c", "constraints.txt") - else: - session.install("-r", "requirements.txt") - - if os.path.exists("requirements-test.txt"): - if os.path.exists("constraints-test.txt"): - session.install("-r", "requirements-test.txt", "-c", "constraints-test.txt") - else: - session.install("-r", "requirements-test.txt") - - if INSTALL_LIBRARY_FROM_SOURCE: - session.install("-e", _get_repo_root()) - - if post_install: - post_install(session) - - session.run( - "pytest", - *(PYTEST_COMMON_ARGS + session.posargs), - # Pytest will return 5 when no tests are collected. This can happen - # on travis where slow and flaky tests are excluded. - # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html - success_codes=[0, 5], - env=get_pytest_env_vars(), - ) + # check for presence of tests + test_list = glob.glob("*_test.py") + glob.glob("test_*.py") + test_list.extend(glob.glob("tests")) + if len(test_list) == 0: + print("No tests found, skipping directory.") + else: + if TEST_CONFIG["pip_version_override"]: + pip_version = TEST_CONFIG["pip_version_override"] + session.install(f"pip=={pip_version}") + """Runs py.test for a particular project.""" + if os.path.exists("requirements.txt"): + if os.path.exists("constraints.txt"): + session.install("-r", "requirements.txt", "-c", "constraints.txt") + else: + session.install("-r", "requirements.txt") + + if os.path.exists("requirements-test.txt"): + if os.path.exists("constraints-test.txt"): + session.install( + "-r", "requirements-test.txt", "-c", "constraints-test.txt" + ) + else: + session.install("-r", "requirements-test.txt") + + if INSTALL_LIBRARY_FROM_SOURCE: + session.install("-e", _get_repo_root()) + + if post_install: + post_install(session) + + session.run( + "pytest", + *(PYTEST_COMMON_ARGS + session.posargs), + # Pytest will return 5 when no tests are collected. This can happen + # on travis where slow and flaky tests are excluded. + # See http://doc.pytest.org/en/latest/_modules/_pytest/main.html + success_codes=[0, 5], + env=get_pytest_env_vars(), + ) @nox.session(python=ALL_VERSIONS) diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index 79a02771..8c51bb99 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,3 +1,3 @@ -google-cloud-translate==3.6.0 -google-cloud-storage==1.42.3 -google-cloud-automl==2.5.2 +google-cloud-translate==3.6.1 +google-cloud-storage==2.1.0 +google-cloud-automl==2.6.0 diff --git a/samples/snippets/translate_v3beta1_translate_document.py b/samples/snippets/translate_v3beta1_translate_document.py index 28e28579..963d1d87 100644 --- a/samples/snippets/translate_v3beta1_translate_document.py +++ b/samples/snippets/translate_v3beta1_translate_document.py @@ -41,7 +41,11 @@ def translate_document(project_id: str, file_path: str): } ) - # To view translated document, write `response.document_translation.byte_stream_outputs` to file. + # To output the translated document, uncomment the code below. + # f = open('/tmp/output', 'wb') + # f.write(response.document_translation.byte_stream_outputs) + # f.close() + # If not provided in the TranslationRequest, the translated file will only be returned through a byte-stream # and its output mime type will be the same as the input file's mime type print("Response: Detected Language Code - {}".format(response.document_translation.detected_language_code)) diff --git a/setup.py b/setup.py index 322ea30a..3c1e1432 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ name = "google-cloud-translate" description = "Google Cloud Translation API client library" -version = "3.6.1" +version = "3.7.0" # Should be one of: # 'Development Status :: 3 - Alpha' # 'Development Status :: 4 - Beta' diff --git a/tests/unit/gapic/translate_v3/test_translation_service.py b/tests/unit/gapic/translate_v3/test_translation_service.py index 0130b68c..b82ff411 100644 --- a/tests/unit/gapic/translate_v3/test_translation_service.py +++ b/tests/unit/gapic/translate_v3/test_translation_service.py @@ -29,6 +29,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 from google.api_core import operation_async # type: ignore from google.api_core import operations_v1 from google.api_core import path_template @@ -256,20 +257,20 @@ def test_translation_service_client_client_options( # unsupported value. with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): with pytest.raises(MutualTLSChannelError): - client = client_class() + client = client_class(transport=transport_name) # 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() + client = client_class(transport=transport_name) # 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(transport=transport_name, client_options=options) + client = client_class(client_options=options, transport=transport_name) patched.assert_called_once_with( credentials=None, credentials_file=None, @@ -338,7 +339,7 @@ def test_translation_service_client_mtls_env_auto( ) with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None - client = client_class(transport=transport_name, client_options=options) + client = client_class(client_options=options, transport=transport_name) if use_client_cert_env == "false": expected_client_cert_source = None @@ -415,6 +416,87 @@ def test_translation_service_client_mtls_env_auto( ) +@pytest.mark.parametrize( + "client_class", [TranslationServiceClient, TranslationServiceAsyncClient] +) +@mock.patch.object( + TranslationServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TranslationServiceClient), +) +@mock.patch.object( + TranslationServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TranslationServiceAsyncClient), +) +def test_translation_service_client_get_mtls_endpoint_and_cert_source(client_class): + mock_client_cert_source = mock.Mock() + + # Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "true". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + mock_api_endpoint = "foo" + options = client_options.ClientOptions( + client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint + ) + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source( + options + ) + assert api_endpoint == mock_api_endpoint + assert cert_source == mock_client_cert_source + + # Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "false". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): + mock_client_cert_source = mock.Mock() + mock_api_endpoint = "foo" + options = client_options.ClientOptions( + client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint + ) + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source( + options + ) + assert api_endpoint == mock_api_endpoint + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "never". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "always". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert doesn't exist. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert exists. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, + ): + with mock.patch( + "google.auth.transport.mtls.default_client_cert_source", + return_value=mock_client_cert_source, + ): + ( + api_endpoint, + cert_source, + ) = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT + assert cert_source == mock_client_cert_source + + @pytest.mark.parametrize( "client_class,transport_class,transport_name", [ @@ -433,7 +515,7 @@ def test_translation_service_client_client_options_scopes( options = client_options.ClientOptions(scopes=["1", "2"],) with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None - client = client_class(transport=transport_name, client_options=options) + client = client_class(client_options=options, transport=transport_name) patched.assert_called_once_with( credentials=None, credentials_file=None, @@ -447,24 +529,31 @@ def test_translation_service_client_client_options_scopes( @pytest.mark.parametrize( - "client_class,transport_class,transport_name", + "client_class,transport_class,transport_name,grpc_helpers", [ - (TranslationServiceClient, transports.TranslationServiceGrpcTransport, "grpc"), + ( + TranslationServiceClient, + transports.TranslationServiceGrpcTransport, + "grpc", + grpc_helpers, + ), ( TranslationServiceAsyncClient, transports.TranslationServiceGrpcAsyncIOTransport, "grpc_asyncio", + grpc_helpers_async, ), ], ) def test_translation_service_client_client_options_credentials_file( - client_class, transport_class, transport_name + client_class, transport_class, transport_name, grpc_helpers ): # Check the case credentials file is provided. options = client_options.ClientOptions(credentials_file="credentials.json") + with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None - client = client_class(transport=transport_name, client_options=options) + client = client_class(client_options=options, transport=transport_name) patched.assert_called_once_with( credentials=None, credentials_file="credentials.json", @@ -497,9 +586,79 @@ def test_translation_service_client_client_options_from_dict(): ) -def test_translate_text( - transport: str = "grpc", request_type=translation_service.TranslateTextRequest +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,grpc_helpers", + [ + ( + TranslationServiceClient, + transports.TranslationServiceGrpcTransport, + "grpc", + grpc_helpers, + ), + ( + TranslationServiceAsyncClient, + transports.TranslationServiceGrpcAsyncIOTransport, + "grpc_asyncio", + grpc_helpers_async, + ), + ], +) +def test_translation_service_client_create_channel_credentials_file( + client_class, transport_class, transport_name, grpc_helpers ): + # Check the case credentials file is provided. + options = client_options.ClientOptions(credentials_file="credentials.json") + + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options, transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file="credentials.json", + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + ) + + # test that the credentials from file are saved and used as the credentials. + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch.object( + google.auth, "default", autospec=True + ) as adc, mock.patch.object( + grpc_helpers, "create_channel" + ) as create_channel: + creds = ga_credentials.AnonymousCredentials() + file_creds = ga_credentials.AnonymousCredentials() + load_creds.return_value = (file_creds, None) + adc.return_value = (creds, None) + client = client_class(client_options=options, transport=transport_name) + create_channel.assert_called_with( + "translate.googleapis.com:443", + credentials=file_creds, + credentials_file=None, + quota_project_id=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-translation", + ), + scopes=None, + default_host="translate.googleapis.com", + ssl_credentials=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + +@pytest.mark.parametrize( + "request_type", [translation_service.TranslateTextRequest, dict,] +) +def test_translate_text(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -523,10 +682,6 @@ def test_translate_text( assert isinstance(response, translation_service.TranslateTextResponse) -def test_translate_text_from_dict(): - test_translate_text(request_type=dict) - - def test_translate_text_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -656,12 +811,24 @@ def test_translate_text_flattened(): # request object values. assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].target_language_code == "target_language_code_value" - assert args[0].contents == ["contents_value"] - assert args[0].model == "model_value" - assert args[0].mime_type == "mime_type_value" - assert args[0].source_language_code == "source_language_code_value" + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].target_language_code + mock_val = "target_language_code_value" + assert arg == mock_val + arg = args[0].contents + mock_val = ["contents_value"] + assert arg == mock_val + arg = args[0].model + mock_val = "model_value" + assert arg == mock_val + arg = args[0].mime_type + mock_val = "mime_type_value" + assert arg == mock_val + arg = args[0].source_language_code + mock_val = "source_language_code_value" + assert arg == mock_val def test_translate_text_flattened_error(): @@ -712,12 +879,24 @@ async def test_translate_text_flattened_async(): # request object values. assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].target_language_code == "target_language_code_value" - assert args[0].contents == ["contents_value"] - assert args[0].model == "model_value" - assert args[0].mime_type == "mime_type_value" - assert args[0].source_language_code == "source_language_code_value" + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].target_language_code + mock_val = "target_language_code_value" + assert arg == mock_val + arg = args[0].contents + mock_val = ["contents_value"] + assert arg == mock_val + arg = args[0].model + mock_val = "model_value" + assert arg == mock_val + arg = args[0].mime_type + mock_val = "mime_type_value" + assert arg == mock_val + arg = args[0].source_language_code + mock_val = "source_language_code_value" + assert arg == mock_val @pytest.mark.asyncio @@ -740,9 +919,10 @@ async def test_translate_text_flattened_error_async(): ) -def test_detect_language( - transport: str = "grpc", request_type=translation_service.DetectLanguageRequest -): +@pytest.mark.parametrize( + "request_type", [translation_service.DetectLanguageRequest, dict,] +) +def test_detect_language(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -766,10 +946,6 @@ def test_detect_language( assert isinstance(response, translation_service.DetectLanguageResponse) -def test_detect_language_from_dict(): - test_detect_language(request_type=dict) - - def test_detect_language_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -897,9 +1073,15 @@ def test_detect_language_flattened(): # request object values. assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].model == "model_value" - assert args[0].mime_type == "mime_type_value" + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].model + mock_val = "model_value" + assert arg == mock_val + arg = args[0].mime_type + mock_val = "mime_type_value" + assert arg == mock_val assert args[0].content == "content_value" @@ -947,9 +1129,15 @@ async def test_detect_language_flattened_async(): # request object values. assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].model == "model_value" - assert args[0].mime_type == "mime_type_value" + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].model + mock_val = "model_value" + assert arg == mock_val + arg = args[0].mime_type + mock_val = "mime_type_value" + assert arg == mock_val assert args[0].content == "content_value" @@ -971,10 +1159,10 @@ async def test_detect_language_flattened_error_async(): ) -def test_get_supported_languages( - transport: str = "grpc", - request_type=translation_service.GetSupportedLanguagesRequest, -): +@pytest.mark.parametrize( + "request_type", [translation_service.GetSupportedLanguagesRequest, dict,] +) +def test_get_supported_languages(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -1000,10 +1188,6 @@ def test_get_supported_languages( assert isinstance(response, translation_service.SupportedLanguages) -def test_get_supported_languages_from_dict(): - test_get_supported_languages(request_type=dict) - - def test_get_supported_languages_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1140,9 +1324,15 @@ def test_get_supported_languages_flattened(): # request object values. assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].model == "model_value" - assert args[0].display_language_code == "display_language_code_value" + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].model + mock_val = "model_value" + assert arg == mock_val + arg = args[0].display_language_code + mock_val = "display_language_code_value" + assert arg == mock_val def test_get_supported_languages_flattened_error(): @@ -1189,9 +1379,15 @@ async def test_get_supported_languages_flattened_async(): # request object values. assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].model == "model_value" - assert args[0].display_language_code == "display_language_code_value" + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].model + mock_val = "model_value" + assert arg == mock_val + arg = args[0].display_language_code + mock_val = "display_language_code_value" + assert arg == mock_val @pytest.mark.asyncio @@ -1211,9 +1407,10 @@ async def test_get_supported_languages_flattened_error_async(): ) -def test_translate_document( - transport: str = "grpc", request_type=translation_service.TranslateDocumentRequest -): +@pytest.mark.parametrize( + "request_type", [translation_service.TranslateDocumentRequest, dict,] +) +def test_translate_document(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -1242,10 +1439,6 @@ def test_translate_document( assert response.model == "model_value" -def test_translate_document_from_dict(): - test_translate_document(request_type=dict) - - def test_translate_document_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1360,9 +1553,10 @@ async def test_translate_document_field_headers_async(): assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] -def test_batch_translate_text( - transport: str = "grpc", request_type=translation_service.BatchTranslateTextRequest -): +@pytest.mark.parametrize( + "request_type", [translation_service.BatchTranslateTextRequest, dict,] +) +def test_batch_translate_text(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -1388,10 +1582,6 @@ def test_batch_translate_text( assert isinstance(response, future.Future) -def test_batch_translate_text_from_dict(): - test_batch_translate_text(request_type=dict) - - def test_batch_translate_text_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1505,10 +1695,10 @@ async def test_batch_translate_text_field_headers_async(): assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] -def test_batch_translate_document( - transport: str = "grpc", - request_type=translation_service.BatchTranslateDocumentRequest, -): +@pytest.mark.parametrize( + "request_type", [translation_service.BatchTranslateDocumentRequest, dict,] +) +def test_batch_translate_document(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -1534,10 +1724,6 @@ def test_batch_translate_document( assert isinstance(response, future.Future) -def test_batch_translate_document_from_dict(): - test_batch_translate_document(request_type=dict) - - def test_batch_translate_document_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1686,19 +1872,29 @@ def test_batch_translate_document_flattened(): # request object values. assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].source_language_code == "source_language_code_value" - assert args[0].target_language_codes == ["target_language_codes_value"] - assert args[0].input_configs == [ + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].source_language_code + mock_val = "source_language_code_value" + assert arg == mock_val + arg = args[0].target_language_codes + mock_val = ["target_language_codes_value"] + assert arg == mock_val + arg = args[0].input_configs + mock_val = [ translation_service.BatchDocumentInputConfig( gcs_source=translation_service.GcsSource(input_uri="input_uri_value") ) ] - assert args[0].output_config == translation_service.BatchDocumentOutputConfig( + assert arg == mock_val + arg = args[0].output_config + mock_val = translation_service.BatchDocumentOutputConfig( gcs_destination=translation_service.GcsDestination( output_uri_prefix="output_uri_prefix_value" ) ) + assert arg == mock_val def test_batch_translate_document_flattened_error(): @@ -1769,19 +1965,29 @@ async def test_batch_translate_document_flattened_async(): # request object values. assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].source_language_code == "source_language_code_value" - assert args[0].target_language_codes == ["target_language_codes_value"] - assert args[0].input_configs == [ + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].source_language_code + mock_val = "source_language_code_value" + assert arg == mock_val + arg = args[0].target_language_codes + mock_val = ["target_language_codes_value"] + assert arg == mock_val + arg = args[0].input_configs + mock_val = [ translation_service.BatchDocumentInputConfig( gcs_source=translation_service.GcsSource(input_uri="input_uri_value") ) ] - assert args[0].output_config == translation_service.BatchDocumentOutputConfig( + assert arg == mock_val + arg = args[0].output_config + mock_val = translation_service.BatchDocumentOutputConfig( gcs_destination=translation_service.GcsDestination( output_uri_prefix="output_uri_prefix_value" ) ) + assert arg == mock_val @pytest.mark.asyncio @@ -1813,9 +2019,10 @@ async def test_batch_translate_document_flattened_error_async(): ) -def test_create_glossary( - transport: str = "grpc", request_type=translation_service.CreateGlossaryRequest -): +@pytest.mark.parametrize( + "request_type", [translation_service.CreateGlossaryRequest, dict,] +) +def test_create_glossary(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -1839,10 +2046,6 @@ def test_create_glossary( assert isinstance(response, future.Future) -def test_create_glossary_from_dict(): - test_create_glossary(request_type=dict) - - def test_create_glossary_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1968,8 +2171,12 @@ def test_create_glossary_flattened(): # request object values. assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].glossary == translation_service.Glossary(name="name_value") + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].glossary + mock_val = translation_service.Glossary(name="name_value") + assert arg == mock_val def test_create_glossary_flattened_error(): @@ -2012,8 +2219,12 @@ async def test_create_glossary_flattened_async(): # request object values. assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].glossary == translation_service.Glossary(name="name_value") + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].glossary + mock_val = translation_service.Glossary(name="name_value") + assert arg == mock_val @pytest.mark.asyncio @@ -2032,9 +2243,10 @@ async def test_create_glossary_flattened_error_async(): ) -def test_list_glossaries( - transport: str = "grpc", request_type=translation_service.ListGlossariesRequest -): +@pytest.mark.parametrize( + "request_type", [translation_service.ListGlossariesRequest, dict,] +) +def test_list_glossaries(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -2061,10 +2273,6 @@ def test_list_glossaries( assert response.next_page_token == "next_page_token_value" -def test_list_glossaries_from_dict(): - test_list_glossaries(request_type=dict) - - def test_list_glossaries_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -2190,7 +2398,9 @@ def test_list_glossaries_flattened(): # request object values. assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val def test_list_glossaries_flattened_error(): @@ -2228,7 +2438,9 @@ async def test_list_glossaries_flattened_async(): # request object values. assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val @pytest.mark.asyncio @@ -2245,8 +2457,10 @@ async def test_list_glossaries_flattened_error_async(): ) -def test_list_glossaries_pager(): - client = TranslationServiceClient(credentials=ga_credentials.AnonymousCredentials,) +def test_list_glossaries_pager(transport_name: str = "grpc"): + client = TranslationServiceClient( + credentials=ga_credentials.AnonymousCredentials, transport=transport_name, + ) # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object(type(client.transport.list_glossaries), "__call__") as call: @@ -2288,8 +2502,10 @@ def test_list_glossaries_pager(): assert all(isinstance(i, translation_service.Glossary) for i in results) -def test_list_glossaries_pages(): - client = TranslationServiceClient(credentials=ga_credentials.AnonymousCredentials,) +def test_list_glossaries_pages(transport_name: str = "grpc"): + client = TranslationServiceClient( + credentials=ga_credentials.AnonymousCredentials, transport=transport_name, + ) # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object(type(client.transport.list_glossaries), "__call__") as call: @@ -2407,9 +2623,10 @@ async def test_list_glossaries_async_pages(): assert page_.raw_page.next_page_token == token -def test_get_glossary( - transport: str = "grpc", request_type=translation_service.GetGlossaryRequest -): +@pytest.mark.parametrize( + "request_type", [translation_service.GetGlossaryRequest, dict,] +) +def test_get_glossary(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -2441,10 +2658,6 @@ def test_get_glossary( assert response.entry_count == 1210 -def test_get_glossary_from_dict(): - test_get_glossary(request_type=dict) - - def test_get_glossary_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -2568,7 +2781,9 @@ def test_get_glossary_flattened(): # request object values. assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0].name == "name_value" + arg = args[0].name + mock_val = "name_value" + assert arg == mock_val def test_get_glossary_flattened_error(): @@ -2606,7 +2821,9 @@ async def test_get_glossary_flattened_async(): # request object values. assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0].name == "name_value" + arg = args[0].name + mock_val = "name_value" + assert arg == mock_val @pytest.mark.asyncio @@ -2623,9 +2840,10 @@ async def test_get_glossary_flattened_error_async(): ) -def test_delete_glossary( - transport: str = "grpc", request_type=translation_service.DeleteGlossaryRequest -): +@pytest.mark.parametrize( + "request_type", [translation_service.DeleteGlossaryRequest, dict,] +) +def test_delete_glossary(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -2649,10 +2867,6 @@ def test_delete_glossary( assert isinstance(response, future.Future) -def test_delete_glossary_from_dict(): - test_delete_glossary(request_type=dict) - - def test_delete_glossary_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -2775,7 +2989,9 @@ def test_delete_glossary_flattened(): # request object values. assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0].name == "name_value" + arg = args[0].name + mock_val = "name_value" + assert arg == mock_val def test_delete_glossary_flattened_error(): @@ -2813,7 +3029,9 @@ async def test_delete_glossary_flattened_async(): # request object values. assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0].name == "name_value" + arg = args[0].name + mock_val = "name_value" + assert arg == mock_val @pytest.mark.asyncio @@ -2850,6 +3068,23 @@ def test_credentials_transport_error(): transport=transport, ) + # It is an error to provide an api_key and a transport instance. + transport = transports.TranslationServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = TranslationServiceClient(client_options=options, transport=transport,) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = TranslationServiceClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + # It is an error to provide scopes and a transport instance. transport = transports.TranslationServiceGrpcTransport( credentials=ga_credentials.AnonymousCredentials(), @@ -3400,7 +3635,7 @@ def test_parse_common_location_path(): assert expected == actual -def test_client_withDEFAULT_CLIENT_INFO(): +def test_client_with_default_client_info(): client_info = gapic_v1.client_info.ClientInfo() with mock.patch.object( @@ -3465,3 +3700,36 @@ def test_client_ctx(): with client: pass close.assert_called() + + +@pytest.mark.parametrize( + "client_class,transport_class", + [ + (TranslationServiceClient, transports.TranslationServiceGrpcTransport), + ( + TranslationServiceAsyncClient, + transports.TranslationServiceGrpcAsyncIOTransport, + ), + ], +) +def test_api_key_credentials(client_class, transport_class): + with mock.patch.object( + google.auth._default, "get_api_key_credentials", create=True + ) as get_api_key_credentials: + mock_cred = mock.Mock() + get_api_key_credentials.return_value = mock_cred + options = client_options.ClientOptions() + options.api_key = "api_key" + 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=mock_cred, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + ) diff --git a/tests/unit/gapic/translate_v3beta1/test_translation_service.py b/tests/unit/gapic/translate_v3beta1/test_translation_service.py index 7f70bd91..ec260dcd 100644 --- a/tests/unit/gapic/translate_v3beta1/test_translation_service.py +++ b/tests/unit/gapic/translate_v3beta1/test_translation_service.py @@ -29,6 +29,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 from google.api_core import operation_async # type: ignore from google.api_core import operations_v1 from google.api_core import path_template @@ -256,20 +257,20 @@ def test_translation_service_client_client_options( # unsupported value. with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): with pytest.raises(MutualTLSChannelError): - client = client_class() + client = client_class(transport=transport_name) # 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() + client = client_class(transport=transport_name) # 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(transport=transport_name, client_options=options) + client = client_class(client_options=options, transport=transport_name) patched.assert_called_once_with( credentials=None, credentials_file=None, @@ -338,7 +339,7 @@ def test_translation_service_client_mtls_env_auto( ) with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None - client = client_class(transport=transport_name, client_options=options) + client = client_class(client_options=options, transport=transport_name) if use_client_cert_env == "false": expected_client_cert_source = None @@ -415,6 +416,87 @@ def test_translation_service_client_mtls_env_auto( ) +@pytest.mark.parametrize( + "client_class", [TranslationServiceClient, TranslationServiceAsyncClient] +) +@mock.patch.object( + TranslationServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TranslationServiceClient), +) +@mock.patch.object( + TranslationServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TranslationServiceAsyncClient), +) +def test_translation_service_client_get_mtls_endpoint_and_cert_source(client_class): + mock_client_cert_source = mock.Mock() + + # Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "true". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + mock_api_endpoint = "foo" + options = client_options.ClientOptions( + client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint + ) + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source( + options + ) + assert api_endpoint == mock_api_endpoint + assert cert_source == mock_client_cert_source + + # Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "false". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}): + mock_client_cert_source = mock.Mock() + mock_api_endpoint = "foo" + options = client_options.ClientOptions( + client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint + ) + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source( + options + ) + assert api_endpoint == mock_api_endpoint + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "never". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "always". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert doesn't exist. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_ENDPOINT + assert cert_source is None + + # Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert exists. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}): + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, + ): + with mock.patch( + "google.auth.transport.mtls.default_client_cert_source", + return_value=mock_client_cert_source, + ): + ( + api_endpoint, + cert_source, + ) = client_class.get_mtls_endpoint_and_cert_source() + assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT + assert cert_source == mock_client_cert_source + + @pytest.mark.parametrize( "client_class,transport_class,transport_name", [ @@ -433,7 +515,7 @@ def test_translation_service_client_client_options_scopes( options = client_options.ClientOptions(scopes=["1", "2"],) with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None - client = client_class(transport=transport_name, client_options=options) + client = client_class(client_options=options, transport=transport_name) patched.assert_called_once_with( credentials=None, credentials_file=None, @@ -447,24 +529,31 @@ def test_translation_service_client_client_options_scopes( @pytest.mark.parametrize( - "client_class,transport_class,transport_name", + "client_class,transport_class,transport_name,grpc_helpers", [ - (TranslationServiceClient, transports.TranslationServiceGrpcTransport, "grpc"), + ( + TranslationServiceClient, + transports.TranslationServiceGrpcTransport, + "grpc", + grpc_helpers, + ), ( TranslationServiceAsyncClient, transports.TranslationServiceGrpcAsyncIOTransport, "grpc_asyncio", + grpc_helpers_async, ), ], ) def test_translation_service_client_client_options_credentials_file( - client_class, transport_class, transport_name + client_class, transport_class, transport_name, grpc_helpers ): # Check the case credentials file is provided. options = client_options.ClientOptions(credentials_file="credentials.json") + with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None - client = client_class(transport=transport_name, client_options=options) + client = client_class(client_options=options, transport=transport_name) patched.assert_called_once_with( credentials=None, credentials_file="credentials.json", @@ -497,9 +586,79 @@ def test_translation_service_client_client_options_from_dict(): ) -def test_translate_text( - transport: str = "grpc", request_type=translation_service.TranslateTextRequest +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,grpc_helpers", + [ + ( + TranslationServiceClient, + transports.TranslationServiceGrpcTransport, + "grpc", + grpc_helpers, + ), + ( + TranslationServiceAsyncClient, + transports.TranslationServiceGrpcAsyncIOTransport, + "grpc_asyncio", + grpc_helpers_async, + ), + ], +) +def test_translation_service_client_create_channel_credentials_file( + client_class, transport_class, transport_name, grpc_helpers ): + # Check the case credentials file is provided. + options = client_options.ClientOptions(credentials_file="credentials.json") + + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options, transport=transport_name) + patched.assert_called_once_with( + credentials=None, + credentials_file="credentials.json", + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + ) + + # test that the credentials from file are saved and used as the credentials. + with mock.patch.object( + google.auth, "load_credentials_from_file", autospec=True + ) as load_creds, mock.patch.object( + google.auth, "default", autospec=True + ) as adc, mock.patch.object( + grpc_helpers, "create_channel" + ) as create_channel: + creds = ga_credentials.AnonymousCredentials() + file_creds = ga_credentials.AnonymousCredentials() + load_creds.return_value = (file_creds, None) + adc.return_value = (creds, None) + client = client_class(client_options=options, transport=transport_name) + create_channel.assert_called_with( + "translate.googleapis.com:443", + credentials=file_creds, + credentials_file=None, + quota_project_id=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-translation", + ), + scopes=None, + default_host="translate.googleapis.com", + ssl_credentials=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + +@pytest.mark.parametrize( + "request_type", [translation_service.TranslateTextRequest, dict,] +) +def test_translate_text(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -523,10 +682,6 @@ def test_translate_text( assert isinstance(response, translation_service.TranslateTextResponse) -def test_translate_text_from_dict(): - test_translate_text(request_type=dict) - - def test_translate_text_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -632,9 +787,10 @@ async def test_translate_text_field_headers_async(): assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] -def test_detect_language( - transport: str = "grpc", request_type=translation_service.DetectLanguageRequest -): +@pytest.mark.parametrize( + "request_type", [translation_service.DetectLanguageRequest, dict,] +) +def test_detect_language(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -658,10 +814,6 @@ def test_detect_language( assert isinstance(response, translation_service.DetectLanguageResponse) -def test_detect_language_from_dict(): - test_detect_language(request_type=dict) - - def test_detect_language_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -786,9 +938,15 @@ def test_detect_language_flattened(): # request object values. assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].model == "model_value" - assert args[0].mime_type == "mime_type_value" + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].model + mock_val = "model_value" + assert arg == mock_val + arg = args[0].mime_type + mock_val = "mime_type_value" + assert arg == mock_val def test_detect_language_flattened_error(): @@ -831,9 +989,15 @@ async def test_detect_language_flattened_async(): # request object values. assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].model == "model_value" - assert args[0].mime_type == "mime_type_value" + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].model + mock_val = "model_value" + assert arg == mock_val + arg = args[0].mime_type + mock_val = "mime_type_value" + assert arg == mock_val @pytest.mark.asyncio @@ -853,10 +1017,10 @@ async def test_detect_language_flattened_error_async(): ) -def test_get_supported_languages( - transport: str = "grpc", - request_type=translation_service.GetSupportedLanguagesRequest, -): +@pytest.mark.parametrize( + "request_type", [translation_service.GetSupportedLanguagesRequest, dict,] +) +def test_get_supported_languages(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -882,10 +1046,6 @@ def test_get_supported_languages( assert isinstance(response, translation_service.SupportedLanguages) -def test_get_supported_languages_from_dict(): - test_get_supported_languages(request_type=dict) - - def test_get_supported_languages_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1022,9 +1182,15 @@ def test_get_supported_languages_flattened(): # request object values. assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].display_language_code == "display_language_code_value" - assert args[0].model == "model_value" + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].display_language_code + mock_val = "display_language_code_value" + assert arg == mock_val + arg = args[0].model + mock_val = "model_value" + assert arg == mock_val def test_get_supported_languages_flattened_error(): @@ -1071,9 +1237,15 @@ async def test_get_supported_languages_flattened_async(): # request object values. assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].display_language_code == "display_language_code_value" - assert args[0].model == "model_value" + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].display_language_code + mock_val = "display_language_code_value" + assert arg == mock_val + arg = args[0].model + mock_val = "model_value" + assert arg == mock_val @pytest.mark.asyncio @@ -1093,9 +1265,10 @@ async def test_get_supported_languages_flattened_error_async(): ) -def test_translate_document( - transport: str = "grpc", request_type=translation_service.TranslateDocumentRequest -): +@pytest.mark.parametrize( + "request_type", [translation_service.TranslateDocumentRequest, dict,] +) +def test_translate_document(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -1124,10 +1297,6 @@ def test_translate_document( assert response.model == "model_value" -def test_translate_document_from_dict(): - test_translate_document(request_type=dict) - - def test_translate_document_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1242,9 +1411,10 @@ async def test_translate_document_field_headers_async(): assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] -def test_batch_translate_text( - transport: str = "grpc", request_type=translation_service.BatchTranslateTextRequest -): +@pytest.mark.parametrize( + "request_type", [translation_service.BatchTranslateTextRequest, dict,] +) +def test_batch_translate_text(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -1270,10 +1440,6 @@ def test_batch_translate_text( assert isinstance(response, future.Future) -def test_batch_translate_text_from_dict(): - test_batch_translate_text(request_type=dict) - - def test_batch_translate_text_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1387,10 +1553,10 @@ async def test_batch_translate_text_field_headers_async(): assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] -def test_batch_translate_document( - transport: str = "grpc", - request_type=translation_service.BatchTranslateDocumentRequest, -): +@pytest.mark.parametrize( + "request_type", [translation_service.BatchTranslateDocumentRequest, dict,] +) +def test_batch_translate_document(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -1416,10 +1582,6 @@ def test_batch_translate_document( assert isinstance(response, future.Future) -def test_batch_translate_document_from_dict(): - test_batch_translate_document(request_type=dict) - - def test_batch_translate_document_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1568,19 +1730,29 @@ def test_batch_translate_document_flattened(): # request object values. assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].source_language_code == "source_language_code_value" - assert args[0].target_language_codes == ["target_language_codes_value"] - assert args[0].input_configs == [ + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].source_language_code + mock_val = "source_language_code_value" + assert arg == mock_val + arg = args[0].target_language_codes + mock_val = ["target_language_codes_value"] + assert arg == mock_val + arg = args[0].input_configs + mock_val = [ translation_service.BatchDocumentInputConfig( gcs_source=translation_service.GcsSource(input_uri="input_uri_value") ) ] - assert args[0].output_config == translation_service.BatchDocumentOutputConfig( + assert arg == mock_val + arg = args[0].output_config + mock_val = translation_service.BatchDocumentOutputConfig( gcs_destination=translation_service.GcsDestination( output_uri_prefix="output_uri_prefix_value" ) ) + assert arg == mock_val def test_batch_translate_document_flattened_error(): @@ -1651,19 +1823,29 @@ async def test_batch_translate_document_flattened_async(): # request object values. assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].source_language_code == "source_language_code_value" - assert args[0].target_language_codes == ["target_language_codes_value"] - assert args[0].input_configs == [ + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].source_language_code + mock_val = "source_language_code_value" + assert arg == mock_val + arg = args[0].target_language_codes + mock_val = ["target_language_codes_value"] + assert arg == mock_val + arg = args[0].input_configs + mock_val = [ translation_service.BatchDocumentInputConfig( gcs_source=translation_service.GcsSource(input_uri="input_uri_value") ) ] - assert args[0].output_config == translation_service.BatchDocumentOutputConfig( + assert arg == mock_val + arg = args[0].output_config + mock_val = translation_service.BatchDocumentOutputConfig( gcs_destination=translation_service.GcsDestination( output_uri_prefix="output_uri_prefix_value" ) ) + assert arg == mock_val @pytest.mark.asyncio @@ -1695,9 +1877,10 @@ async def test_batch_translate_document_flattened_error_async(): ) -def test_create_glossary( - transport: str = "grpc", request_type=translation_service.CreateGlossaryRequest -): +@pytest.mark.parametrize( + "request_type", [translation_service.CreateGlossaryRequest, dict,] +) +def test_create_glossary(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -1721,10 +1904,6 @@ def test_create_glossary( assert isinstance(response, future.Future) -def test_create_glossary_from_dict(): - test_create_glossary(request_type=dict) - - def test_create_glossary_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -1850,8 +2029,12 @@ def test_create_glossary_flattened(): # request object values. assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].glossary == translation_service.Glossary(name="name_value") + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].glossary + mock_val = translation_service.Glossary(name="name_value") + assert arg == mock_val def test_create_glossary_flattened_error(): @@ -1894,8 +2077,12 @@ async def test_create_glossary_flattened_async(): # request object values. assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].glossary == translation_service.Glossary(name="name_value") + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].glossary + mock_val = translation_service.Glossary(name="name_value") + assert arg == mock_val @pytest.mark.asyncio @@ -1914,9 +2101,10 @@ async def test_create_glossary_flattened_error_async(): ) -def test_list_glossaries( - transport: str = "grpc", request_type=translation_service.ListGlossariesRequest -): +@pytest.mark.parametrize( + "request_type", [translation_service.ListGlossariesRequest, dict,] +) +def test_list_glossaries(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -1943,10 +2131,6 @@ def test_list_glossaries( assert response.next_page_token == "next_page_token_value" -def test_list_glossaries_from_dict(): - test_list_glossaries(request_type=dict) - - def test_list_glossaries_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -2074,8 +2258,12 @@ def test_list_glossaries_flattened(): # request object values. assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].filter == "filter_value" + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].filter + mock_val = "filter_value" + assert arg == mock_val def test_list_glossaries_flattened_error(): @@ -2117,8 +2305,12 @@ async def test_list_glossaries_flattened_async(): # request object values. assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0].parent == "parent_value" - assert args[0].filter == "filter_value" + arg = args[0].parent + mock_val = "parent_value" + assert arg == mock_val + arg = args[0].filter + mock_val = "filter_value" + assert arg == mock_val @pytest.mark.asyncio @@ -2137,8 +2329,10 @@ async def test_list_glossaries_flattened_error_async(): ) -def test_list_glossaries_pager(): - client = TranslationServiceClient(credentials=ga_credentials.AnonymousCredentials,) +def test_list_glossaries_pager(transport_name: str = "grpc"): + client = TranslationServiceClient( + credentials=ga_credentials.AnonymousCredentials, transport=transport_name, + ) # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object(type(client.transport.list_glossaries), "__call__") as call: @@ -2180,8 +2374,10 @@ def test_list_glossaries_pager(): assert all(isinstance(i, translation_service.Glossary) for i in results) -def test_list_glossaries_pages(): - client = TranslationServiceClient(credentials=ga_credentials.AnonymousCredentials,) +def test_list_glossaries_pages(transport_name: str = "grpc"): + client = TranslationServiceClient( + credentials=ga_credentials.AnonymousCredentials, transport=transport_name, + ) # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object(type(client.transport.list_glossaries), "__call__") as call: @@ -2299,9 +2495,10 @@ async def test_list_glossaries_async_pages(): assert page_.raw_page.next_page_token == token -def test_get_glossary( - transport: str = "grpc", request_type=translation_service.GetGlossaryRequest -): +@pytest.mark.parametrize( + "request_type", [translation_service.GetGlossaryRequest, dict,] +) +def test_get_glossary(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -2333,10 +2530,6 @@ def test_get_glossary( assert response.entry_count == 1210 -def test_get_glossary_from_dict(): - test_get_glossary(request_type=dict) - - def test_get_glossary_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -2460,7 +2653,9 @@ def test_get_glossary_flattened(): # request object values. assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0].name == "name_value" + arg = args[0].name + mock_val = "name_value" + assert arg == mock_val def test_get_glossary_flattened_error(): @@ -2498,7 +2693,9 @@ async def test_get_glossary_flattened_async(): # request object values. assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0].name == "name_value" + arg = args[0].name + mock_val = "name_value" + assert arg == mock_val @pytest.mark.asyncio @@ -2515,9 +2712,10 @@ async def test_get_glossary_flattened_error_async(): ) -def test_delete_glossary( - transport: str = "grpc", request_type=translation_service.DeleteGlossaryRequest -): +@pytest.mark.parametrize( + "request_type", [translation_service.DeleteGlossaryRequest, dict,] +) +def test_delete_glossary(request_type, transport: str = "grpc"): client = TranslationServiceClient( credentials=ga_credentials.AnonymousCredentials(), transport=transport, ) @@ -2541,10 +2739,6 @@ def test_delete_glossary( assert isinstance(response, future.Future) -def test_delete_glossary_from_dict(): - test_delete_glossary(request_type=dict) - - def test_delete_glossary_empty_call(): # This test is a coverage failsafe to make sure that totally empty calls, # i.e. request == None and no flattened fields passed, work. @@ -2667,7 +2861,9 @@ def test_delete_glossary_flattened(): # request object values. assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0].name == "name_value" + arg = args[0].name + mock_val = "name_value" + assert arg == mock_val def test_delete_glossary_flattened_error(): @@ -2705,7 +2901,9 @@ async def test_delete_glossary_flattened_async(): # request object values. assert len(call.mock_calls) _, args, _ = call.mock_calls[0] - assert args[0].name == "name_value" + arg = args[0].name + mock_val = "name_value" + assert arg == mock_val @pytest.mark.asyncio @@ -2742,6 +2940,23 @@ def test_credentials_transport_error(): transport=transport, ) + # It is an error to provide an api_key and a transport instance. + transport = transports.TranslationServiceGrpcTransport( + credentials=ga_credentials.AnonymousCredentials(), + ) + options = client_options.ClientOptions() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = TranslationServiceClient(client_options=options, transport=transport,) + + # It is an error to provide an api_key and a credential. + options = mock.Mock() + options.api_key = "api_key" + with pytest.raises(ValueError): + client = TranslationServiceClient( + client_options=options, credentials=ga_credentials.AnonymousCredentials() + ) + # It is an error to provide scopes and a transport instance. transport = transports.TranslationServiceGrpcTransport( credentials=ga_credentials.AnonymousCredentials(), @@ -3292,7 +3507,7 @@ def test_parse_common_location_path(): assert expected == actual -def test_client_withDEFAULT_CLIENT_INFO(): +def test_client_with_default_client_info(): client_info = gapic_v1.client_info.ClientInfo() with mock.patch.object( @@ -3357,3 +3572,36 @@ def test_client_ctx(): with client: pass close.assert_called() + + +@pytest.mark.parametrize( + "client_class,transport_class", + [ + (TranslationServiceClient, transports.TranslationServiceGrpcTransport), + ( + TranslationServiceAsyncClient, + transports.TranslationServiceGrpcAsyncIOTransport, + ), + ], +) +def test_api_key_credentials(client_class, transport_class): + with mock.patch.object( + google.auth._default, "get_api_key_credentials", create=True + ) as get_api_key_credentials: + mock_cred = mock.Mock() + get_api_key_credentials.return_value = mock_cred + options = client_options.ClientOptions() + options.api_key = "api_key" + 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=mock_cred, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + client_cert_source_for_mtls=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + always_use_jwt_access=True, + )