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 204e32ec..5b67ea9a 100644 --- a/google/cloud/translate_v3/services/translation_service/transports/base.py +++ b/google/cloud/translate_v3/services/translation_service/transports/base.py @@ -17,9 +17,11 @@ import abc import typing +import packaging.version import pkg_resources from google import auth # type: ignore +import google.api_core from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore @@ -37,6 +39,17 @@ except pkg_resources.DistributionNotFound: DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() +try: + # google.auth.__version__ was added in 1.26.0 + _GOOGLE_AUTH_VERSION = auth.__version__ +except AttributeError: + try: # try pkg_resources if it is available + _GOOGLE_AUTH_VERSION = pkg_resources.get_distribution("google-auth").version + except pkg_resources.DistributionNotFound: # pragma: NO COVER + _GOOGLE_AUTH_VERSION = None + +_API_CORE_VERSION = google.api_core.__version__ + class TranslationServiceTransport(abc.ABC): """Abstract transport class for TranslationService.""" @@ -45,14 +58,15 @@ class TranslationServiceTransport(abc.ABC): "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/cloud-translation", ) + DEFAULT_HOST = "translate.googleapis.com" def __init__( self, *, - host: str = "translate.googleapis.com", + host: str = DEFAULT_HOST, credentials: credentials.Credentials = None, credentials_file: typing.Optional[str] = None, - scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, + scopes: typing.Optional[typing.Sequence[str]] = None, quota_project_id: typing.Optional[str] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, **kwargs, @@ -69,7 +83,7 @@ def __init__( credentials_file (Optional[str]): A file with credentials that can be loaded with :func:`google.auth.load_credentials_from_file`. This argument is mutually exclusive with credentials. - scope (Optional[Sequence[str]]): A list of scopes. + scopes (Optional[Sequence[str]]): A list of scopes. quota_project_id (Optional[str]): An optional project to use for billing and quota. client_info (google.api_core.gapic_v1.client_info.ClientInfo): @@ -83,6 +97,21 @@ def __init__( host += ":443" self._host = host + # If a custom API endpoint is set, set scopes to ensure the auth + # library does not used the self-signed JWT flow for service + # accounts + if host.split(":")[0] != self.DEFAULT_HOST and not scopes: + scopes = self.AUTH_SCOPES + + # TODO: Remove this if/else once google-auth >= 1.25.0 is required + if _GOOGLE_AUTH_VERSION and ( + packaging.version.parse(_GOOGLE_AUTH_VERSION) + >= packaging.version.parse("1.25.0") + ): + scopes_kwargs = {"scopes": scopes, "default_scopes": self.AUTH_SCOPES} + else: + scopes_kwargs = {"scopes": scopes or self.AUTH_SCOPES} + # If no credentials are provided, then determine the appropriate # defaults. if credentials and credentials_file: @@ -92,12 +121,12 @@ def __init__( if credentials_file is not None: credentials, _ = auth.load_credentials_from_file( - credentials_file, scopes=scopes, quota_project_id=quota_project_id + credentials_file, **scopes_kwargs, quota_project_id=quota_project_id ) elif credentials is None: credentials, _ = auth.default( - scopes=scopes, quota_project_id=quota_project_id + **scopes_kwargs, quota_project_id=quota_project_id ) # Save the credentials. 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 7efc5785..142f2fc2 100644 --- a/google/cloud/translate_v3/services/translation_service/transports/grpc.py +++ b/google/cloud/translate_v3/services/translation_service/transports/grpc.py @@ -24,6 +24,8 @@ from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore +import packaging +import pkg_resources import grpc # type: ignore @@ -31,6 +33,7 @@ from google.longrunning import operations_pb2 as operations # type: ignore from .base import TranslationServiceTransport, DEFAULT_CLIENT_INFO +from .base import _GOOGLE_AUTH_VERSION, _API_CORE_VERSION class TranslationServiceGrpcTransport(TranslationServiceTransport): @@ -105,6 +108,21 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + # If a custom API endpoint is set, set scopes to ensure the auth + # library does not used the self-signed JWT flow for service + # accounts + if host.split(":")[0] != self.DEFAULT_HOST and not scopes: + scopes = self.AUTH_SCOPES + + # TODO: Remove this if/else once google-auth >= 1.25.0 is required + if _GOOGLE_AUTH_VERSION and ( + packaging.version.parse(_GOOGLE_AUTH_VERSION) + >= packaging.version.parse("1.25.0") + ): + scopes_kwargs = {"scopes": scopes, "default_scopes": self.AUTH_SCOPES} + else: + scopes_kwargs = {"scopes": scopes or self.AUTH_SCOPES} + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -127,7 +145,7 @@ def __init__( if credentials is None: credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + **scopes_kwargs, quota_project_id=quota_project_id, ) # Create SSL credentials with client_cert_source or application @@ -146,7 +164,7 @@ def __init__( credentials=credentials, credentials_file=credentials_file, ssl_credentials=ssl_credentials, - scopes=scopes or self.AUTH_SCOPES, + scopes=scopes, quota_project_id=quota_project_id, options=[ ("grpc.max_send_message_length", -1), @@ -159,7 +177,7 @@ def __init__( if credentials is None: credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + **scopes_kwargs, quota_project_id=quota_project_id, ) # create a new channel. The provided one is ignored. @@ -168,7 +186,7 @@ def __init__( credentials=credentials, credentials_file=credentials_file, ssl_credentials=ssl_channel_credentials, - scopes=scopes or self.AUTH_SCOPES, + scopes=scopes, quota_project_id=quota_project_id, options=[ ("grpc.max_send_message_length", -1), @@ -184,7 +202,7 @@ def __init__( host=host, credentials=credentials, credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES, + scopes=scopes, quota_project_id=quota_project_id, client_info=client_info, ) @@ -224,13 +242,26 @@ def create_channel( google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` and ``credentials_file`` are passed. """ - scopes = scopes or cls.AUTH_SCOPES + + self_signed_jwt_kwargs = {} + + # TODO: Remove this if/else once google-api-core >= 1.26.0 is required + if _API_CORE_VERSION and ( + packaging.version.parse(_API_CORE_VERSION) + >= packaging.version.parse("1.26.0") + ): + self_signed_jwt_kwargs["default_scopes"] = cls.AUTH_SCOPES + self_signed_jwt_kwargs["scopes"] = scopes + self_signed_jwt_kwargs["default_host"] = cls.DEFAULT_HOST + else: + self_signed_jwt_kwargs["scopes"] = scopes or cls.AUTH_SCOPES + return grpc_helpers.create_channel( host, credentials=credentials, credentials_file=credentials_file, - scopes=scopes, quota_project_id=quota_project_id, + **self_signed_jwt_kwargs, **kwargs, ) 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 d63d8c0b..586777cf 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 @@ -24,6 +24,7 @@ from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore +import packaging.version import grpc # type: ignore from grpc.experimental import aio # type: ignore @@ -32,6 +33,7 @@ from google.longrunning import operations_pb2 as operations # type: ignore from .base import TranslationServiceTransport, DEFAULT_CLIENT_INFO +from .base import _GOOGLE_AUTH_VERSION, _API_CORE_VERSION from .grpc import TranslationServiceGrpcTransport @@ -82,13 +84,25 @@ def create_channel( Returns: aio.Channel: A gRPC AsyncIO channel object. """ - scopes = scopes or cls.AUTH_SCOPES + self_signed_jwt_kwargs = {} + + # TODO: Remove this if/else once google-api-core >= 1.26.0 is required + if _API_CORE_VERSION and ( + packaging.version.parse(_API_CORE_VERSION) + >= packaging.version.parse("1.26.0") + ): + self_signed_jwt_kwargs["default_scopes"] = cls.AUTH_SCOPES + self_signed_jwt_kwargs["scopes"] = scopes + self_signed_jwt_kwargs["default_host"] = cls.DEFAULT_HOST + else: + self_signed_jwt_kwargs["scopes"] = scopes or cls.AUTH_SCOPES + return grpc_helpers_async.create_channel( host, credentials=credentials, credentials_file=credentials_file, - scopes=scopes, quota_project_id=quota_project_id, + **self_signed_jwt_kwargs, **kwargs, ) @@ -150,6 +164,20 @@ def __init__( """ self._ssl_channel_credentials = ssl_channel_credentials + # If a custom API endpoint is set, set scopes to ensure the auth + # library does not used the self-signed JWT flow for service + # accounts + if host.split(":")[0] != self.DEFAULT_HOST and not scopes: + scopes = self.AUTH_SCOPES + + # TODO: Remove this if/else once google-auth >= 1.25.0 is required + if _GOOGLE_AUTH_VERSION and packaging.version.parse( + _GOOGLE_AUTH_VERSION + ) >= packaging.version.parse("1.25.0"): + scopes_kwargs = {"scopes": scopes, "default_scopes": self.AUTH_SCOPES} + else: + scopes_kwargs = {"scopes": scopes or self.AUTH_SCOPES} + if channel: # Sanity check: Ensure that channel and credentials are not both # provided. @@ -172,7 +200,7 @@ def __init__( if credentials is None: credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + quota_project_id=quota_project_id, **scopes_kwargs ) # Create SSL credentials with client_cert_source or application @@ -191,7 +219,7 @@ def __init__( credentials=credentials, credentials_file=credentials_file, ssl_credentials=ssl_credentials, - scopes=scopes or self.AUTH_SCOPES, + scopes=scopes, quota_project_id=quota_project_id, options=[ ("grpc.max_send_message_length", -1), @@ -204,7 +232,7 @@ def __init__( if credentials is None: credentials, _ = auth.default( - scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + quota_project_id=quota_project_id, **scopes_kwargs, ) # create a new channel. The provided one is ignored. @@ -213,7 +241,7 @@ def __init__( credentials=credentials, credentials_file=credentials_file, ssl_credentials=ssl_channel_credentials, - scopes=scopes or self.AUTH_SCOPES, + scopes=scopes, quota_project_id=quota_project_id, options=[ ("grpc.max_send_message_length", -1), @@ -226,7 +254,7 @@ def __init__( host=host, credentials=credentials, credentials_file=credentials_file, - scopes=scopes or self.AUTH_SCOPES, + scopes=scopes, quota_project_id=quota_project_id, client_info=client_info, ) diff --git a/noxfile.py b/noxfile.py index 8004482e..0e3f1430 100644 --- a/noxfile.py +++ b/noxfile.py @@ -18,6 +18,9 @@ from __future__ import absolute_import import os +import sys +import pathlib +import subprocess import shutil import nox @@ -30,6 +33,11 @@ SYSTEM_TEST_PYTHON_VERSIONS = ["3.8"] UNIT_TEST_PYTHON_VERSIONS = ["3.6", "3.7", "3.8", "3.9"] +CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() + +LOWER_BOUND_CONSTRAINTS_FILE = CURRENT_DIRECTORY / "testing" / "constraints-3.6.txt" +PACKAGE_NAME = package_name = subprocess.check_output([sys.executable, "setup.py", "--name"], encoding='utf-8').strip() + @nox.session(python=DEFAULT_PYTHON_VERSION) def lint(session): @@ -69,13 +77,17 @@ def lint_setup_py(session): def default(session): + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + ) + # Install all test dependencies, then install this package in-place. session.install("asyncmock", "pytest-asyncio") session.install( "mock", "pytest", "pytest-cov", ) - session.install("-e", ".") + session.install("-e", ".", "-c", constraints_path) # Run py.test against the unit tests. session.run( @@ -101,6 +113,11 @@ def unit(session): @nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS) def system(session): """Run the system test suite.""" + + constraints_path = str( + CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt" + ) + system_test_path = os.path.join("tests", "system.py") system_test_folder_path = os.path.join("tests", "system") @@ -125,7 +142,16 @@ def system(session): session.install( "mock", "pytest", "google-cloud-testutils", ) - session.install("-e", ".") + session.install("-e", ".", "-c", constraints_path) + + # Temporarily install google-api-core from HEAD to test self-signed jwt + session.install( + "-e", + "git+https://github.com/googleapis/python-api-core.git@master#egg=google-api-core", + ) + + # Install google-auth *again* since it will have been overwitten by api-core + session.install("google-auth", "-c", constraints_path) # Run py.test against the system tests. if system_test_exists: @@ -147,6 +173,37 @@ def cover(session): session.run("coverage", "erase") +@nox.session(python="3.8") +def check_lower_bounds(session): + """Check lower bounds in setup.py are reflected in constraints file""" + session.install("google-cloud-testutils") + session.install(".") + + session.run( + "lower-bound-checker", + "check", + "--package-name", + PACKAGE_NAME, + "--constraints-file", + str(LOWER_BOUND_CONSTRAINTS_FILE), + ) + + +@nox.session(python="3.8") +def update_lower_bounds(session): + """Update lower bounds in constraints.txt to match setup.py""" + session.install("google-cloud-testutils") + session.install(".") + + session.run( + "lower-bound-checker", + "update", + "--package-name", + PACKAGE_NAME, + "--constraints-file", + str(LOWER_BOUND_CONSTRAINTS_FILE), + ) + @nox.session(python=DEFAULT_PYTHON_VERSION) def docs(session): """Build the docs for this library.""" diff --git a/setup.py b/setup.py index 9045fdcf..1bd0bca9 100644 --- a/setup.py +++ b/setup.py @@ -29,10 +29,10 @@ # 'Development Status :: 5 - Production/Stable' release_status = "Development Status :: 5 - Production/Stable" dependencies = [ - "google-api-core[grpc] >= 1.22.0, < 2.0.0dev", - "google-cloud-core >= 1.1.0, < 2.0dev", - "libcst >= 0.2.5", - "proto-plus >= 0.4.0", + "google-api-core[grpc] >= 1.22.2, < 2.0.0dev", + "google-cloud-core >= 1.4.0, < 2.0dev", + "proto-plus >= 1.4.0", + "packaging >= 14.3", ] extras = {} diff --git a/testing/constraints-3.10.txt b/testing/constraints-3.10.txt new file mode 100644 index 00000000..e69de29b diff --git a/testing/constraints-3.11.txt b/testing/constraints-3.11.txt new file mode 100644 index 00000000..e69de29b diff --git a/testing/constraints-3.6.txt b/testing/constraints-3.6.txt new file mode 100644 index 00000000..81b134a7 --- /dev/null +++ b/testing/constraints-3.6.txt @@ -0,0 +1,4 @@ +google-api-core==1.22.2 +google-cloud-core==1.4.0 +packaging==14.3 +proto-plus==1.4.0 \ No newline at end of file diff --git a/testing/constraints-3.7.txt b/testing/constraints-3.7.txt new file mode 100644 index 00000000..e69de29b diff --git a/testing/constraints-3.8.txt b/testing/constraints-3.8.txt new file mode 100644 index 00000000..e69de29b diff --git a/testing/constraints-3.9.txt b/testing/constraints-3.9.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/gapic/translate_v3/test_translation_service.py b/tests/unit/gapic/translate_v3/test_translation_service.py index 215b08b1..3a1f061a 100644 --- a/tests/unit/gapic/translate_v3/test_translation_service.py +++ b/tests/unit/gapic/translate_v3/test_translation_service.py @@ -21,6 +21,7 @@ import grpc from grpc.experimental import aio import math +import packaging.version import pytest from proto.marshal.rules.dates import DurationRule, TimestampRule @@ -43,12 +44,41 @@ ) from google.cloud.translate_v3.services.translation_service import pagers from google.cloud.translate_v3.services.translation_service import transports +from google.cloud.translate_v3.services.translation_service.transports.base import ( + _GOOGLE_AUTH_VERSION, +) +from google.cloud.translate_v3.services.translation_service.transports.base import ( + _API_CORE_VERSION, +) from google.cloud.translate_v3.types import translation_service from google.longrunning import operations_pb2 from google.oauth2 import service_account from google.protobuf import timestamp_pb2 as timestamp # type: ignore +# TODO(busunkim): Once google-api-core >= 1.26.0 is required: +# - Delete all the api-core and auth "less than" test cases +# - Delete these pytest markers (Make the "greater than or equal to" tests the default). +requires_google_auth_lt_1_25_0 = pytest.mark.skipif( + packaging.version.parse(_GOOGLE_AUTH_VERSION) >= packaging.version.parse("1.25.0"), + reason="This test requires google-auth < 1.25.0", +) +requires_google_auth_gte_1_25_0 = pytest.mark.skipif( + packaging.version.parse(_GOOGLE_AUTH_VERSION) < packaging.version.parse("1.25.0"), + reason="This test requires google-auth >= 1.25.0", +) + +requires_api_core_lt_1_26_0 = pytest.mark.skipif( + packaging.version.parse(_API_CORE_VERSION) >= packaging.version.parse("1.26.0"), + reason="This test requires google-api-core < 1.26.0", +) + +requires_api_core_gte_1_26_0 = pytest.mark.skipif( + packaging.version.parse(_API_CORE_VERSION) < packaging.version.parse("1.26.0"), + reason="This test requires google-api-core >= 1.26.0", +) + + def client_cert_source_callback(): return b"cert bytes", b"key bytes" @@ -2367,6 +2397,7 @@ def test_translation_service_base_transport(): transport.operations_client +@requires_google_auth_gte_1_25_0 def test_translation_service_base_transport_with_credentials_file(): # Instantiate the base transport with a credentials file with mock.patch.object( @@ -2381,17 +2412,71 @@ def test_translation_service_base_transport_with_credentials_file(): ) load_creds.assert_called_once_with( "credentials.json", + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-translation", + ), + quota_project_id="octopus", + ) + + +@requires_google_auth_lt_1_25_0 +def test_translation_service_base_transport_with_credentials_file_old_google_auth(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + auth, "load_credentials_from_file" + ) as load_creds, mock.patch( + "google.cloud.translate_v3.services.translation_service.transports.TranslationServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.TranslationServiceTransport( + credentials_file="credentials.json", quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-translation", + ), + quota_project_id="octopus", + ) + + +@requires_google_auth_gte_1_25_0 +def test_translation_service_base_transport_custom_host(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + auth, "load_credentials_from_file" + ) as load_creds, mock.patch( + "google.cloud.translate_v3.services.translation_service.transports.TranslationServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.TranslationServiceTransport( + host="squid.clam.whelk", + credentials_file="credentials.json", + quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + # scopes should be set since custom endpoint was passed scopes=( "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/cloud-translation", ), + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-translation", + ), quota_project_id="octopus", ) def test_translation_service_base_transport_with_adc(): # Test the default credentials are used if credentials and credentials_file are None. - with mock.patch.object(auth, "default") as adc, mock.patch( + with mock.patch.object(auth, "default", autospec=True) as adc, mock.patch( "google.cloud.translate_v3.services.translation_service.transports.TranslationServiceTransport._prep_wrapped_messages" ) as Transport: Transport.return_value = None @@ -2400,9 +2485,26 @@ def test_translation_service_base_transport_with_adc(): adc.assert_called_once() +@requires_google_auth_gte_1_25_0 def test_translation_service_auth_adc(): # If no credentials are provided, we should use ADC credentials. - with mock.patch.object(auth, "default") as adc: + with mock.patch.object(auth, "default", autospec=True) as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + TranslationServiceClient() + adc.assert_called_once_with( + scopes=None, + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-translation", + ), + quota_project_id=None, + ) + + +@requires_google_auth_lt_1_25_0 +def test_translation_service_auth_adc_old_google_auth(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(auth, "default", autospec=True) as adc: adc.return_value = (credentials.AnonymousCredentials(), None) TranslationServiceClient() adc.assert_called_once_with( @@ -2414,14 +2516,72 @@ def test_translation_service_auth_adc(): ) -def test_translation_service_transport_auth_adc(): +@pytest.mark.parametrize( + "transport_class", + [ + transports.TranslationServiceGrpcTransport, + transports.TranslationServiceGrpcAsyncIOTransport, + ], +) +@requires_google_auth_gte_1_25_0 +def test_translation_service_transport_auth_adc(transport_class): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(auth, "default", autospec=True) as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class(quota_project_id="octopus", scopes=["1", "2"]) + adc.assert_called_once_with( + scopes=["1", "2"], + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-translation", + ), + quota_project_id="octopus", + ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.TranslationServiceGrpcTransport, + transports.TranslationServiceGrpcAsyncIOTransport, + ], +) +@requires_google_auth_gte_1_25_0 +def test_translation_service_transport_auth_adc_custom_host(transport_class): # If credentials and host are not provided, the transport class should use # ADC credentials. with mock.patch.object(auth, "default") as adc: adc.return_value = (credentials.AnonymousCredentials(), None) - transports.TranslationServiceGrpcTransport( - host="squid.clam.whelk", quota_project_id="octopus" + transport_class(host="squid.clam.whelk", quota_project_id="octopus") + adc.assert_called_once_with( + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-translation", + ), + # scopes should be set since a custom endpoint (host) was set + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-translation", + ), + quota_project_id="octopus", ) + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.TranslationServiceGrpcTransport, + transports.TranslationServiceGrpcAsyncIOTransport, + ], +) +@requires_google_auth_lt_1_25_0 +def test_translation_service_transport_auth_adc_old_google_auth(transport_class): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(auth, "default", autospec=True) as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class(quota_project_id="octopus") adc.assert_called_once_with( scopes=( "https://www.googleapis.com/auth/cloud-platform", @@ -2431,6 +2591,112 @@ def test_translation_service_transport_auth_adc(): ) +@pytest.mark.parametrize( + "transport_class,grpc_helpers", + [ + (transports.TranslationServiceGrpcTransport, grpc_helpers), + (transports.TranslationServiceGrpcAsyncIOTransport, grpc_helpers_async), + ], +) +@requires_api_core_gte_1_26_0 +def test_translation_service_transport_create_channel(transport_class, grpc_helpers): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(auth, "default", autospec=True) as adc, mock.patch.object( + grpc_helpers, "create_channel", autospec=True + ) as create_channel: + creds = credentials.AnonymousCredentials() + adc.return_value = (creds, None) + transport_class(quota_project_id="octopus", scopes=["1", "2"]) + + create_channel.assert_called_with( + "translate.googleapis.com:443", + credentials=creds, + credentials_file=None, + quota_project_id="octopus", + default_scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-translation", + ), + scopes=["1", "2"], + default_host="translate.googleapis.com", + ssl_credentials=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + +@pytest.mark.parametrize( + "transport_class,grpc_helpers", + [ + (transports.TranslationServiceGrpcTransport, grpc_helpers), + (transports.TranslationServiceGrpcAsyncIOTransport, grpc_helpers_async), + ], +) +@requires_api_core_lt_1_26_0 +def test_translation_service_transport_create_channel(transport_class, grpc_helpers): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(auth, "default", autospec=True) as adc, mock.patch.object( + grpc_helpers, "create_channel", autospec=True + ) as create_channel: + creds = credentials.AnonymousCredentials() + adc.return_value = (creds, None) + transport_class(quota_project_id="octopus") + + create_channel.assert_called_with( + "translate.googleapis.com:443", + credentials=creds, + credentials_file=None, + quota_project_id="octopus", + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/cloud-translation", + ), + ssl_credentials=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + +@pytest.mark.parametrize( + "transport_class,grpc_helpers", + [ + (transports.TranslationServiceGrpcTransport, grpc_helpers), + (transports.TranslationServiceGrpcAsyncIOTransport, grpc_helpers_async), + ], +) +@requires_api_core_lt_1_26_0 +def test_translation_service_transport_create_channel_old_api_core_user_scopes( + transport_class, grpc_helpers +): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(auth, "default", autospec=True) as adc, mock.patch.object( + grpc_helpers, "create_channel", autospec=True + ) as create_channel: + creds = credentials.AnonymousCredentials() + adc.return_value = (creds, None) + transport_class(quota_project_id="octopus", scopes=["1", "2"]) + + create_channel.assert_called_with( + "translate.googleapis.com:443", + credentials=creds, + credentials_file=None, + quota_project_id="octopus", + scopes=["1", "2"], + ssl_credentials=None, + options=[ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ], + ) + + def test_translation_service_host_no_port(): client = TranslationServiceClient( credentials=credentials.AnonymousCredentials(),