From 887d8d501ce9255fee44170b5fc40ebfb1ea953d Mon Sep 17 00:00:00 2001 From: Yoshi Automation Bot Date: Fri, 19 Jun 2020 19:08:14 -0700 Subject: [PATCH] feat: add async client (#53) This PR was generated using Autosynth. :rainbow: Synth log will be available here: https://source.cloud.google.com/results/invocations/6a02e8e8-a66f-42c9-b48a-8df33bd9ffe3/targets - [ ] To automatically regenerate this PR, check this box. Source-Link: https://github.com/googleapis/synthtool/commit/cd522c3b4dde821766d95c80ae5aeb43d7a41170 --- docs/conf.py | 2 +- docs/texttospeech_v1/services.rst | 6 +- docs/texttospeech_v1/types.rst | 4 +- docs/texttospeech_v1beta1/services.rst | 6 +- docs/texttospeech_v1beta1/types.rst | 4 +- google/cloud/texttospeech/__init__.py | 5 +- google/cloud/texttospeech_v1/__init__.py | 1 - .../services/text_to_speech/__init__.py | 3 +- .../services/text_to_speech/async_client.py | 262 ++++++ .../services/text_to_speech/client.py | 81 +- .../text_to_speech/transports/__init__.py | 8 +- .../text_to_speech/transports/base.py | 20 +- .../text_to_speech/transports/grpc.py | 21 +- .../text_to_speech/transports/grpc_asyncio.py | 224 +++++ google/cloud/texttospeech_v1beta1/__init__.py | 1 - .../services/text_to_speech/__init__.py | 3 +- .../services/text_to_speech/async_client.py | 262 ++++++ .../services/text_to_speech/client.py | 81 +- .../text_to_speech/transports/__init__.py | 8 +- .../text_to_speech/transports/base.py | 20 +- .../text_to_speech/transports/grpc.py | 21 +- .../text_to_speech/transports/grpc_asyncio.py | 224 +++++ noxfile.py | 1 + setup.py | 2 +- synth.metadata | 6 +- synth.py | 8 +- .../{ => gapic}/texttospeech_v1/__init__.py | 0 .../texttospeech_v1/test_text_to_speech.py | 782 +++++++++++++++++ .../texttospeech_v1beta1/__init__.py | 0 .../test_text_to_speech.py | 784 ++++++++++++++++++ .../texttospeech_v1/test_text_to_speech.py | 453 ---------- .../test_text_to_speech.py | 453 ---------- 32 files changed, 2728 insertions(+), 1028 deletions(-) create mode 100644 google/cloud/texttospeech_v1/services/text_to_speech/async_client.py create mode 100644 google/cloud/texttospeech_v1/services/text_to_speech/transports/grpc_asyncio.py create mode 100644 google/cloud/texttospeech_v1beta1/services/text_to_speech/async_client.py create mode 100644 google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/grpc_asyncio.py rename tests/unit/{ => gapic}/texttospeech_v1/__init__.py (100%) create mode 100644 tests/unit/gapic/texttospeech_v1/test_text_to_speech.py rename tests/unit/{ => gapic}/texttospeech_v1beta1/__init__.py (100%) create mode 100644 tests/unit/gapic/texttospeech_v1beta1/test_text_to_speech.py delete mode 100644 tests/unit/texttospeech_v1/test_text_to_speech.py delete mode 100644 tests/unit/texttospeech_v1beta1/test_text_to_speech.py diff --git a/docs/conf.py b/docs/conf.py index 08deeece..ebd435af 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -43,7 +43,7 @@ # autodoc/autosummary flags autoclass_content = "both" -autodoc_default_flags = ["members"] +autodoc_default_options = {"members": True} autosummary_generate = True diff --git a/docs/texttospeech_v1/services.rst b/docs/texttospeech_v1/services.rst index 9f65d607..23799bc0 100644 --- a/docs/texttospeech_v1/services.rst +++ b/docs/texttospeech_v1/services.rst @@ -1,6 +1,6 @@ -Client for Google Cloud Texttospeech API -======================================== +Services for Google Cloud Texttospeech v1 API +============================================= -.. automodule:: google.cloud.texttospeech_v1 +.. automodule:: google.cloud.texttospeech_v1.services.text_to_speech :members: :inherited-members: diff --git a/docs/texttospeech_v1/types.rst b/docs/texttospeech_v1/types.rst index bdbf4056..626ff0d3 100644 --- a/docs/texttospeech_v1/types.rst +++ b/docs/texttospeech_v1/types.rst @@ -1,5 +1,5 @@ -Types for Google Cloud Texttospeech API -======================================= +Types for Google Cloud Texttospeech v1 API +========================================== .. automodule:: google.cloud.texttospeech_v1.types :members: diff --git a/docs/texttospeech_v1beta1/services.rst b/docs/texttospeech_v1beta1/services.rst index 22fd9307..ddf8876a 100644 --- a/docs/texttospeech_v1beta1/services.rst +++ b/docs/texttospeech_v1beta1/services.rst @@ -1,6 +1,6 @@ -Client for Google Cloud Texttospeech API -======================================== +Services for Google Cloud Texttospeech v1beta1 API +================================================== -.. automodule:: google.cloud.texttospeech_v1beta1 +.. automodule:: google.cloud.texttospeech_v1beta1.services.text_to_speech :members: :inherited-members: diff --git a/docs/texttospeech_v1beta1/types.rst b/docs/texttospeech_v1beta1/types.rst index ba72a5f1..84c63fc8 100644 --- a/docs/texttospeech_v1beta1/types.rst +++ b/docs/texttospeech_v1beta1/types.rst @@ -1,5 +1,5 @@ -Types for Google Cloud Texttospeech API -======================================= +Types for Google Cloud Texttospeech v1beta1 API +=============================================== .. automodule:: google.cloud.texttospeech_v1beta1.types :members: diff --git a/google/cloud/texttospeech/__init__.py b/google/cloud/texttospeech/__init__.py index 5a80d1d1..c111427a 100644 --- a/google/cloud/texttospeech/__init__.py +++ b/google/cloud/texttospeech/__init__.py @@ -15,7 +15,9 @@ # limitations under the License. # - +from google.cloud.texttospeech_v1.services.text_to_speech.async_client import ( + TextToSpeechAsyncClient, +) from google.cloud.texttospeech_v1.services.text_to_speech.client import ( TextToSpeechClient, ) @@ -39,6 +41,7 @@ "SynthesisInput", "SynthesizeSpeechRequest", "SynthesizeSpeechResponse", + "TextToSpeechAsyncClient", "TextToSpeechClient", "Voice", "VoiceSelectionParams", diff --git a/google/cloud/texttospeech_v1/__init__.py b/google/cloud/texttospeech_v1/__init__.py index b0f218b1..4d285a25 100644 --- a/google/cloud/texttospeech_v1/__init__.py +++ b/google/cloud/texttospeech_v1/__init__.py @@ -15,7 +15,6 @@ # limitations under the License. # - from .services.text_to_speech import TextToSpeechClient from .types.cloud_tts import AudioConfig from .types.cloud_tts import AudioEncoding diff --git a/google/cloud/texttospeech_v1/services/text_to_speech/__init__.py b/google/cloud/texttospeech_v1/services/text_to_speech/__init__.py index 9ec2d969..989f886a 100644 --- a/google/cloud/texttospeech_v1/services/text_to_speech/__init__.py +++ b/google/cloud/texttospeech_v1/services/text_to_speech/__init__.py @@ -16,5 +16,6 @@ # from .client import TextToSpeechClient +from .async_client import TextToSpeechAsyncClient -__all__ = ("TextToSpeechClient",) +__all__ = ("TextToSpeechClient", "TextToSpeechAsyncClient") diff --git a/google/cloud/texttospeech_v1/services/text_to_speech/async_client.py b/google/cloud/texttospeech_v1/services/text_to_speech/async_client.py new file mode 100644 index 00000000..b450d611 --- /dev/null +++ b/google/cloud/texttospeech_v1/services/text_to_speech/async_client.py @@ -0,0 +1,262 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +import functools +import re +from typing import Dict, Sequence, Tuple, Type, Union +import pkg_resources + +import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore +from google.oauth2 import service_account # type: ignore + +from google.cloud.texttospeech_v1.types import cloud_tts + +from .transports.base import TextToSpeechTransport +from .transports.grpc_asyncio import TextToSpeechGrpcAsyncIOTransport +from .client import TextToSpeechClient + + +class TextToSpeechAsyncClient: + """Service that implements Google Cloud Text-to-Speech API.""" + + _client: TextToSpeechClient + + DEFAULT_ENDPOINT = TextToSpeechClient.DEFAULT_ENDPOINT + DEFAULT_MTLS_ENDPOINT = TextToSpeechClient.DEFAULT_MTLS_ENDPOINT + + from_service_account_file = TextToSpeechClient.from_service_account_file + from_service_account_json = from_service_account_file + + get_transport_class = functools.partial( + type(TextToSpeechClient).get_transport_class, type(TextToSpeechClient) + ) + + def __init__( + self, + *, + credentials: credentials.Credentials = None, + transport: Union[str, TextToSpeechTransport] = "grpc_asyncio", + client_options: ClientOptions = None, + ) -> None: + """Instantiate the text to speech client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + transport (Union[str, ~.TextToSpeechTransport]): The + transport to use. If set to None, a transport is chosen + automatically. + client_options (ClientOptions): Custom options for the client. It + won't take effect if a ``transport`` instance is provided. + (1) The ``api_endpoint`` property can be used to override the + default endpoint provided by the client. GOOGLE_API_USE_MTLS + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint, this is the default value for + the environment variable) and "auto" (auto switch to the default + mTLS endpoint if client SSL credentials is present). However, + the ``api_endpoint`` property takes precedence if provided. + (2) The ``client_cert_source`` property is used to provide client + SSL credentials for mutual TLS transport. If not provided, the + default SSL credentials will be used if present. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + """ + + self._client = TextToSpeechClient( + credentials=credentials, transport=transport, client_options=client_options + ) + + async def list_voices( + self, + request: cloud_tts.ListVoicesRequest = None, + *, + language_code: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> cloud_tts.ListVoicesResponse: + r"""Returns a list of Voice supported for synthesis. + + Args: + request (:class:`~.cloud_tts.ListVoicesRequest`): + The request object. The top-level message sent by the + client for the `ListVoices` method. + language_code (:class:`str`): + Optional. Recommended. + `BCP-47 `__ + language tag. If specified, the ListVoices call will + only return voices that can be used to synthesize this + language_code. E.g. when specifying "en-NZ", you will + get supported "en-\*" voices; when specifying "no", you + will get supported "no-\*" (Norwegian) and "nb-*" + (Norwegian Bokmal) voices; specifying "zh" will also get + supported "cmn-*" voices; specifying "zh-hk" will also + get supported "yue-\*" voices. + This corresponds to the ``language_code`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.cloud_tts.ListVoicesResponse: + The message returned to the client by the ``ListVoices`` + method. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([language_code]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = cloud_tts.ListVoicesRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if language_code is not None: + request.language_code = language_code + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.list_voices, + default_timeout=None, + client_info=_client_info, + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata) + + # Done; return the response. + return response + + async def synthesize_speech( + self, + request: cloud_tts.SynthesizeSpeechRequest = None, + *, + input: cloud_tts.SynthesisInput = None, + voice: cloud_tts.VoiceSelectionParams = None, + audio_config: cloud_tts.AudioConfig = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> cloud_tts.SynthesizeSpeechResponse: + r"""Synthesizes speech synchronously: receive results + after all text input has been processed. + + Args: + request (:class:`~.cloud_tts.SynthesizeSpeechRequest`): + The request object. The top-level message sent by the + client for the `SynthesizeSpeech` method. + input (:class:`~.cloud_tts.SynthesisInput`): + Required. The Synthesizer requires + either plain text or SSML as input. + This corresponds to the ``input`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + voice (:class:`~.cloud_tts.VoiceSelectionParams`): + Required. The desired voice of the + synthesized audio. + This corresponds to the ``voice`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + audio_config (:class:`~.cloud_tts.AudioConfig`): + Required. The configuration of the + synthesized audio. + This corresponds to the ``audio_config`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.cloud_tts.SynthesizeSpeechResponse: + The message returned to the client by the + ``SynthesizeSpeech`` method. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([input, voice, audio_config]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = cloud_tts.SynthesizeSpeechRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if input is not None: + request.input = input + if voice is not None: + request.voice = voice + if audio_config is not None: + request.audio_config = audio_config + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.synthesize_speech, + default_timeout=None, + client_info=_client_info, + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata) + + # Done; return the response. + return response + + +try: + _client_info = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution( + "google-cloud-texttospeech" + ).version + ) +except pkg_resources.DistributionNotFound: + _client_info = gapic_v1.client_info.ClientInfo() + + +__all__ = ("TextToSpeechAsyncClient",) diff --git a/google/cloud/texttospeech_v1/services/text_to_speech/client.py b/google/cloud/texttospeech_v1/services/text_to_speech/client.py index 5c9e45f8..b87cc114 100644 --- a/google/cloud/texttospeech_v1/services/text_to_speech/client.py +++ b/google/cloud/texttospeech_v1/services/text_to_speech/client.py @@ -16,6 +16,7 @@ # from collections import OrderedDict +import os import re from typing import Callable, Dict, Sequence, Tuple, Type, Union import pkg_resources @@ -25,12 +26,15 @@ from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore from google.auth import credentials # type: ignore +from google.auth.transport import mtls # type: ignore +from google.auth.exceptions import MutualTLSChannelError # type: ignore from google.oauth2 import service_account # type: ignore from google.cloud.texttospeech_v1.types import cloud_tts from .transports.base import TextToSpeechTransport from .transports.grpc import TextToSpeechGrpcTransport +from .transports.grpc_asyncio import TextToSpeechGrpcAsyncIOTransport class TextToSpeechClientMeta(type): @@ -43,6 +47,7 @@ class TextToSpeechClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[TextToSpeechTransport]] _transport_registry["grpc"] = TextToSpeechGrpcTransport + _transport_registry["grpc_asyncio"] = TextToSpeechGrpcAsyncIOTransport def get_transport_class(cls, label: str = None) -> Type[TextToSpeechTransport]: """Return an appropriate transport class. @@ -138,21 +143,49 @@ def __init__( transport (Union[str, ~.TextToSpeechTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (ClientOptions): Custom options for the client. + client_options (ClientOptions): Custom options for the client. It + won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the - default endpoint provided by the client. - (2) If ``transport`` argument is None, ``client_options`` can be - used to create a mutual TLS transport. If ``client_cert_source`` - is provided, mutual TLS transport will be created with the given - ``api_endpoint`` or the default mTLS endpoint, and the client - SSL credentials obtained from ``client_cert_source``. + default endpoint provided by the client. GOOGLE_API_USE_MTLS + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint, this is the default value for + the environment variable) and "auto" (auto switch to the default + mTLS endpoint if client SSL credentials is present). However, + the ``api_endpoint`` property takes precedence if provided. + (2) The ``client_cert_source`` property is used to provide client + SSL credentials for mutual TLS transport. If not provided, the + default SSL credentials will be used if present. Raises: - google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport creation failed for any reason. """ if isinstance(client_options, dict): client_options = ClientOptions.from_dict(client_options) + if client_options is None: + client_options = ClientOptions.ClientOptions() + + if client_options.api_endpoint is None: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "never") + if use_mtls_env == "never": + client_options.api_endpoint = self.DEFAULT_ENDPOINT + elif use_mtls_env == "always": + client_options.api_endpoint = self.DEFAULT_MTLS_ENDPOINT + elif use_mtls_env == "auto": + has_client_cert_source = ( + client_options.client_cert_source is not None + or mtls.has_default_client_cert_source() + ) + client_options.api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT + if has_client_cert_source + else self.DEFAULT_ENDPOINT + ) + else: + raise MutualTLSChannelError( + "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: never, auto, always" + ) # Save or instantiate the transport. # Ordinarily, we provide the transport, but allowing a custom transport @@ -165,38 +198,12 @@ def __init__( "provide its credentials directly." ) self._transport = transport - elif client_options is None or ( - client_options.api_endpoint is None - and client_options.client_cert_source is None - ): - # Don't trigger mTLS if we get an empty ClientOptions. + else: Transport = type(self).get_transport_class(transport) self._transport = Transport( - credentials=credentials, host=self.DEFAULT_ENDPOINT - ) - else: - # We have a non-empty ClientOptions. If client_cert_source is - # provided, trigger mTLS with user provided endpoint or the default - # mTLS endpoint. - if client_options.client_cert_source: - api_mtls_endpoint = ( - client_options.api_endpoint - if client_options.api_endpoint - else self.DEFAULT_MTLS_ENDPOINT - ) - else: - api_mtls_endpoint = None - - api_endpoint = ( - client_options.api_endpoint - if client_options.api_endpoint - else self.DEFAULT_ENDPOINT - ) - - self._transport = TextToSpeechGrpcTransport( credentials=credentials, - host=api_endpoint, - api_mtls_endpoint=api_mtls_endpoint, + host=client_options.api_endpoint, + api_mtls_endpoint=client_options.api_endpoint, client_cert_source=client_options.client_cert_source, ) diff --git a/google/cloud/texttospeech_v1/services/text_to_speech/transports/__init__.py b/google/cloud/texttospeech_v1/services/text_to_speech/transports/__init__.py index bd57d801..3a92efc9 100644 --- a/google/cloud/texttospeech_v1/services/text_to_speech/transports/__init__.py +++ b/google/cloud/texttospeech_v1/services/text_to_speech/transports/__init__.py @@ -20,11 +20,17 @@ from .base import TextToSpeechTransport from .grpc import TextToSpeechGrpcTransport +from .grpc_asyncio import TextToSpeechGrpcAsyncIOTransport # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[TextToSpeechTransport]] _transport_registry["grpc"] = TextToSpeechGrpcTransport +_transport_registry["grpc_asyncio"] = TextToSpeechGrpcAsyncIOTransport -__all__ = ("TextToSpeechTransport", "TextToSpeechGrpcTransport") +__all__ = ( + "TextToSpeechTransport", + "TextToSpeechGrpcTransport", + "TextToSpeechGrpcAsyncIOTransport", +) diff --git a/google/cloud/texttospeech_v1/services/text_to_speech/transports/base.py b/google/cloud/texttospeech_v1/services/text_to_speech/transports/base.py index d73ae0bf..6aa84b25 100644 --- a/google/cloud/texttospeech_v1/services/text_to_speech/transports/base.py +++ b/google/cloud/texttospeech_v1/services/text_to_speech/transports/base.py @@ -24,7 +24,7 @@ from google.cloud.texttospeech_v1.types import cloud_tts -class TextToSpeechTransport(metaclass=abc.ABCMeta): +class TextToSpeechTransport(abc.ABC): """Abstract transport class for TextToSpeech.""" AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",) @@ -34,6 +34,7 @@ def __init__( *, host: str = "texttospeech.googleapis.com", credentials: credentials.Credentials = None, + **kwargs, ) -> None: """Instantiate the transport. @@ -61,16 +62,25 @@ def __init__( @property def list_voices( self - ) -> typing.Callable[[cloud_tts.ListVoicesRequest], cloud_tts.ListVoicesResponse]: - raise NotImplementedError + ) -> typing.Callable[ + [cloud_tts.ListVoicesRequest], + typing.Union[ + cloud_tts.ListVoicesResponse, typing.Awaitable[cloud_tts.ListVoicesResponse] + ], + ]: + raise NotImplementedError() @property def synthesize_speech( self ) -> typing.Callable[ - [cloud_tts.SynthesizeSpeechRequest], cloud_tts.SynthesizeSpeechResponse + [cloud_tts.SynthesizeSpeechRequest], + typing.Union[ + cloud_tts.SynthesizeSpeechResponse, + typing.Awaitable[cloud_tts.SynthesizeSpeechResponse], + ], ]: - raise NotImplementedError + raise NotImplementedError() __all__ = ("TextToSpeechTransport",) diff --git a/google/cloud/texttospeech_v1/services/text_to_speech/transports/grpc.py b/google/cloud/texttospeech_v1/services/text_to_speech/transports/grpc.py index 4f121d0c..498b3501 100644 --- a/google/cloud/texttospeech_v1/services/text_to_speech/transports/grpc.py +++ b/google/cloud/texttospeech_v1/services/text_to_speech/transports/grpc.py @@ -15,9 +15,10 @@ # limitations under the License. # -from typing import Callable, Dict, Tuple +from typing import Callable, Dict, Optional, Sequence, Tuple from google.api_core import grpc_helpers # type: ignore +from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -42,6 +43,8 @@ class TextToSpeechGrpcTransport(TextToSpeechTransport): top of HTTP/2); the ``grpcio`` package must be installed. """ + _stubs: Dict[str, Callable] + def __init__( self, *, @@ -73,8 +76,8 @@ def __init__( is None. Raises: - google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport - creation failed for any reason. + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. """ if channel: # Sanity check: Ensure that channel and credentials are not both @@ -90,6 +93,9 @@ def __init__( else api_mtls_endpoint + ":443" ) + if credentials is None: + credentials, _ = auth.default(scopes=self.AUTH_SCOPES) + # Create SSL credentials with client_cert_source or application # default SSL credentials. if client_cert_source: @@ -101,7 +107,7 @@ def __init__( ssl_credentials = SslCredentials().ssl_credentials # create a new channel. The provided one is ignored. - self._grpc_channel = grpc_helpers.create_channel( + self._grpc_channel = type(self).create_channel( host, credentials=credentials, ssl_credentials=ssl_credentials, @@ -117,6 +123,7 @@ def create_channel( cls, host: str = "texttospeech.googleapis.com", credentials: credentials.Credentials = None, + scopes: Optional[Sequence[str]] = None, **kwargs ) -> grpc.Channel: """Create and return a gRPC channel object. @@ -127,13 +134,17 @@ def create_channel( credentials identify this application to the service. If none are specified, the client will attempt to ascertain the credentials from the environment. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. kwargs (Optional[dict]): Keyword arguments, which are passed to the channel creation. Returns: grpc.Channel: A gRPC channel object. """ + scopes = scopes or cls.AUTH_SCOPES return grpc_helpers.create_channel( - host, credentials=credentials, scopes=cls.AUTH_SCOPES, **kwargs + host, credentials=credentials, scopes=scopes, **kwargs ) @property diff --git a/google/cloud/texttospeech_v1/services/text_to_speech/transports/grpc_asyncio.py b/google/cloud/texttospeech_v1/services/text_to_speech/transports/grpc_asyncio.py new file mode 100644 index 00000000..e93dfb2f --- /dev/null +++ b/google/cloud/texttospeech_v1/services/text_to_speech/transports/grpc_asyncio.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple + +from google.api_core import grpc_helpers_async # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore + +import grpc # type: ignore +from grpc.experimental import aio # type: ignore + +from google.cloud.texttospeech_v1.types import cloud_tts + +from .base import TextToSpeechTransport +from .grpc import TextToSpeechGrpcTransport + + +class TextToSpeechGrpcAsyncIOTransport(TextToSpeechTransport): + """gRPC AsyncIO backend transport for TextToSpeech. + + Service that implements Google Cloud Text-to-Speech API. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _grpc_channel: aio.Channel + _stubs: Dict[str, Callable] = {} + + @classmethod + def create_channel( + cls, + host: str = "texttospeech.googleapis.com", + credentials: credentials.Credentials = None, + scopes: Optional[Sequence[str]] = None, + **kwargs + ) -> aio.Channel: + """Create and return a gRPC AsyncIO channel object. + Args: + address (Optional[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + aio.Channel: A gRPC AsyncIO channel object. + """ + scopes = scopes or cls.AUTH_SCOPES + return grpc_helpers_async.create_channel( + host, credentials=credentials, scopes=scopes, **kwargs + ) + + def __init__( + self, + *, + host: str = "texttospeech.googleapis.com", + credentials: credentials.Credentials = None, + channel: aio.Channel = None, + api_mtls_endpoint: str = None, + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + This argument is ignored if ``channel`` is provided. + channel (Optional[aio.Channel]): A ``Channel`` instance through + which to make calls. + api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If + provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or applicatin default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A + callback to provide client SSL certificate bytes and private key + bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` + is None. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + """ + if channel: + # Sanity check: Ensure that channel and credentials are not both + # provided. + credentials = False + + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + elif api_mtls_endpoint: + host = ( + api_mtls_endpoint + if ":" in api_mtls_endpoint + else api_mtls_endpoint + ":443" + ) + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + ssl_credentials = SslCredentials().ssl_credentials + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + ssl_credentials=ssl_credentials, + scopes=self.AUTH_SCOPES, + ) + + # Run the base constructor. + super().__init__(host=host, credentials=credentials) + self._stubs = {} + + @property + def grpc_channel(self) -> aio.Channel: + """Create the channel designed to connect to this service. + + This property caches on the instance; repeated calls return + the same channel. + """ + # Sanity check: Only create a new channel if we do not already + # have one. + if not hasattr(self, "_grpc_channel"): + self._grpc_channel = self.create_channel( + self._host, credentials=self._credentials + ) + + # Return the channel from cache. + return self._grpc_channel + + @property + def list_voices( + self + ) -> Callable[ + [cloud_tts.ListVoicesRequest], Awaitable[cloud_tts.ListVoicesResponse] + ]: + r"""Return a callable for the list voices method over gRPC. + + Returns a list of Voice supported for synthesis. + + Returns: + Callable[[~.ListVoicesRequest], + Awaitable[~.ListVoicesResponse]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "list_voices" not in self._stubs: + self._stubs["list_voices"] = self.grpc_channel.unary_unary( + "/google.cloud.texttospeech.v1.TextToSpeech/ListVoices", + request_serializer=cloud_tts.ListVoicesRequest.serialize, + response_deserializer=cloud_tts.ListVoicesResponse.deserialize, + ) + return self._stubs["list_voices"] + + @property + def synthesize_speech( + self + ) -> Callable[ + [cloud_tts.SynthesizeSpeechRequest], + Awaitable[cloud_tts.SynthesizeSpeechResponse], + ]: + r"""Return a callable for the synthesize speech method over gRPC. + + Synthesizes speech synchronously: receive results + after all text input has been processed. + + Returns: + Callable[[~.SynthesizeSpeechRequest], + Awaitable[~.SynthesizeSpeechResponse]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "synthesize_speech" not in self._stubs: + self._stubs["synthesize_speech"] = self.grpc_channel.unary_unary( + "/google.cloud.texttospeech.v1.TextToSpeech/SynthesizeSpeech", + request_serializer=cloud_tts.SynthesizeSpeechRequest.serialize, + response_deserializer=cloud_tts.SynthesizeSpeechResponse.deserialize, + ) + return self._stubs["synthesize_speech"] + + +__all__ = ("TextToSpeechGrpcAsyncIOTransport",) diff --git a/google/cloud/texttospeech_v1beta1/__init__.py b/google/cloud/texttospeech_v1beta1/__init__.py index b0f218b1..4d285a25 100644 --- a/google/cloud/texttospeech_v1beta1/__init__.py +++ b/google/cloud/texttospeech_v1beta1/__init__.py @@ -15,7 +15,6 @@ # limitations under the License. # - from .services.text_to_speech import TextToSpeechClient from .types.cloud_tts import AudioConfig from .types.cloud_tts import AudioEncoding diff --git a/google/cloud/texttospeech_v1beta1/services/text_to_speech/__init__.py b/google/cloud/texttospeech_v1beta1/services/text_to_speech/__init__.py index 9ec2d969..989f886a 100644 --- a/google/cloud/texttospeech_v1beta1/services/text_to_speech/__init__.py +++ b/google/cloud/texttospeech_v1beta1/services/text_to_speech/__init__.py @@ -16,5 +16,6 @@ # from .client import TextToSpeechClient +from .async_client import TextToSpeechAsyncClient -__all__ = ("TextToSpeechClient",) +__all__ = ("TextToSpeechClient", "TextToSpeechAsyncClient") diff --git a/google/cloud/texttospeech_v1beta1/services/text_to_speech/async_client.py b/google/cloud/texttospeech_v1beta1/services/text_to_speech/async_client.py new file mode 100644 index 00000000..f6fa7df9 --- /dev/null +++ b/google/cloud/texttospeech_v1beta1/services/text_to_speech/async_client.py @@ -0,0 +1,262 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +import functools +import re +from typing import Dict, Sequence, Tuple, Type, Union +import pkg_resources + +import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore +from google.oauth2 import service_account # type: ignore + +from google.cloud.texttospeech_v1beta1.types import cloud_tts + +from .transports.base import TextToSpeechTransport +from .transports.grpc_asyncio import TextToSpeechGrpcAsyncIOTransport +from .client import TextToSpeechClient + + +class TextToSpeechAsyncClient: + """Service that implements Google Cloud Text-to-Speech API.""" + + _client: TextToSpeechClient + + DEFAULT_ENDPOINT = TextToSpeechClient.DEFAULT_ENDPOINT + DEFAULT_MTLS_ENDPOINT = TextToSpeechClient.DEFAULT_MTLS_ENDPOINT + + from_service_account_file = TextToSpeechClient.from_service_account_file + from_service_account_json = from_service_account_file + + get_transport_class = functools.partial( + type(TextToSpeechClient).get_transport_class, type(TextToSpeechClient) + ) + + def __init__( + self, + *, + credentials: credentials.Credentials = None, + transport: Union[str, TextToSpeechTransport] = "grpc_asyncio", + client_options: ClientOptions = None, + ) -> None: + """Instantiate the text to speech client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + transport (Union[str, ~.TextToSpeechTransport]): The + transport to use. If set to None, a transport is chosen + automatically. + client_options (ClientOptions): Custom options for the client. It + won't take effect if a ``transport`` instance is provided. + (1) The ``api_endpoint`` property can be used to override the + default endpoint provided by the client. GOOGLE_API_USE_MTLS + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint, this is the default value for + the environment variable) and "auto" (auto switch to the default + mTLS endpoint if client SSL credentials is present). However, + the ``api_endpoint`` property takes precedence if provided. + (2) The ``client_cert_source`` property is used to provide client + SSL credentials for mutual TLS transport. If not provided, the + default SSL credentials will be used if present. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + """ + + self._client = TextToSpeechClient( + credentials=credentials, transport=transport, client_options=client_options + ) + + async def list_voices( + self, + request: cloud_tts.ListVoicesRequest = None, + *, + language_code: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> cloud_tts.ListVoicesResponse: + r"""Returns a list of Voice supported for synthesis. + + Args: + request (:class:`~.cloud_tts.ListVoicesRequest`): + The request object. The top-level message sent by the + client for the `ListVoices` method. + language_code (:class:`str`): + Optional. Recommended. + `BCP-47 `__ + language tag. If specified, the ListVoices call will + only return voices that can be used to synthesize this + language_code. E.g. when specifying "en-NZ", you will + get supported "en-\*" voices; when specifying "no", you + will get supported "no-\*" (Norwegian) and "nb-*" + (Norwegian Bokmal) voices; specifying "zh" will also get + supported "cmn-*" voices; specifying "zh-hk" will also + get supported "yue-\*" voices. + This corresponds to the ``language_code`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.cloud_tts.ListVoicesResponse: + The message returned to the client by the ``ListVoices`` + method. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([language_code]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = cloud_tts.ListVoicesRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if language_code is not None: + request.language_code = language_code + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.list_voices, + default_timeout=None, + client_info=_client_info, + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata) + + # Done; return the response. + return response + + async def synthesize_speech( + self, + request: cloud_tts.SynthesizeSpeechRequest = None, + *, + input: cloud_tts.SynthesisInput = None, + voice: cloud_tts.VoiceSelectionParams = None, + audio_config: cloud_tts.AudioConfig = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> cloud_tts.SynthesizeSpeechResponse: + r"""Synthesizes speech synchronously: receive results + after all text input has been processed. + + Args: + request (:class:`~.cloud_tts.SynthesizeSpeechRequest`): + The request object. The top-level message sent by the + client for the `SynthesizeSpeech` method. + input (:class:`~.cloud_tts.SynthesisInput`): + Required. The Synthesizer requires + either plain text or SSML as input. + This corresponds to the ``input`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + voice (:class:`~.cloud_tts.VoiceSelectionParams`): + Required. The desired voice of the + synthesized audio. + This corresponds to the ``voice`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + audio_config (:class:`~.cloud_tts.AudioConfig`): + Required. The configuration of the + synthesized audio. + This corresponds to the ``audio_config`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.cloud_tts.SynthesizeSpeechResponse: + The message returned to the client by the + ``SynthesizeSpeech`` method. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([input, voice, audio_config]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = cloud_tts.SynthesizeSpeechRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if input is not None: + request.input = input + if voice is not None: + request.voice = voice + if audio_config is not None: + request.audio_config = audio_config + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.synthesize_speech, + default_timeout=None, + client_info=_client_info, + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata) + + # Done; return the response. + return response + + +try: + _client_info = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution( + "google-cloud-texttospeech" + ).version + ) +except pkg_resources.DistributionNotFound: + _client_info = gapic_v1.client_info.ClientInfo() + + +__all__ = ("TextToSpeechAsyncClient",) diff --git a/google/cloud/texttospeech_v1beta1/services/text_to_speech/client.py b/google/cloud/texttospeech_v1beta1/services/text_to_speech/client.py index ee9784fb..f885936b 100644 --- a/google/cloud/texttospeech_v1beta1/services/text_to_speech/client.py +++ b/google/cloud/texttospeech_v1beta1/services/text_to_speech/client.py @@ -16,6 +16,7 @@ # from collections import OrderedDict +import os import re from typing import Callable, Dict, Sequence, Tuple, Type, Union import pkg_resources @@ -25,12 +26,15 @@ from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore from google.auth import credentials # type: ignore +from google.auth.transport import mtls # type: ignore +from google.auth.exceptions import MutualTLSChannelError # type: ignore from google.oauth2 import service_account # type: ignore from google.cloud.texttospeech_v1beta1.types import cloud_tts from .transports.base import TextToSpeechTransport from .transports.grpc import TextToSpeechGrpcTransport +from .transports.grpc_asyncio import TextToSpeechGrpcAsyncIOTransport class TextToSpeechClientMeta(type): @@ -43,6 +47,7 @@ class TextToSpeechClientMeta(type): _transport_registry = OrderedDict() # type: Dict[str, Type[TextToSpeechTransport]] _transport_registry["grpc"] = TextToSpeechGrpcTransport + _transport_registry["grpc_asyncio"] = TextToSpeechGrpcAsyncIOTransport def get_transport_class(cls, label: str = None) -> Type[TextToSpeechTransport]: """Return an appropriate transport class. @@ -138,21 +143,49 @@ def __init__( transport (Union[str, ~.TextToSpeechTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (ClientOptions): Custom options for the client. + client_options (ClientOptions): Custom options for the client. It + won't take effect if a ``transport`` instance is provided. (1) The ``api_endpoint`` property can be used to override the - default endpoint provided by the client. - (2) If ``transport`` argument is None, ``client_options`` can be - used to create a mutual TLS transport. If ``client_cert_source`` - is provided, mutual TLS transport will be created with the given - ``api_endpoint`` or the default mTLS endpoint, and the client - SSL credentials obtained from ``client_cert_source``. + default endpoint provided by the client. GOOGLE_API_USE_MTLS + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint, this is the default value for + the environment variable) and "auto" (auto switch to the default + mTLS endpoint if client SSL credentials is present). However, + the ``api_endpoint`` property takes precedence if provided. + (2) The ``client_cert_source`` property is used to provide client + SSL credentials for mutual TLS transport. If not provided, the + default SSL credentials will be used if present. Raises: - google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport creation failed for any reason. """ if isinstance(client_options, dict): client_options = ClientOptions.from_dict(client_options) + if client_options is None: + client_options = ClientOptions.ClientOptions() + + if client_options.api_endpoint is None: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "never") + if use_mtls_env == "never": + client_options.api_endpoint = self.DEFAULT_ENDPOINT + elif use_mtls_env == "always": + client_options.api_endpoint = self.DEFAULT_MTLS_ENDPOINT + elif use_mtls_env == "auto": + has_client_cert_source = ( + client_options.client_cert_source is not None + or mtls.has_default_client_cert_source() + ) + client_options.api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT + if has_client_cert_source + else self.DEFAULT_ENDPOINT + ) + else: + raise MutualTLSChannelError( + "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: never, auto, always" + ) # Save or instantiate the transport. # Ordinarily, we provide the transport, but allowing a custom transport @@ -165,38 +198,12 @@ def __init__( "provide its credentials directly." ) self._transport = transport - elif client_options is None or ( - client_options.api_endpoint is None - and client_options.client_cert_source is None - ): - # Don't trigger mTLS if we get an empty ClientOptions. + else: Transport = type(self).get_transport_class(transport) self._transport = Transport( - credentials=credentials, host=self.DEFAULT_ENDPOINT - ) - else: - # We have a non-empty ClientOptions. If client_cert_source is - # provided, trigger mTLS with user provided endpoint or the default - # mTLS endpoint. - if client_options.client_cert_source: - api_mtls_endpoint = ( - client_options.api_endpoint - if client_options.api_endpoint - else self.DEFAULT_MTLS_ENDPOINT - ) - else: - api_mtls_endpoint = None - - api_endpoint = ( - client_options.api_endpoint - if client_options.api_endpoint - else self.DEFAULT_ENDPOINT - ) - - self._transport = TextToSpeechGrpcTransport( credentials=credentials, - host=api_endpoint, - api_mtls_endpoint=api_mtls_endpoint, + host=client_options.api_endpoint, + api_mtls_endpoint=client_options.api_endpoint, client_cert_source=client_options.client_cert_source, ) diff --git a/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/__init__.py b/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/__init__.py index bd57d801..3a92efc9 100644 --- a/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/__init__.py +++ b/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/__init__.py @@ -20,11 +20,17 @@ from .base import TextToSpeechTransport from .grpc import TextToSpeechGrpcTransport +from .grpc_asyncio import TextToSpeechGrpcAsyncIOTransport # Compile a registry of transports. _transport_registry = OrderedDict() # type: Dict[str, Type[TextToSpeechTransport]] _transport_registry["grpc"] = TextToSpeechGrpcTransport +_transport_registry["grpc_asyncio"] = TextToSpeechGrpcAsyncIOTransport -__all__ = ("TextToSpeechTransport", "TextToSpeechGrpcTransport") +__all__ = ( + "TextToSpeechTransport", + "TextToSpeechGrpcTransport", + "TextToSpeechGrpcAsyncIOTransport", +) diff --git a/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/base.py b/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/base.py index b1ebfa21..4e8c12da 100644 --- a/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/base.py +++ b/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/base.py @@ -24,7 +24,7 @@ from google.cloud.texttospeech_v1beta1.types import cloud_tts -class TextToSpeechTransport(metaclass=abc.ABCMeta): +class TextToSpeechTransport(abc.ABC): """Abstract transport class for TextToSpeech.""" AUTH_SCOPES = ("https://www.googleapis.com/auth/cloud-platform",) @@ -34,6 +34,7 @@ def __init__( *, host: str = "texttospeech.googleapis.com", credentials: credentials.Credentials = None, + **kwargs, ) -> None: """Instantiate the transport. @@ -61,16 +62,25 @@ def __init__( @property def list_voices( self - ) -> typing.Callable[[cloud_tts.ListVoicesRequest], cloud_tts.ListVoicesResponse]: - raise NotImplementedError + ) -> typing.Callable[ + [cloud_tts.ListVoicesRequest], + typing.Union[ + cloud_tts.ListVoicesResponse, typing.Awaitable[cloud_tts.ListVoicesResponse] + ], + ]: + raise NotImplementedError() @property def synthesize_speech( self ) -> typing.Callable[ - [cloud_tts.SynthesizeSpeechRequest], cloud_tts.SynthesizeSpeechResponse + [cloud_tts.SynthesizeSpeechRequest], + typing.Union[ + cloud_tts.SynthesizeSpeechResponse, + typing.Awaitable[cloud_tts.SynthesizeSpeechResponse], + ], ]: - raise NotImplementedError + raise NotImplementedError() __all__ = ("TextToSpeechTransport",) diff --git a/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/grpc.py b/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/grpc.py index 9d110b51..a516fbb8 100644 --- a/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/grpc.py +++ b/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/grpc.py @@ -15,9 +15,10 @@ # limitations under the License. # -from typing import Callable, Dict, Tuple +from typing import Callable, Dict, Optional, Sequence, Tuple from google.api_core import grpc_helpers # type: ignore +from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -42,6 +43,8 @@ class TextToSpeechGrpcTransport(TextToSpeechTransport): top of HTTP/2); the ``grpcio`` package must be installed. """ + _stubs: Dict[str, Callable] + def __init__( self, *, @@ -73,8 +76,8 @@ def __init__( is None. Raises: - google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport - creation failed for any reason. + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. """ if channel: # Sanity check: Ensure that channel and credentials are not both @@ -90,6 +93,9 @@ def __init__( else api_mtls_endpoint + ":443" ) + if credentials is None: + credentials, _ = auth.default(scopes=self.AUTH_SCOPES) + # Create SSL credentials with client_cert_source or application # default SSL credentials. if client_cert_source: @@ -101,7 +107,7 @@ def __init__( ssl_credentials = SslCredentials().ssl_credentials # create a new channel. The provided one is ignored. - self._grpc_channel = grpc_helpers.create_channel( + self._grpc_channel = type(self).create_channel( host, credentials=credentials, ssl_credentials=ssl_credentials, @@ -117,6 +123,7 @@ def create_channel( cls, host: str = "texttospeech.googleapis.com", credentials: credentials.Credentials = None, + scopes: Optional[Sequence[str]] = None, **kwargs ) -> grpc.Channel: """Create and return a gRPC channel object. @@ -127,13 +134,17 @@ def create_channel( credentials identify this application to the service. If none are specified, the client will attempt to ascertain the credentials from the environment. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. kwargs (Optional[dict]): Keyword arguments, which are passed to the channel creation. Returns: grpc.Channel: A gRPC channel object. """ + scopes = scopes or cls.AUTH_SCOPES return grpc_helpers.create_channel( - host, credentials=credentials, scopes=cls.AUTH_SCOPES, **kwargs + host, credentials=credentials, scopes=scopes, **kwargs ) @property diff --git a/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/grpc_asyncio.py b/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/grpc_asyncio.py new file mode 100644 index 00000000..e937b24d --- /dev/null +++ b/google/cloud/texttospeech_v1beta1/services/text_to_speech/transports/grpc_asyncio.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple + +from google.api_core import grpc_helpers_async # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore + +import grpc # type: ignore +from grpc.experimental import aio # type: ignore + +from google.cloud.texttospeech_v1beta1.types import cloud_tts + +from .base import TextToSpeechTransport +from .grpc import TextToSpeechGrpcTransport + + +class TextToSpeechGrpcAsyncIOTransport(TextToSpeechTransport): + """gRPC AsyncIO backend transport for TextToSpeech. + + Service that implements Google Cloud Text-to-Speech API. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _grpc_channel: aio.Channel + _stubs: Dict[str, Callable] = {} + + @classmethod + def create_channel( + cls, + host: str = "texttospeech.googleapis.com", + credentials: credentials.Credentials = None, + scopes: Optional[Sequence[str]] = None, + **kwargs + ) -> aio.Channel: + """Create and return a gRPC AsyncIO channel object. + Args: + address (Optional[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + aio.Channel: A gRPC AsyncIO channel object. + """ + scopes = scopes or cls.AUTH_SCOPES + return grpc_helpers_async.create_channel( + host, credentials=credentials, scopes=scopes, **kwargs + ) + + def __init__( + self, + *, + host: str = "texttospeech.googleapis.com", + credentials: credentials.Credentials = None, + channel: aio.Channel = None, + api_mtls_endpoint: str = None, + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + This argument is ignored if ``channel`` is provided. + channel (Optional[aio.Channel]): A ``Channel`` instance through + which to make calls. + api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If + provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or applicatin default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A + callback to provide client SSL certificate bytes and private key + bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` + is None. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + """ + if channel: + # Sanity check: Ensure that channel and credentials are not both + # provided. + credentials = False + + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + elif api_mtls_endpoint: + host = ( + api_mtls_endpoint + if ":" in api_mtls_endpoint + else api_mtls_endpoint + ":443" + ) + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + ssl_credentials = SslCredentials().ssl_credentials + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + ssl_credentials=ssl_credentials, + scopes=self.AUTH_SCOPES, + ) + + # Run the base constructor. + super().__init__(host=host, credentials=credentials) + self._stubs = {} + + @property + def grpc_channel(self) -> aio.Channel: + """Create the channel designed to connect to this service. + + This property caches on the instance; repeated calls return + the same channel. + """ + # Sanity check: Only create a new channel if we do not already + # have one. + if not hasattr(self, "_grpc_channel"): + self._grpc_channel = self.create_channel( + self._host, credentials=self._credentials + ) + + # Return the channel from cache. + return self._grpc_channel + + @property + def list_voices( + self + ) -> Callable[ + [cloud_tts.ListVoicesRequest], Awaitable[cloud_tts.ListVoicesResponse] + ]: + r"""Return a callable for the list voices method over gRPC. + + Returns a list of Voice supported for synthesis. + + Returns: + Callable[[~.ListVoicesRequest], + Awaitable[~.ListVoicesResponse]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "list_voices" not in self._stubs: + self._stubs["list_voices"] = self.grpc_channel.unary_unary( + "/google.cloud.texttospeech.v1beta1.TextToSpeech/ListVoices", + request_serializer=cloud_tts.ListVoicesRequest.serialize, + response_deserializer=cloud_tts.ListVoicesResponse.deserialize, + ) + return self._stubs["list_voices"] + + @property + def synthesize_speech( + self + ) -> Callable[ + [cloud_tts.SynthesizeSpeechRequest], + Awaitable[cloud_tts.SynthesizeSpeechResponse], + ]: + r"""Return a callable for the synthesize speech method over gRPC. + + Synthesizes speech synchronously: receive results + after all text input has been processed. + + Returns: + Callable[[~.SynthesizeSpeechRequest], + Awaitable[~.SynthesizeSpeechResponse]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "synthesize_speech" not in self._stubs: + self._stubs["synthesize_speech"] = self.grpc_channel.unary_unary( + "/google.cloud.texttospeech.v1beta1.TextToSpeech/SynthesizeSpeech", + request_serializer=cloud_tts.SynthesizeSpeechRequest.serialize, + response_deserializer=cloud_tts.SynthesizeSpeechResponse.deserialize, + ) + return self._stubs["synthesize_speech"] + + +__all__ = ("TextToSpeechGrpcAsyncIOTransport",) diff --git a/noxfile.py b/noxfile.py index 3dd79725..6a4c844b 100644 --- a/noxfile.py +++ b/noxfile.py @@ -66,6 +66,7 @@ def lint_setup_py(session): def default(session): # Install all test dependencies, then install this package in-place. + session.install("asyncmock", "pytest-asyncio") session.install("mock", "pytest", "pytest-cov") session.install("-e", ".") diff --git a/setup.py b/setup.py index 6310e36a..50a944ec 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,7 @@ # 'Development Status :: 5 - Production/Stable' release_status = "Development Status :: 5 - Production/Stable" dependencies = [ - "google-api-core[grpc] >= 1.17.0, < 2.0.0dev", + "google-api-core[grpc] >= 1.17.2, < 2.0.0dev", "proto-plus >= 0.4.0", "libcst >= 0.2.5", ] diff --git a/synth.metadata b/synth.metadata index f4f9a8c1..793637ac 100644 --- a/synth.metadata +++ b/synth.metadata @@ -4,7 +4,7 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/python-texttospeech.git", - "sha": "6ce76b62f41c354ee47b6cb13c52574f91e7a80c" + "sha": "08e6de92448d546f8aae466f77951d30704fba2d" } }, { @@ -19,14 +19,14 @@ "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "cb3c683e958a4b5c016bb3734436fc1cb887eb7b" + "sha": "cd522c3b4dde821766d95c80ae5aeb43d7a41170" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "cb3c683e958a4b5c016bb3734436fc1cb887eb7b" + "sha": "cd522c3b4dde821766d95c80ae5aeb43d7a41170" } } ], diff --git a/synth.py b/synth.py index 7b11e286..a6f6ba14 100644 --- a/synth.py +++ b/synth.py @@ -32,7 +32,7 @@ # Sphinx interprets `*` as emphasis s.replace( - ["google/cloud/**/client.py", "google/cloud/**/cloud_tts.py"], + ["google/cloud/**/*_client.py", "google/cloud/**/cloud_tts.py"], "((en)|(no)|(nb)(cmn)|(yue))-\*", "\g<1>-\*", ) @@ -41,8 +41,8 @@ # Add templated files # ---------------------------------------------------------------------------- templated_files = common.py_library( - cov_level=100, samples=True, + microgenerator=True, unit_test_python_versions=["3.6", "3.7", "3.8"], system_test_python_versions=["3.7"], ) @@ -53,8 +53,4 @@ # ---------------------------------------------------------------------------- python.py_samples(skip_readmes=True) -# Extra lint ignores for microgenerator tests -# TODO: Remove when https://github.com/googleapis/gapic-generator-python/issues/425 is closed -s.replace(".flake8", "(ignore = .*)", "\g<1>, F401, F841") - s.shell.run(["nox", "-s", "blacken"], hide_output=False) diff --git a/tests/unit/texttospeech_v1/__init__.py b/tests/unit/gapic/texttospeech_v1/__init__.py similarity index 100% rename from tests/unit/texttospeech_v1/__init__.py rename to tests/unit/gapic/texttospeech_v1/__init__.py diff --git a/tests/unit/gapic/texttospeech_v1/test_text_to_speech.py b/tests/unit/gapic/texttospeech_v1/test_text_to_speech.py new file mode 100644 index 00000000..e05ee7b5 --- /dev/null +++ b/tests/unit/gapic/texttospeech_v1/test_text_to_speech.py @@ -0,0 +1,782 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +import mock + +import grpc +from grpc.experimental import aio +import math +import pytest + +from google import auth +from google.api_core import client_options +from google.api_core import grpc_helpers +from google.api_core import grpc_helpers_async +from google.auth import credentials +from google.auth.exceptions import MutualTLSChannelError +from google.cloud.texttospeech_v1.services.text_to_speech import TextToSpeechAsyncClient +from google.cloud.texttospeech_v1.services.text_to_speech import TextToSpeechClient +from google.cloud.texttospeech_v1.services.text_to_speech import transports +from google.cloud.texttospeech_v1.types import cloud_tts +from google.oauth2 import service_account + + +def client_cert_source_callback(): + return b"cert bytes", b"key bytes" + + +def test__get_default_mtls_endpoint(): + api_endpoint = "example.googleapis.com" + api_mtls_endpoint = "example.mtls.googleapis.com" + sandbox_endpoint = "example.sandbox.googleapis.com" + sandbox_mtls_endpoint = "example.mtls.sandbox.googleapis.com" + non_googleapi = "api.example.com" + + assert TextToSpeechClient._get_default_mtls_endpoint(None) is None + assert ( + TextToSpeechClient._get_default_mtls_endpoint(api_endpoint) == api_mtls_endpoint + ) + assert ( + TextToSpeechClient._get_default_mtls_endpoint(api_mtls_endpoint) + == api_mtls_endpoint + ) + assert ( + TextToSpeechClient._get_default_mtls_endpoint(sandbox_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + TextToSpeechClient._get_default_mtls_endpoint(sandbox_mtls_endpoint) + == sandbox_mtls_endpoint + ) + assert TextToSpeechClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi + + +@pytest.mark.parametrize("client_class", [TextToSpeechClient, TextToSpeechAsyncClient]) +def test_text_to_speech_client_from_service_account_file(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_file" + ) as factory: + factory.return_value = creds + client = client_class.from_service_account_file("dummy/file/path.json") + assert client._transport._credentials == creds + + client = client_class.from_service_account_json("dummy/file/path.json") + assert client._transport._credentials == creds + + assert client._transport._host == "texttospeech.googleapis.com:443" + + +def test_text_to_speech_client_get_transport_class(): + transport = TextToSpeechClient.get_transport_class() + assert transport == transports.TextToSpeechGrpcTransport + + transport = TextToSpeechClient.get_transport_class("grpc") + assert transport == transports.TextToSpeechGrpcTransport + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (TextToSpeechClient, transports.TextToSpeechGrpcTransport, "grpc"), + ( + TextToSpeechAsyncClient, + transports.TextToSpeechGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +def test_text_to_speech_client_client_options( + client_class, transport_class, transport_name +): + # Check that if channel is provided we won't create a new one. + with mock.patch.object(TextToSpeechClient, "get_transport_class") as gtc: + transport = transport_class(credentials=credentials.AnonymousCredentials()) + client = client_class(transport=transport) + gtc.assert_not_called() + + # Check that if channel is provided via str we will create a new one. + with mock.patch.object(TextToSpeechClient, "get_transport_class") as gtc: + client = client_class(transport=transport_name) + gtc.assert_called() + + # Check the case api_endpoint is provided. + options = client_options.ClientOptions(api_endpoint="squid.clam.whelk") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + api_mtls_endpoint="squid.clam.whelk", + client_cert_source=None, + credentials=None, + host="squid.clam.whelk", + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # "never". + os.environ["GOOGLE_API_USE_MTLS"] = "never" + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + credentials=None, + host=client.DEFAULT_ENDPOINT, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # "always". + os.environ["GOOGLE_API_USE_MTLS"] = "always" + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=None, + credentials=None, + host=client.DEFAULT_MTLS_ENDPOINT, + ) + + # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is + # "auto", and client_cert_source is provided. + os.environ["GOOGLE_API_USE_MTLS"] = "auto" + options = client_options.ClientOptions( + client_cert_source=client_cert_source_callback + ) + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=client_cert_source_callback, + credentials=None, + host=client.DEFAULT_MTLS_ENDPOINT, + ) + + # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is + # "auto", and default_client_cert_source is provided. + os.environ["GOOGLE_API_USE_MTLS"] = "auto" + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=None, + credentials=None, + host=client.DEFAULT_MTLS_ENDPOINT, + ) + + # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is + # "auto", but client_cert_source and default_client_cert_source are None. + os.environ["GOOGLE_API_USE_MTLS"] = "auto" + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + credentials=None, + host=client.DEFAULT_ENDPOINT, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has + # unsupported value. + os.environ["GOOGLE_API_USE_MTLS"] = "Unsupported" + with pytest.raises(MutualTLSChannelError): + client = client_class() + + del os.environ["GOOGLE_API_USE_MTLS"] + + +def test_text_to_speech_client_client_options_from_dict(): + with mock.patch( + "google.cloud.texttospeech_v1.services.text_to_speech.transports.TextToSpeechGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = TextToSpeechClient(client_options={"api_endpoint": "squid.clam.whelk"}) + grpc_transport.assert_called_once_with( + api_mtls_endpoint="squid.clam.whelk", + client_cert_source=None, + credentials=None, + host="squid.clam.whelk", + ) + + +def test_list_voices(transport: str = "grpc"): + client = TextToSpeechClient( + credentials=credentials.AnonymousCredentials(), transport=transport + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = cloud_tts.ListVoicesRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_voices), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = cloud_tts.ListVoicesResponse() + + response = client.list_voices(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, cloud_tts.ListVoicesResponse) + + +@pytest.mark.asyncio +async def test_list_voices_async(transport: str = "grpc_asyncio"): + client = TextToSpeechAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = cloud_tts.ListVoicesRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_voices), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_tts.ListVoicesResponse() + ) + + response = await client.list_voices(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, cloud_tts.ListVoicesResponse) + + +def test_list_voices_flattened(): + client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_voices), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = cloud_tts.ListVoicesResponse() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.list_voices(language_code="language_code_value") + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0].language_code == "language_code_value" + + +def test_list_voices_flattened_error(): + client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_voices( + cloud_tts.ListVoicesRequest(), language_code="language_code_value" + ) + + +@pytest.mark.asyncio +async def test_list_voices_flattened_async(): + client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials()) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_voices), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = cloud_tts.ListVoicesResponse() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_tts.ListVoicesResponse() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.list_voices(language_code="language_code_value") + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0].language_code == "language_code_value" + + +@pytest.mark.asyncio +async def test_list_voices_flattened_error_async(): + client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials()) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.list_voices( + cloud_tts.ListVoicesRequest(), language_code="language_code_value" + ) + + +def test_synthesize_speech(transport: str = "grpc"): + client = TextToSpeechClient( + credentials=credentials.AnonymousCredentials(), transport=transport + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = cloud_tts.SynthesizeSpeechRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.synthesize_speech), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = cloud_tts.SynthesizeSpeechResponse( + audio_content=b"audio_content_blob" + ) + + response = client.synthesize_speech(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, cloud_tts.SynthesizeSpeechResponse) + assert response.audio_content == b"audio_content_blob" + + +@pytest.mark.asyncio +async def test_synthesize_speech_async(transport: str = "grpc_asyncio"): + client = TextToSpeechAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = cloud_tts.SynthesizeSpeechRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.synthesize_speech), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_tts.SynthesizeSpeechResponse(audio_content=b"audio_content_blob") + ) + + response = await client.synthesize_speech(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, cloud_tts.SynthesizeSpeechResponse) + assert response.audio_content == b"audio_content_blob" + + +def test_synthesize_speech_flattened(): + client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.synthesize_speech), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = cloud_tts.SynthesizeSpeechResponse() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.synthesize_speech( + input=cloud_tts.SynthesisInput(text="text_value"), + voice=cloud_tts.VoiceSelectionParams(language_code="language_code_value"), + audio_config=cloud_tts.AudioConfig( + audio_encoding=cloud_tts.AudioEncoding.LINEAR16 + ), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0].input == cloud_tts.SynthesisInput(text="text_value") + assert args[0].voice == cloud_tts.VoiceSelectionParams( + language_code="language_code_value" + ) + assert args[0].audio_config == cloud_tts.AudioConfig( + audio_encoding=cloud_tts.AudioEncoding.LINEAR16 + ) + + +def test_synthesize_speech_flattened_error(): + client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.synthesize_speech( + cloud_tts.SynthesizeSpeechRequest(), + input=cloud_tts.SynthesisInput(text="text_value"), + voice=cloud_tts.VoiceSelectionParams(language_code="language_code_value"), + audio_config=cloud_tts.AudioConfig( + audio_encoding=cloud_tts.AudioEncoding.LINEAR16 + ), + ) + + +@pytest.mark.asyncio +async def test_synthesize_speech_flattened_async(): + client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials()) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.synthesize_speech), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = cloud_tts.SynthesizeSpeechResponse() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_tts.SynthesizeSpeechResponse() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.synthesize_speech( + input=cloud_tts.SynthesisInput(text="text_value"), + voice=cloud_tts.VoiceSelectionParams(language_code="language_code_value"), + audio_config=cloud_tts.AudioConfig( + audio_encoding=cloud_tts.AudioEncoding.LINEAR16 + ), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0].input == cloud_tts.SynthesisInput(text="text_value") + assert args[0].voice == cloud_tts.VoiceSelectionParams( + language_code="language_code_value" + ) + assert args[0].audio_config == cloud_tts.AudioConfig( + audio_encoding=cloud_tts.AudioEncoding.LINEAR16 + ) + + +@pytest.mark.asyncio +async def test_synthesize_speech_flattened_error_async(): + client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials()) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.synthesize_speech( + cloud_tts.SynthesizeSpeechRequest(), + input=cloud_tts.SynthesisInput(text="text_value"), + voice=cloud_tts.VoiceSelectionParams(language_code="language_code_value"), + audio_config=cloud_tts.AudioConfig( + audio_encoding=cloud_tts.AudioEncoding.LINEAR16 + ), + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.TextToSpeechGrpcTransport( + credentials=credentials.AnonymousCredentials() + ) + with pytest.raises(ValueError): + client = TextToSpeechClient( + credentials=credentials.AnonymousCredentials(), transport=transport + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.TextToSpeechGrpcTransport( + credentials=credentials.AnonymousCredentials() + ) + client = TextToSpeechClient(transport=transport) + assert client._transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.TextToSpeechGrpcTransport( + credentials=credentials.AnonymousCredentials() + ) + channel = transport.grpc_channel + assert channel + + transport = transports.TextToSpeechGrpcAsyncIOTransport( + credentials=credentials.AnonymousCredentials() + ) + channel = transport.grpc_channel + assert channel + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) + assert isinstance(client._transport, transports.TextToSpeechGrpcTransport) + + +def test_text_to_speech_base_transport(): + # Instantiate the base transport. + transport = transports.TextToSpeechTransport( + credentials=credentials.AnonymousCredentials() + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ("list_voices", "synthesize_speech") + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + +def test_text_to_speech_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + TextToSpeechClient() + adc.assert_called_once_with( + scopes=("https://www.googleapis.com/auth/cloud-platform",) + ) + + +def test_text_to_speech_transport_auth_adc(): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transports.TextToSpeechGrpcTransport(host="squid.clam.whelk") + adc.assert_called_once_with( + scopes=("https://www.googleapis.com/auth/cloud-platform",) + ) + + +def test_text_to_speech_host_no_port(): + client = TextToSpeechClient( + credentials=credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="texttospeech.googleapis.com" + ), + ) + assert client._transport._host == "texttospeech.googleapis.com:443" + + +def test_text_to_speech_host_with_port(): + client = TextToSpeechClient( + credentials=credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="texttospeech.googleapis.com:8000" + ), + ) + assert client._transport._host == "texttospeech.googleapis.com:8000" + + +def test_text_to_speech_grpc_transport_channel(): + channel = grpc.insecure_channel("http://localhost/") + + # Check that if channel is provided, mtls endpoint and client_cert_source + # won't be used. + callback = mock.MagicMock() + transport = transports.TextToSpeechGrpcTransport( + host="squid.clam.whelk", + channel=channel, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=callback, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + assert not callback.called + + +def test_text_to_speech_grpc_asyncio_transport_channel(): + channel = aio.insecure_channel("http://localhost/") + + # Check that if channel is provided, mtls endpoint and client_cert_source + # won't be used. + callback = mock.MagicMock() + transport = transports.TextToSpeechGrpcAsyncIOTransport( + host="squid.clam.whelk", + channel=channel, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=callback, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + assert not callback.called + + +@mock.patch("grpc.ssl_channel_credentials", autospec=True) +@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) +def test_text_to_speech_grpc_transport_channel_mtls_with_client_cert_source( + grpc_create_channel, grpc_ssl_channel_cred +): + # Check that if channel is None, but api_mtls_endpoint and client_cert_source + # are provided, then a mTLS channel will be created. + mock_cred = mock.Mock() + + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + transport = transports.TextToSpeechGrpcTransport( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + ssl_credentials=mock_ssl_cred, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ) + assert transport.grpc_channel == mock_grpc_channel + + +@mock.patch("grpc.ssl_channel_credentials", autospec=True) +@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) +def test_text_to_speech_grpc_asyncio_transport_channel_mtls_with_client_cert_source( + grpc_create_channel, grpc_ssl_channel_cred +): + # Check that if channel is None, but api_mtls_endpoint and client_cert_source + # are provided, then a mTLS channel will be created. + mock_cred = mock.Mock() + + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + transport = transports.TextToSpeechGrpcAsyncIOTransport( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + ssl_credentials=mock_ssl_cred, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ) + assert transport.grpc_channel == mock_grpc_channel + + +@pytest.mark.parametrize( + "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] +) +@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) +def test_text_to_speech_grpc_transport_channel_mtls_with_adc( + grpc_create_channel, api_mtls_endpoint +): + # Check that if channel and client_cert_source are None, but api_mtls_endpoint + # is provided, then a mTLS channel will be created with SSL ADC. + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + # Mock google.auth.transport.grpc.SslCredentials class. + mock_ssl_cred = mock.Mock() + with mock.patch.multiple( + "google.auth.transport.grpc.SslCredentials", + __init__=mock.Mock(return_value=None), + ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), + ): + mock_cred = mock.Mock() + transport = transports.TextToSpeechGrpcTransport( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint=api_mtls_endpoint, + client_cert_source=None, + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + ssl_credentials=mock_ssl_cred, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ) + assert transport.grpc_channel == mock_grpc_channel + + +@pytest.mark.parametrize( + "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] +) +@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) +def test_text_to_speech_grpc_asyncio_transport_channel_mtls_with_adc( + grpc_create_channel, api_mtls_endpoint +): + # Check that if channel and client_cert_source are None, but api_mtls_endpoint + # is provided, then a mTLS channel will be created with SSL ADC. + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + # Mock google.auth.transport.grpc.SslCredentials class. + mock_ssl_cred = mock.Mock() + with mock.patch.multiple( + "google.auth.transport.grpc.SslCredentials", + __init__=mock.Mock(return_value=None), + ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), + ): + mock_cred = mock.Mock() + transport = transports.TextToSpeechGrpcAsyncIOTransport( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint=api_mtls_endpoint, + client_cert_source=None, + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + ssl_credentials=mock_ssl_cred, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ) + assert transport.grpc_channel == mock_grpc_channel diff --git a/tests/unit/texttospeech_v1beta1/__init__.py b/tests/unit/gapic/texttospeech_v1beta1/__init__.py similarity index 100% rename from tests/unit/texttospeech_v1beta1/__init__.py rename to tests/unit/gapic/texttospeech_v1beta1/__init__.py diff --git a/tests/unit/gapic/texttospeech_v1beta1/test_text_to_speech.py b/tests/unit/gapic/texttospeech_v1beta1/test_text_to_speech.py new file mode 100644 index 00000000..6e81f325 --- /dev/null +++ b/tests/unit/gapic/texttospeech_v1beta1/test_text_to_speech.py @@ -0,0 +1,784 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +import mock + +import grpc +from grpc.experimental import aio +import math +import pytest + +from google import auth +from google.api_core import client_options +from google.api_core import grpc_helpers +from google.api_core import grpc_helpers_async +from google.auth import credentials +from google.auth.exceptions import MutualTLSChannelError +from google.cloud.texttospeech_v1beta1.services.text_to_speech import ( + TextToSpeechAsyncClient, +) +from google.cloud.texttospeech_v1beta1.services.text_to_speech import TextToSpeechClient +from google.cloud.texttospeech_v1beta1.services.text_to_speech import transports +from google.cloud.texttospeech_v1beta1.types import cloud_tts +from google.oauth2 import service_account + + +def client_cert_source_callback(): + return b"cert bytes", b"key bytes" + + +def test__get_default_mtls_endpoint(): + api_endpoint = "example.googleapis.com" + api_mtls_endpoint = "example.mtls.googleapis.com" + sandbox_endpoint = "example.sandbox.googleapis.com" + sandbox_mtls_endpoint = "example.mtls.sandbox.googleapis.com" + non_googleapi = "api.example.com" + + assert TextToSpeechClient._get_default_mtls_endpoint(None) is None + assert ( + TextToSpeechClient._get_default_mtls_endpoint(api_endpoint) == api_mtls_endpoint + ) + assert ( + TextToSpeechClient._get_default_mtls_endpoint(api_mtls_endpoint) + == api_mtls_endpoint + ) + assert ( + TextToSpeechClient._get_default_mtls_endpoint(sandbox_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + TextToSpeechClient._get_default_mtls_endpoint(sandbox_mtls_endpoint) + == sandbox_mtls_endpoint + ) + assert TextToSpeechClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi + + +@pytest.mark.parametrize("client_class", [TextToSpeechClient, TextToSpeechAsyncClient]) +def test_text_to_speech_client_from_service_account_file(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_file" + ) as factory: + factory.return_value = creds + client = client_class.from_service_account_file("dummy/file/path.json") + assert client._transport._credentials == creds + + client = client_class.from_service_account_json("dummy/file/path.json") + assert client._transport._credentials == creds + + assert client._transport._host == "texttospeech.googleapis.com:443" + + +def test_text_to_speech_client_get_transport_class(): + transport = TextToSpeechClient.get_transport_class() + assert transport == transports.TextToSpeechGrpcTransport + + transport = TextToSpeechClient.get_transport_class("grpc") + assert transport == transports.TextToSpeechGrpcTransport + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (TextToSpeechClient, transports.TextToSpeechGrpcTransport, "grpc"), + ( + TextToSpeechAsyncClient, + transports.TextToSpeechGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +def test_text_to_speech_client_client_options( + client_class, transport_class, transport_name +): + # Check that if channel is provided we won't create a new one. + with mock.patch.object(TextToSpeechClient, "get_transport_class") as gtc: + transport = transport_class(credentials=credentials.AnonymousCredentials()) + client = client_class(transport=transport) + gtc.assert_not_called() + + # Check that if channel is provided via str we will create a new one. + with mock.patch.object(TextToSpeechClient, "get_transport_class") as gtc: + client = client_class(transport=transport_name) + gtc.assert_called() + + # Check the case api_endpoint is provided. + options = client_options.ClientOptions(api_endpoint="squid.clam.whelk") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + api_mtls_endpoint="squid.clam.whelk", + client_cert_source=None, + credentials=None, + host="squid.clam.whelk", + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # "never". + os.environ["GOOGLE_API_USE_MTLS"] = "never" + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + credentials=None, + host=client.DEFAULT_ENDPOINT, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # "always". + os.environ["GOOGLE_API_USE_MTLS"] = "always" + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=None, + credentials=None, + host=client.DEFAULT_MTLS_ENDPOINT, + ) + + # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is + # "auto", and client_cert_source is provided. + os.environ["GOOGLE_API_USE_MTLS"] = "auto" + options = client_options.ClientOptions( + client_cert_source=client_cert_source_callback + ) + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=client_cert_source_callback, + credentials=None, + host=client.DEFAULT_MTLS_ENDPOINT, + ) + + # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is + # "auto", and default_client_cert_source is provided. + os.environ["GOOGLE_API_USE_MTLS"] = "auto" + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=None, + credentials=None, + host=client.DEFAULT_MTLS_ENDPOINT, + ) + + # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is + # "auto", but client_cert_source and default_client_cert_source are None. + os.environ["GOOGLE_API_USE_MTLS"] = "auto" + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=False, + ): + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + credentials=None, + host=client.DEFAULT_ENDPOINT, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has + # unsupported value. + os.environ["GOOGLE_API_USE_MTLS"] = "Unsupported" + with pytest.raises(MutualTLSChannelError): + client = client_class() + + del os.environ["GOOGLE_API_USE_MTLS"] + + +def test_text_to_speech_client_client_options_from_dict(): + with mock.patch( + "google.cloud.texttospeech_v1beta1.services.text_to_speech.transports.TextToSpeechGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = TextToSpeechClient(client_options={"api_endpoint": "squid.clam.whelk"}) + grpc_transport.assert_called_once_with( + api_mtls_endpoint="squid.clam.whelk", + client_cert_source=None, + credentials=None, + host="squid.clam.whelk", + ) + + +def test_list_voices(transport: str = "grpc"): + client = TextToSpeechClient( + credentials=credentials.AnonymousCredentials(), transport=transport + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = cloud_tts.ListVoicesRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_voices), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = cloud_tts.ListVoicesResponse() + + response = client.list_voices(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, cloud_tts.ListVoicesResponse) + + +@pytest.mark.asyncio +async def test_list_voices_async(transport: str = "grpc_asyncio"): + client = TextToSpeechAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = cloud_tts.ListVoicesRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_voices), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_tts.ListVoicesResponse() + ) + + response = await client.list_voices(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, cloud_tts.ListVoicesResponse) + + +def test_list_voices_flattened(): + client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_voices), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = cloud_tts.ListVoicesResponse() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.list_voices(language_code="language_code_value") + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0].language_code == "language_code_value" + + +def test_list_voices_flattened_error(): + client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_voices( + cloud_tts.ListVoicesRequest(), language_code="language_code_value" + ) + + +@pytest.mark.asyncio +async def test_list_voices_flattened_async(): + client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials()) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_voices), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = cloud_tts.ListVoicesResponse() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_tts.ListVoicesResponse() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.list_voices(language_code="language_code_value") + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0].language_code == "language_code_value" + + +@pytest.mark.asyncio +async def test_list_voices_flattened_error_async(): + client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials()) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.list_voices( + cloud_tts.ListVoicesRequest(), language_code="language_code_value" + ) + + +def test_synthesize_speech(transport: str = "grpc"): + client = TextToSpeechClient( + credentials=credentials.AnonymousCredentials(), transport=transport + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = cloud_tts.SynthesizeSpeechRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.synthesize_speech), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = cloud_tts.SynthesizeSpeechResponse( + audio_content=b"audio_content_blob" + ) + + response = client.synthesize_speech(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, cloud_tts.SynthesizeSpeechResponse) + assert response.audio_content == b"audio_content_blob" + + +@pytest.mark.asyncio +async def test_synthesize_speech_async(transport: str = "grpc_asyncio"): + client = TextToSpeechAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = cloud_tts.SynthesizeSpeechRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.synthesize_speech), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_tts.SynthesizeSpeechResponse(audio_content=b"audio_content_blob") + ) + + response = await client.synthesize_speech(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, cloud_tts.SynthesizeSpeechResponse) + assert response.audio_content == b"audio_content_blob" + + +def test_synthesize_speech_flattened(): + client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.synthesize_speech), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = cloud_tts.SynthesizeSpeechResponse() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.synthesize_speech( + input=cloud_tts.SynthesisInput(text="text_value"), + voice=cloud_tts.VoiceSelectionParams(language_code="language_code_value"), + audio_config=cloud_tts.AudioConfig( + audio_encoding=cloud_tts.AudioEncoding.LINEAR16 + ), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0].input == cloud_tts.SynthesisInput(text="text_value") + assert args[0].voice == cloud_tts.VoiceSelectionParams( + language_code="language_code_value" + ) + assert args[0].audio_config == cloud_tts.AudioConfig( + audio_encoding=cloud_tts.AudioEncoding.LINEAR16 + ) + + +def test_synthesize_speech_flattened_error(): + client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.synthesize_speech( + cloud_tts.SynthesizeSpeechRequest(), + input=cloud_tts.SynthesisInput(text="text_value"), + voice=cloud_tts.VoiceSelectionParams(language_code="language_code_value"), + audio_config=cloud_tts.AudioConfig( + audio_encoding=cloud_tts.AudioEncoding.LINEAR16 + ), + ) + + +@pytest.mark.asyncio +async def test_synthesize_speech_flattened_async(): + client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials()) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.synthesize_speech), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = cloud_tts.SynthesizeSpeechResponse() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + cloud_tts.SynthesizeSpeechResponse() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.synthesize_speech( + input=cloud_tts.SynthesisInput(text="text_value"), + voice=cloud_tts.VoiceSelectionParams(language_code="language_code_value"), + audio_config=cloud_tts.AudioConfig( + audio_encoding=cloud_tts.AudioEncoding.LINEAR16 + ), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0].input == cloud_tts.SynthesisInput(text="text_value") + assert args[0].voice == cloud_tts.VoiceSelectionParams( + language_code="language_code_value" + ) + assert args[0].audio_config == cloud_tts.AudioConfig( + audio_encoding=cloud_tts.AudioEncoding.LINEAR16 + ) + + +@pytest.mark.asyncio +async def test_synthesize_speech_flattened_error_async(): + client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials()) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.synthesize_speech( + cloud_tts.SynthesizeSpeechRequest(), + input=cloud_tts.SynthesisInput(text="text_value"), + voice=cloud_tts.VoiceSelectionParams(language_code="language_code_value"), + audio_config=cloud_tts.AudioConfig( + audio_encoding=cloud_tts.AudioEncoding.LINEAR16 + ), + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.TextToSpeechGrpcTransport( + credentials=credentials.AnonymousCredentials() + ) + with pytest.raises(ValueError): + client = TextToSpeechClient( + credentials=credentials.AnonymousCredentials(), transport=transport + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.TextToSpeechGrpcTransport( + credentials=credentials.AnonymousCredentials() + ) + client = TextToSpeechClient(transport=transport) + assert client._transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.TextToSpeechGrpcTransport( + credentials=credentials.AnonymousCredentials() + ) + channel = transport.grpc_channel + assert channel + + transport = transports.TextToSpeechGrpcAsyncIOTransport( + credentials=credentials.AnonymousCredentials() + ) + channel = transport.grpc_channel + assert channel + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) + assert isinstance(client._transport, transports.TextToSpeechGrpcTransport) + + +def test_text_to_speech_base_transport(): + # Instantiate the base transport. + transport = transports.TextToSpeechTransport( + credentials=credentials.AnonymousCredentials() + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ("list_voices", "synthesize_speech") + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + +def test_text_to_speech_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + TextToSpeechClient() + adc.assert_called_once_with( + scopes=("https://www.googleapis.com/auth/cloud-platform",) + ) + + +def test_text_to_speech_transport_auth_adc(): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transports.TextToSpeechGrpcTransport(host="squid.clam.whelk") + adc.assert_called_once_with( + scopes=("https://www.googleapis.com/auth/cloud-platform",) + ) + + +def test_text_to_speech_host_no_port(): + client = TextToSpeechClient( + credentials=credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="texttospeech.googleapis.com" + ), + ) + assert client._transport._host == "texttospeech.googleapis.com:443" + + +def test_text_to_speech_host_with_port(): + client = TextToSpeechClient( + credentials=credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="texttospeech.googleapis.com:8000" + ), + ) + assert client._transport._host == "texttospeech.googleapis.com:8000" + + +def test_text_to_speech_grpc_transport_channel(): + channel = grpc.insecure_channel("http://localhost/") + + # Check that if channel is provided, mtls endpoint and client_cert_source + # won't be used. + callback = mock.MagicMock() + transport = transports.TextToSpeechGrpcTransport( + host="squid.clam.whelk", + channel=channel, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=callback, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + assert not callback.called + + +def test_text_to_speech_grpc_asyncio_transport_channel(): + channel = aio.insecure_channel("http://localhost/") + + # Check that if channel is provided, mtls endpoint and client_cert_source + # won't be used. + callback = mock.MagicMock() + transport = transports.TextToSpeechGrpcAsyncIOTransport( + host="squid.clam.whelk", + channel=channel, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=callback, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + assert not callback.called + + +@mock.patch("grpc.ssl_channel_credentials", autospec=True) +@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) +def test_text_to_speech_grpc_transport_channel_mtls_with_client_cert_source( + grpc_create_channel, grpc_ssl_channel_cred +): + # Check that if channel is None, but api_mtls_endpoint and client_cert_source + # are provided, then a mTLS channel will be created. + mock_cred = mock.Mock() + + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + transport = transports.TextToSpeechGrpcTransport( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + ssl_credentials=mock_ssl_cred, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ) + assert transport.grpc_channel == mock_grpc_channel + + +@mock.patch("grpc.ssl_channel_credentials", autospec=True) +@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) +def test_text_to_speech_grpc_asyncio_transport_channel_mtls_with_client_cert_source( + grpc_create_channel, grpc_ssl_channel_cred +): + # Check that if channel is None, but api_mtls_endpoint and client_cert_source + # are provided, then a mTLS channel will be created. + mock_cred = mock.Mock() + + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + transport = transports.TextToSpeechGrpcAsyncIOTransport( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + ssl_credentials=mock_ssl_cred, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ) + assert transport.grpc_channel == mock_grpc_channel + + +@pytest.mark.parametrize( + "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] +) +@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) +def test_text_to_speech_grpc_transport_channel_mtls_with_adc( + grpc_create_channel, api_mtls_endpoint +): + # Check that if channel and client_cert_source are None, but api_mtls_endpoint + # is provided, then a mTLS channel will be created with SSL ADC. + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + # Mock google.auth.transport.grpc.SslCredentials class. + mock_ssl_cred = mock.Mock() + with mock.patch.multiple( + "google.auth.transport.grpc.SslCredentials", + __init__=mock.Mock(return_value=None), + ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), + ): + mock_cred = mock.Mock() + transport = transports.TextToSpeechGrpcTransport( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint=api_mtls_endpoint, + client_cert_source=None, + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + ssl_credentials=mock_ssl_cred, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ) + assert transport.grpc_channel == mock_grpc_channel + + +@pytest.mark.parametrize( + "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] +) +@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) +def test_text_to_speech_grpc_asyncio_transport_channel_mtls_with_adc( + grpc_create_channel, api_mtls_endpoint +): + # Check that if channel and client_cert_source are None, but api_mtls_endpoint + # is provided, then a mTLS channel will be created with SSL ADC. + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + # Mock google.auth.transport.grpc.SslCredentials class. + mock_ssl_cred = mock.Mock() + with mock.patch.multiple( + "google.auth.transport.grpc.SslCredentials", + __init__=mock.Mock(return_value=None), + ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), + ): + mock_cred = mock.Mock() + transport = transports.TextToSpeechGrpcAsyncIOTransport( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint=api_mtls_endpoint, + client_cert_source=None, + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + ssl_credentials=mock_ssl_cred, + scopes=("https://www.googleapis.com/auth/cloud-platform",), + ) + assert transport.grpc_channel == mock_grpc_channel diff --git a/tests/unit/texttospeech_v1/test_text_to_speech.py b/tests/unit/texttospeech_v1/test_text_to_speech.py deleted file mode 100644 index 6ed9e648..00000000 --- a/tests/unit/texttospeech_v1/test_text_to_speech.py +++ /dev/null @@ -1,453 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -from unittest import mock - -import grpc -import math -import pytest - -from google import auth -from google.api_core import client_options -from google.api_core import grpc_helpers -from google.auth import credentials -from google.cloud.texttospeech_v1.services.text_to_speech import TextToSpeechClient -from google.cloud.texttospeech_v1.services.text_to_speech import transports -from google.cloud.texttospeech_v1.types import cloud_tts -from google.oauth2 import service_account - - -def client_cert_source_callback(): - return b"cert bytes", b"key bytes" - - -def test__get_default_mtls_endpoint(): - api_endpoint = "example.googleapis.com" - api_mtls_endpoint = "example.mtls.googleapis.com" - sandbox_endpoint = "example.sandbox.googleapis.com" - sandbox_mtls_endpoint = "example.mtls.sandbox.googleapis.com" - non_googleapi = "api.example.com" - - assert TextToSpeechClient._get_default_mtls_endpoint(None) is None - assert ( - TextToSpeechClient._get_default_mtls_endpoint(api_endpoint) == api_mtls_endpoint - ) - assert ( - TextToSpeechClient._get_default_mtls_endpoint(api_mtls_endpoint) - == api_mtls_endpoint - ) - assert ( - TextToSpeechClient._get_default_mtls_endpoint(sandbox_endpoint) - == sandbox_mtls_endpoint - ) - assert ( - TextToSpeechClient._get_default_mtls_endpoint(sandbox_mtls_endpoint) - == sandbox_mtls_endpoint - ) - assert TextToSpeechClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi - - -def test_text_to_speech_client_from_service_account_file(): - creds = credentials.AnonymousCredentials() - with mock.patch.object( - service_account.Credentials, "from_service_account_file" - ) as factory: - factory.return_value = creds - client = TextToSpeechClient.from_service_account_file("dummy/file/path.json") - assert client._transport._credentials == creds - - client = TextToSpeechClient.from_service_account_json("dummy/file/path.json") - assert client._transport._credentials == creds - - assert client._transport._host == "texttospeech.googleapis.com:443" - - -def test_text_to_speech_client_client_options(): - # Check that if channel is provided we won't create a new one. - with mock.patch( - "google.cloud.texttospeech_v1.services.text_to_speech.TextToSpeechClient.get_transport_class" - ) as gtc: - transport = transports.TextToSpeechGrpcTransport( - credentials=credentials.AnonymousCredentials() - ) - client = TextToSpeechClient(transport=transport) - gtc.assert_not_called() - - # Check mTLS is not triggered with empty client options. - options = client_options.ClientOptions() - with mock.patch( - "google.cloud.texttospeech_v1.services.text_to_speech.TextToSpeechClient.get_transport_class" - ) as gtc: - transport = gtc.return_value = mock.MagicMock() - client = TextToSpeechClient(client_options=options) - transport.assert_called_once_with( - credentials=None, host=client.DEFAULT_ENDPOINT - ) - - # Check mTLS is not triggered if api_endpoint is provided but - # client_cert_source is None. - options = client_options.ClientOptions(api_endpoint="squid.clam.whelk") - with mock.patch( - "google.cloud.texttospeech_v1.services.text_to_speech.transports.TextToSpeechGrpcTransport.__init__" - ) as grpc_transport: - grpc_transport.return_value = None - client = TextToSpeechClient(client_options=options) - grpc_transport.assert_called_once_with( - api_mtls_endpoint=None, - client_cert_source=None, - credentials=None, - host="squid.clam.whelk", - ) - - # Check mTLS is triggered if client_cert_source is provided. - options = client_options.ClientOptions( - client_cert_source=client_cert_source_callback - ) - with mock.patch( - "google.cloud.texttospeech_v1.services.text_to_speech.transports.TextToSpeechGrpcTransport.__init__" - ) as grpc_transport: - grpc_transport.return_value = None - client = TextToSpeechClient(client_options=options) - grpc_transport.assert_called_once_with( - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=client_cert_source_callback, - credentials=None, - host=client.DEFAULT_ENDPOINT, - ) - - # Check mTLS is triggered if api_endpoint and client_cert_source are provided. - options = client_options.ClientOptions( - api_endpoint="squid.clam.whelk", client_cert_source=client_cert_source_callback - ) - with mock.patch( - "google.cloud.texttospeech_v1.services.text_to_speech.transports.TextToSpeechGrpcTransport.__init__" - ) as grpc_transport: - grpc_transport.return_value = None - client = TextToSpeechClient(client_options=options) - grpc_transport.assert_called_once_with( - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=client_cert_source_callback, - credentials=None, - host="squid.clam.whelk", - ) - - -def test_text_to_speech_client_client_options_from_dict(): - with mock.patch( - "google.cloud.texttospeech_v1.services.text_to_speech.transports.TextToSpeechGrpcTransport.__init__" - ) as grpc_transport: - grpc_transport.return_value = None - client = TextToSpeechClient(client_options={"api_endpoint": "squid.clam.whelk"}) - grpc_transport.assert_called_once_with( - api_mtls_endpoint=None, - client_cert_source=None, - credentials=None, - host="squid.clam.whelk", - ) - - -def test_list_voices(transport: str = "grpc"): - client = TextToSpeechClient( - credentials=credentials.AnonymousCredentials(), transport=transport - ) - - # Everything is optional in proto3 as far as the runtime is concerned, - # and we are mocking out the actual API, so just send an empty request. - request = cloud_tts.ListVoicesRequest() - - # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object(type(client._transport.list_voices), "__call__") as call: - # Designate an appropriate return value for the call. - call.return_value = cloud_tts.ListVoicesResponse() - - response = client.list_voices(request) - - # Establish that the underlying gRPC stub method was called. - assert len(call.mock_calls) == 1 - _, args, _ = call.mock_calls[0] - - assert args[0] == request - - # Establish that the response is the type that we expect. - assert isinstance(response, cloud_tts.ListVoicesResponse) - - -def test_list_voices_flattened(): - client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) - - # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object(type(client._transport.list_voices), "__call__") as call: - # Designate an appropriate return value for the call. - call.return_value = cloud_tts.ListVoicesResponse() - - # Call the method with a truthy value for each flattened field, - # using the keyword arguments to the method. - response = client.list_voices(language_code="language_code_value") - - # Establish that the underlying call was made with the expected - # request object values. - assert len(call.mock_calls) == 1 - _, args, _ = call.mock_calls[0] - assert args[0].language_code == "language_code_value" - - -def test_list_voices_flattened_error(): - client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) - - # Attempting to call a method with both a request object and flattened - # fields is an error. - with pytest.raises(ValueError): - client.list_voices( - cloud_tts.ListVoicesRequest(), language_code="language_code_value" - ) - - -def test_synthesize_speech(transport: str = "grpc"): - client = TextToSpeechClient( - credentials=credentials.AnonymousCredentials(), transport=transport - ) - - # Everything is optional in proto3 as far as the runtime is concerned, - # and we are mocking out the actual API, so just send an empty request. - request = cloud_tts.SynthesizeSpeechRequest() - - # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object( - type(client._transport.synthesize_speech), "__call__" - ) as call: - # Designate an appropriate return value for the call. - call.return_value = cloud_tts.SynthesizeSpeechResponse( - audio_content=b"audio_content_blob" - ) - - response = client.synthesize_speech(request) - - # Establish that the underlying gRPC stub method was called. - assert len(call.mock_calls) == 1 - _, args, _ = call.mock_calls[0] - - assert args[0] == request - - # Establish that the response is the type that we expect. - assert isinstance(response, cloud_tts.SynthesizeSpeechResponse) - assert response.audio_content == b"audio_content_blob" - - -def test_synthesize_speech_flattened(): - client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) - - # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object( - type(client._transport.synthesize_speech), "__call__" - ) as call: - # Designate an appropriate return value for the call. - call.return_value = cloud_tts.SynthesizeSpeechResponse() - - # Call the method with a truthy value for each flattened field, - # using the keyword arguments to the method. - response = client.synthesize_speech( - input=cloud_tts.SynthesisInput(text="text_value"), - voice=cloud_tts.VoiceSelectionParams(language_code="language_code_value"), - audio_config=cloud_tts.AudioConfig( - audio_encoding=cloud_tts.AudioEncoding.LINEAR16 - ), - ) - - # Establish that the underlying call was made with the expected - # request object values. - assert len(call.mock_calls) == 1 - _, args, _ = call.mock_calls[0] - assert args[0].input == cloud_tts.SynthesisInput(text="text_value") - assert args[0].voice == cloud_tts.VoiceSelectionParams( - language_code="language_code_value" - ) - assert args[0].audio_config == cloud_tts.AudioConfig( - audio_encoding=cloud_tts.AudioEncoding.LINEAR16 - ) - - -def test_synthesize_speech_flattened_error(): - client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) - - # Attempting to call a method with both a request object and flattened - # fields is an error. - with pytest.raises(ValueError): - client.synthesize_speech( - cloud_tts.SynthesizeSpeechRequest(), - input=cloud_tts.SynthesisInput(text="text_value"), - voice=cloud_tts.VoiceSelectionParams(language_code="language_code_value"), - audio_config=cloud_tts.AudioConfig( - audio_encoding=cloud_tts.AudioEncoding.LINEAR16 - ), - ) - - -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.TextToSpeechGrpcTransport( - credentials=credentials.AnonymousCredentials() - ) - with pytest.raises(ValueError): - client = TextToSpeechClient( - credentials=credentials.AnonymousCredentials(), transport=transport - ) - - -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.TextToSpeechGrpcTransport( - credentials=credentials.AnonymousCredentials() - ) - client = TextToSpeechClient(transport=transport) - assert client._transport is transport - - -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. - client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) - assert isinstance(client._transport, transports.TextToSpeechGrpcTransport) - - -def test_text_to_speech_base_transport(): - # Instantiate the base transport. - transport = transports.TextToSpeechTransport( - credentials=credentials.AnonymousCredentials() - ) - - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ("list_voices", "synthesize_speech") - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - - -def test_text_to_speech_auth_adc(): - # If no credentials are provided, we should use ADC credentials. - with mock.patch.object(auth, "default") as adc: - adc.return_value = (credentials.AnonymousCredentials(), None) - TextToSpeechClient() - adc.assert_called_once_with( - scopes=("https://www.googleapis.com/auth/cloud-platform",) - ) - - -def test_text_to_speech_host_no_port(): - client = TextToSpeechClient( - credentials=credentials.AnonymousCredentials(), - client_options=client_options.ClientOptions( - api_endpoint="texttospeech.googleapis.com" - ), - transport="grpc", - ) - assert client._transport._host == "texttospeech.googleapis.com:443" - - -def test_text_to_speech_host_with_port(): - client = TextToSpeechClient( - credentials=credentials.AnonymousCredentials(), - client_options=client_options.ClientOptions( - api_endpoint="texttospeech.googleapis.com:8000" - ), - transport="grpc", - ) - assert client._transport._host == "texttospeech.googleapis.com:8000" - - -def test_text_to_speech_grpc_transport_channel(): - channel = grpc.insecure_channel("http://localhost/") - - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() - transport = transports.TextToSpeechGrpcTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, - ) - assert transport.grpc_channel == channel - assert transport._host == "squid.clam.whelk:443" - assert not callback.called - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_text_to_speech_grpc_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - transport = transports.TextToSpeechGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - ssl_credentials=mock_ssl_cred, - scopes=("https://www.googleapis.com/auth/cloud-platform",), - ) - assert transport.grpc_channel == mock_grpc_channel - - -@pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] -) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_text_to_speech_grpc_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. - mock_ssl_cred = mock.Mock() - with mock.patch.multiple( - "google.auth.transport.grpc.SslCredentials", - __init__=mock.Mock(return_value=None), - ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), - ): - mock_cred = mock.Mock() - transport = transports.TextToSpeechGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - ssl_credentials=mock_ssl_cred, - scopes=("https://www.googleapis.com/auth/cloud-platform",), - ) - assert transport.grpc_channel == mock_grpc_channel diff --git a/tests/unit/texttospeech_v1beta1/test_text_to_speech.py b/tests/unit/texttospeech_v1beta1/test_text_to_speech.py deleted file mode 100644 index 4441be4f..00000000 --- a/tests/unit/texttospeech_v1beta1/test_text_to_speech.py +++ /dev/null @@ -1,453 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -from unittest import mock - -import grpc -import math -import pytest - -from google import auth -from google.api_core import client_options -from google.api_core import grpc_helpers -from google.auth import credentials -from google.cloud.texttospeech_v1beta1.services.text_to_speech import TextToSpeechClient -from google.cloud.texttospeech_v1beta1.services.text_to_speech import transports -from google.cloud.texttospeech_v1beta1.types import cloud_tts -from google.oauth2 import service_account - - -def client_cert_source_callback(): - return b"cert bytes", b"key bytes" - - -def test__get_default_mtls_endpoint(): - api_endpoint = "example.googleapis.com" - api_mtls_endpoint = "example.mtls.googleapis.com" - sandbox_endpoint = "example.sandbox.googleapis.com" - sandbox_mtls_endpoint = "example.mtls.sandbox.googleapis.com" - non_googleapi = "api.example.com" - - assert TextToSpeechClient._get_default_mtls_endpoint(None) is None - assert ( - TextToSpeechClient._get_default_mtls_endpoint(api_endpoint) == api_mtls_endpoint - ) - assert ( - TextToSpeechClient._get_default_mtls_endpoint(api_mtls_endpoint) - == api_mtls_endpoint - ) - assert ( - TextToSpeechClient._get_default_mtls_endpoint(sandbox_endpoint) - == sandbox_mtls_endpoint - ) - assert ( - TextToSpeechClient._get_default_mtls_endpoint(sandbox_mtls_endpoint) - == sandbox_mtls_endpoint - ) - assert TextToSpeechClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi - - -def test_text_to_speech_client_from_service_account_file(): - creds = credentials.AnonymousCredentials() - with mock.patch.object( - service_account.Credentials, "from_service_account_file" - ) as factory: - factory.return_value = creds - client = TextToSpeechClient.from_service_account_file("dummy/file/path.json") - assert client._transport._credentials == creds - - client = TextToSpeechClient.from_service_account_json("dummy/file/path.json") - assert client._transport._credentials == creds - - assert client._transport._host == "texttospeech.googleapis.com:443" - - -def test_text_to_speech_client_client_options(): - # Check that if channel is provided we won't create a new one. - with mock.patch( - "google.cloud.texttospeech_v1beta1.services.text_to_speech.TextToSpeechClient.get_transport_class" - ) as gtc: - transport = transports.TextToSpeechGrpcTransport( - credentials=credentials.AnonymousCredentials() - ) - client = TextToSpeechClient(transport=transport) - gtc.assert_not_called() - - # Check mTLS is not triggered with empty client options. - options = client_options.ClientOptions() - with mock.patch( - "google.cloud.texttospeech_v1beta1.services.text_to_speech.TextToSpeechClient.get_transport_class" - ) as gtc: - transport = gtc.return_value = mock.MagicMock() - client = TextToSpeechClient(client_options=options) - transport.assert_called_once_with( - credentials=None, host=client.DEFAULT_ENDPOINT - ) - - # Check mTLS is not triggered if api_endpoint is provided but - # client_cert_source is None. - options = client_options.ClientOptions(api_endpoint="squid.clam.whelk") - with mock.patch( - "google.cloud.texttospeech_v1beta1.services.text_to_speech.transports.TextToSpeechGrpcTransport.__init__" - ) as grpc_transport: - grpc_transport.return_value = None - client = TextToSpeechClient(client_options=options) - grpc_transport.assert_called_once_with( - api_mtls_endpoint=None, - client_cert_source=None, - credentials=None, - host="squid.clam.whelk", - ) - - # Check mTLS is triggered if client_cert_source is provided. - options = client_options.ClientOptions( - client_cert_source=client_cert_source_callback - ) - with mock.patch( - "google.cloud.texttospeech_v1beta1.services.text_to_speech.transports.TextToSpeechGrpcTransport.__init__" - ) as grpc_transport: - grpc_transport.return_value = None - client = TextToSpeechClient(client_options=options) - grpc_transport.assert_called_once_with( - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=client_cert_source_callback, - credentials=None, - host=client.DEFAULT_ENDPOINT, - ) - - # Check mTLS is triggered if api_endpoint and client_cert_source are provided. - options = client_options.ClientOptions( - api_endpoint="squid.clam.whelk", client_cert_source=client_cert_source_callback - ) - with mock.patch( - "google.cloud.texttospeech_v1beta1.services.text_to_speech.transports.TextToSpeechGrpcTransport.__init__" - ) as grpc_transport: - grpc_transport.return_value = None - client = TextToSpeechClient(client_options=options) - grpc_transport.assert_called_once_with( - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=client_cert_source_callback, - credentials=None, - host="squid.clam.whelk", - ) - - -def test_text_to_speech_client_client_options_from_dict(): - with mock.patch( - "google.cloud.texttospeech_v1beta1.services.text_to_speech.transports.TextToSpeechGrpcTransport.__init__" - ) as grpc_transport: - grpc_transport.return_value = None - client = TextToSpeechClient(client_options={"api_endpoint": "squid.clam.whelk"}) - grpc_transport.assert_called_once_with( - api_mtls_endpoint=None, - client_cert_source=None, - credentials=None, - host="squid.clam.whelk", - ) - - -def test_list_voices(transport: str = "grpc"): - client = TextToSpeechClient( - credentials=credentials.AnonymousCredentials(), transport=transport - ) - - # Everything is optional in proto3 as far as the runtime is concerned, - # and we are mocking out the actual API, so just send an empty request. - request = cloud_tts.ListVoicesRequest() - - # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object(type(client._transport.list_voices), "__call__") as call: - # Designate an appropriate return value for the call. - call.return_value = cloud_tts.ListVoicesResponse() - - response = client.list_voices(request) - - # Establish that the underlying gRPC stub method was called. - assert len(call.mock_calls) == 1 - _, args, _ = call.mock_calls[0] - - assert args[0] == request - - # Establish that the response is the type that we expect. - assert isinstance(response, cloud_tts.ListVoicesResponse) - - -def test_list_voices_flattened(): - client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) - - # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object(type(client._transport.list_voices), "__call__") as call: - # Designate an appropriate return value for the call. - call.return_value = cloud_tts.ListVoicesResponse() - - # Call the method with a truthy value for each flattened field, - # using the keyword arguments to the method. - response = client.list_voices(language_code="language_code_value") - - # Establish that the underlying call was made with the expected - # request object values. - assert len(call.mock_calls) == 1 - _, args, _ = call.mock_calls[0] - assert args[0].language_code == "language_code_value" - - -def test_list_voices_flattened_error(): - client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) - - # Attempting to call a method with both a request object and flattened - # fields is an error. - with pytest.raises(ValueError): - client.list_voices( - cloud_tts.ListVoicesRequest(), language_code="language_code_value" - ) - - -def test_synthesize_speech(transport: str = "grpc"): - client = TextToSpeechClient( - credentials=credentials.AnonymousCredentials(), transport=transport - ) - - # Everything is optional in proto3 as far as the runtime is concerned, - # and we are mocking out the actual API, so just send an empty request. - request = cloud_tts.SynthesizeSpeechRequest() - - # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object( - type(client._transport.synthesize_speech), "__call__" - ) as call: - # Designate an appropriate return value for the call. - call.return_value = cloud_tts.SynthesizeSpeechResponse( - audio_content=b"audio_content_blob" - ) - - response = client.synthesize_speech(request) - - # Establish that the underlying gRPC stub method was called. - assert len(call.mock_calls) == 1 - _, args, _ = call.mock_calls[0] - - assert args[0] == request - - # Establish that the response is the type that we expect. - assert isinstance(response, cloud_tts.SynthesizeSpeechResponse) - assert response.audio_content == b"audio_content_blob" - - -def test_synthesize_speech_flattened(): - client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) - - # Mock the actual call within the gRPC stub, and fake the request. - with mock.patch.object( - type(client._transport.synthesize_speech), "__call__" - ) as call: - # Designate an appropriate return value for the call. - call.return_value = cloud_tts.SynthesizeSpeechResponse() - - # Call the method with a truthy value for each flattened field, - # using the keyword arguments to the method. - response = client.synthesize_speech( - input=cloud_tts.SynthesisInput(text="text_value"), - voice=cloud_tts.VoiceSelectionParams(language_code="language_code_value"), - audio_config=cloud_tts.AudioConfig( - audio_encoding=cloud_tts.AudioEncoding.LINEAR16 - ), - ) - - # Establish that the underlying call was made with the expected - # request object values. - assert len(call.mock_calls) == 1 - _, args, _ = call.mock_calls[0] - assert args[0].input == cloud_tts.SynthesisInput(text="text_value") - assert args[0].voice == cloud_tts.VoiceSelectionParams( - language_code="language_code_value" - ) - assert args[0].audio_config == cloud_tts.AudioConfig( - audio_encoding=cloud_tts.AudioEncoding.LINEAR16 - ) - - -def test_synthesize_speech_flattened_error(): - client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) - - # Attempting to call a method with both a request object and flattened - # fields is an error. - with pytest.raises(ValueError): - client.synthesize_speech( - cloud_tts.SynthesizeSpeechRequest(), - input=cloud_tts.SynthesisInput(text="text_value"), - voice=cloud_tts.VoiceSelectionParams(language_code="language_code_value"), - audio_config=cloud_tts.AudioConfig( - audio_encoding=cloud_tts.AudioEncoding.LINEAR16 - ), - ) - - -def test_credentials_transport_error(): - # It is an error to provide credentials and a transport instance. - transport = transports.TextToSpeechGrpcTransport( - credentials=credentials.AnonymousCredentials() - ) - with pytest.raises(ValueError): - client = TextToSpeechClient( - credentials=credentials.AnonymousCredentials(), transport=transport - ) - - -def test_transport_instance(): - # A client may be instantiated with a custom transport instance. - transport = transports.TextToSpeechGrpcTransport( - credentials=credentials.AnonymousCredentials() - ) - client = TextToSpeechClient(transport=transport) - assert client._transport is transport - - -def test_transport_grpc_default(): - # A client should use the gRPC transport by default. - client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) - assert isinstance(client._transport, transports.TextToSpeechGrpcTransport) - - -def test_text_to_speech_base_transport(): - # Instantiate the base transport. - transport = transports.TextToSpeechTransport( - credentials=credentials.AnonymousCredentials() - ) - - # Every method on the transport should just blindly - # raise NotImplementedError. - methods = ("list_voices", "synthesize_speech") - for method in methods: - with pytest.raises(NotImplementedError): - getattr(transport, method)(request=object()) - - -def test_text_to_speech_auth_adc(): - # If no credentials are provided, we should use ADC credentials. - with mock.patch.object(auth, "default") as adc: - adc.return_value = (credentials.AnonymousCredentials(), None) - TextToSpeechClient() - adc.assert_called_once_with( - scopes=("https://www.googleapis.com/auth/cloud-platform",) - ) - - -def test_text_to_speech_host_no_port(): - client = TextToSpeechClient( - credentials=credentials.AnonymousCredentials(), - client_options=client_options.ClientOptions( - api_endpoint="texttospeech.googleapis.com" - ), - transport="grpc", - ) - assert client._transport._host == "texttospeech.googleapis.com:443" - - -def test_text_to_speech_host_with_port(): - client = TextToSpeechClient( - credentials=credentials.AnonymousCredentials(), - client_options=client_options.ClientOptions( - api_endpoint="texttospeech.googleapis.com:8000" - ), - transport="grpc", - ) - assert client._transport._host == "texttospeech.googleapis.com:8000" - - -def test_text_to_speech_grpc_transport_channel(): - channel = grpc.insecure_channel("http://localhost/") - - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() - transport = transports.TextToSpeechGrpcTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, - ) - assert transport.grpc_channel == channel - assert transport._host == "squid.clam.whelk:443" - assert not callback.called - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_text_to_speech_grpc_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - transport = transports.TextToSpeechGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - ssl_credentials=mock_ssl_cred, - scopes=("https://www.googleapis.com/auth/cloud-platform",), - ) - assert transport.grpc_channel == mock_grpc_channel - - -@pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] -) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_text_to_speech_grpc_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. - mock_ssl_cred = mock.Mock() - with mock.patch.multiple( - "google.auth.transport.grpc.SslCredentials", - __init__=mock.Mock(return_value=None), - ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), - ): - mock_cred = mock.Mock() - transport = transports.TextToSpeechGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - ssl_credentials=mock_ssl_cred, - scopes=("https://www.googleapis.com/auth/cloud-platform",), - ) - assert transport.grpc_channel == mock_grpc_channel