- On January 1, 2020 this library will no longer support Python 2 on the latest released version. - Previously released library versions will continue to be available. For more information please + As of January 1, 2020 this library no longer supports Python 2 on the latest released version. + Library versions released prior to that date will continue to be available. For more information please visit Python 2 support on Google Cloud.
{% block body %} {% endblock %} diff --git a/docs/conf.py b/docs/conf.py index a4035399..a7ce80f3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,7 +23,6 @@ # For plugins that can not read conf.py. sys.path.insert(0, os.path.abspath(".")) - __version__ = "" # -- General configuration ------------------------------------------------ @@ -341,7 +340,7 @@ intersphinx_mapping = { "python": ("http://python.readthedocs.org/en/latest/", None), "google-auth": ("https://google-auth.readthedocs.io/en/stable", None), - "google.api_core": ("https://googleapis.dev/python/google-api-core/latest/", None), + "google.api_core": ("https://googleapis.dev/python/google-api-core/latest/", None,), "grpc": ("https://grpc.io/grpc/python/", None), } diff --git a/google/cloud/texttospeech_v1/services/text_to_speech/__init__.py b/google/cloud/texttospeech_v1/services/text_to_speech/__init__.py index 989f886a..a63df317 100644 --- a/google/cloud/texttospeech_v1/services/text_to_speech/__init__.py +++ b/google/cloud/texttospeech_v1/services/text_to_speech/__init__.py @@ -18,4 +18,7 @@ from .client import TextToSpeechClient from .async_client import TextToSpeechAsyncClient -__all__ = ("TextToSpeechClient", "TextToSpeechAsyncClient") +__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 index b450d611..94ec3b63 100644 --- a/google/cloud/texttospeech_v1/services/text_to_speech/async_client.py +++ b/google/cloud/texttospeech_v1/services/text_to_speech/async_client.py @@ -88,7 +88,7 @@ def __init__( """ self._client = TextToSpeechClient( - credentials=credentials, transport=transport, client_options=client_options + credentials=credentials, transport=transport, client_options=client_options, ) async def list_voices( @@ -113,9 +113,9 @@ async def list_voices( 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-*" + will get supported "no-\*" (Norwegian) and "nb-\*" (Norwegian Bokmal) voices; specifying "zh" will also get - supported "cmn-*" voices; specifying "zh-hk" will also + 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 @@ -159,7 +159,7 @@ async def list_voices( ) # Send the request. - response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata) + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) # Done; return the response. return response @@ -243,7 +243,7 @@ async def synthesize_speech( ) # Send the request. - response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata) + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) # Done; return the response. return response @@ -252,8 +252,8 @@ async def synthesize_speech( try: _client_info = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution( - "google-cloud-texttospeech" - ).version + "google-cloud-texttospeech", + ).version, ) except pkg_resources.DistributionNotFound: _client_info = gapic_v1.client_info.ClientInfo() 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 b87cc114..25192640 100644 --- a/google/cloud/texttospeech_v1/services/text_to_speech/client.py +++ b/google/cloud/texttospeech_v1/services/text_to_speech/client.py @@ -49,7 +49,7 @@ class TextToSpeechClientMeta(type): _transport_registry["grpc"] = TextToSpeechGrpcTransport _transport_registry["grpc_asyncio"] = TextToSpeechGrpcAsyncIOTransport - def get_transport_class(cls, label: str = None) -> Type[TextToSpeechTransport]: + def get_transport_class(cls, label: str = None,) -> Type[TextToSpeechTransport]: """Return an appropriate transport class. Args: @@ -192,19 +192,27 @@ def __init__( # instance provides an extensibility point for unusual situations. if isinstance(transport, TextToSpeechTransport): # transport is a TextToSpeechTransport instance. - if credentials: + if credentials or client_options.credentials_file: raise ValueError( "When providing a transport instance, " "provide its credentials directly." ) + if client_options.scopes: + raise ValueError( + "When providing a transport instance, " + "provide its scopes directly." + ) self._transport = transport else: Transport = type(self).get_transport_class(transport) self._transport = Transport( credentials=credentials, + credentials_file=client_options.credentials_file, host=client_options.api_endpoint, + scopes=client_options.scopes, api_mtls_endpoint=client_options.api_endpoint, client_cert_source=client_options.client_cert_source, + quota_project_id=client_options.quota_project_id, ) def list_voices( @@ -229,9 +237,9 @@ def list_voices( 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-*" + will get supported "no-\*" (Norwegian) and "nb-\*" (Norwegian Bokmal) voices; specifying "zh" will also get - supported "cmn-*" voices; specifying "zh-hk" will also + 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 @@ -252,28 +260,32 @@ def list_voices( # 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]): + has_flattened_params = any([language_code]) + if request is not None and has_flattened_params: raise ValueError( "If the `request` argument is set, then none of " "the individual field arguments should be set." ) - request = cloud_tts.ListVoicesRequest(request) + # Minor optimization to avoid making a copy if the user passes + # in a cloud_tts.ListVoicesRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, cloud_tts.ListVoicesRequest): + request = cloud_tts.ListVoicesRequest(request) - # If we have keyword arguments corresponding to fields on the - # request, apply these. + # If we have keyword arguments corresponding to fields on the + # request, apply these. - if language_code is not None: - request.language_code = language_code + 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.wrap_method( - self._transport.list_voices, default_timeout=None, client_info=_client_info - ) + rpc = self._transport._wrapped_methods[self._transport.list_voices] # Send the request. - response = rpc(request, retry=retry, timeout=timeout, metadata=metadata) + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) # Done; return the response. return response @@ -330,34 +342,36 @@ def synthesize_speech( # 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]): + has_flattened_params = any([input, voice, audio_config]) + if request is not None and has_flattened_params: raise ValueError( "If the `request` argument is set, then none of " "the individual field arguments should be set." ) - request = cloud_tts.SynthesizeSpeechRequest(request) + # Minor optimization to avoid making a copy if the user passes + # in a cloud_tts.SynthesizeSpeechRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, cloud_tts.SynthesizeSpeechRequest): + request = cloud_tts.SynthesizeSpeechRequest(request) - # If we have keyword arguments corresponding to fields on the - # request, apply these. + # If we have keyword arguments corresponding to fields on the + # request, apply these. - if 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 + 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.wrap_method( - self._transport.synthesize_speech, - default_timeout=None, - client_info=_client_info, - ) + rpc = self._transport._wrapped_methods[self._transport.synthesize_speech] # Send the request. - response = rpc(request, retry=retry, timeout=timeout, metadata=metadata) + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) # Done; return the response. return response @@ -366,8 +380,8 @@ def synthesize_speech( try: _client_info = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution( - "google-cloud-texttospeech" - ).version + "google-cloud-texttospeech", + ).version, ) except pkg_resources.DistributionNotFound: _client_info = gapic_v1.client_info.ClientInfo() 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 6aa84b25..beac0640 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 @@ -17,13 +17,27 @@ import abc import typing +import pkg_resources from google import auth +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.cloud.texttospeech_v1.types import cloud_tts +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() + + class TextToSpeechTransport(abc.ABC): """Abstract transport class for TextToSpeech.""" @@ -34,6 +48,9 @@ def __init__( *, host: str = "texttospeech.googleapis.com", credentials: credentials.Credentials = None, + credentials_file: typing.Optional[str] = None, + scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, + quota_project_id: typing.Optional[str] = None, **kwargs, ) -> None: """Instantiate the transport. @@ -45,6 +62,12 @@ def __init__( credentials identify the application to the service; if none are specified, the client will attempt to ascertain the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scope (Optional[Sequence[str]]): A list of scopes. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. if ":" not in host: @@ -53,15 +76,41 @@ def __init__( # If no credentials are provided, then determine the appropriate # defaults. - if credentials is None: - credentials, _ = auth.default(scopes=self.AUTH_SCOPES) + if credentials and credentials_file: + raise exceptions.DuplicateCredentialArgs( + "'credentials_file' and 'credentials' are mutually exclusive" + ) + + if credentials_file is not None: + credentials, _ = auth.load_credentials_from_file( + credentials_file, scopes=scopes, quota_project_id=quota_project_id + ) + + elif credentials is None: + credentials, _ = auth.default( + scopes=scopes, quota_project_id=quota_project_id + ) # Save the credentials. self._credentials = credentials + # Lifted into its own function so it can be stubbed out during tests. + self._prep_wrapped_messages() + + def _prep_wrapped_messages(self): + # Precompute the wrapped methods. + self._wrapped_methods = { + self.list_voices: gapic_v1.method.wrap_method( + self.list_voices, default_timeout=None, client_info=_client_info, + ), + self.synthesize_speech: gapic_v1.method.wrap_method( + self.synthesize_speech, default_timeout=None, client_info=_client_info, + ), + } + @property def list_voices( - self + self, ) -> typing.Callable[ [cloud_tts.ListVoicesRequest], typing.Union[ @@ -72,7 +121,7 @@ def list_voices( @property def synthesize_speech( - self + self, ) -> typing.Callable[ [cloud_tts.SynthesizeSpeechRequest], typing.Union[ 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 498b3501..d4d181b3 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 @@ -50,9 +50,12 @@ def __init__( *, host: str = "texttospeech.googleapis.com", credentials: credentials.Credentials = None, + credentials_file: str = None, + scopes: Sequence[str] = None, channel: grpc.Channel = None, api_mtls_endpoint: str = None, - client_cert_source: Callable[[], Tuple[bytes, bytes]] = None + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + quota_project_id: Optional[str] = None ) -> None: """Instantiate the transport. @@ -64,6 +67,11 @@ def __init__( are specified, the client will attempt to ascertain the credentials from the environment. This argument is ignored if ``channel`` is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. channel (Optional[grpc.Channel]): A ``Channel`` instance through which to make calls. api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If @@ -74,10 +82,14 @@ def __init__( callback to provide client SSL certificate bytes and private key bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` is None. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. Raises: - google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport - creation failed for any reason. + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. """ if channel: # Sanity check: Ensure that channel and credentials are not both @@ -94,7 +106,9 @@ def __init__( ) if credentials is None: - credentials, _ = auth.default(scopes=self.AUTH_SCOPES) + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) # Create SSL credentials with client_cert_source or application # default SSL credentials. @@ -110,20 +124,31 @@ def __init__( self._grpc_channel = type(self).create_channel( host, credentials=credentials, + credentials_file=credentials_file, ssl_credentials=ssl_credentials, - scopes=self.AUTH_SCOPES, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, ) - # Run the base constructor. - super().__init__(host=host, credentials=credentials) self._stubs = {} # type: Dict[str, Callable] + # Run the base constructor. + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + @classmethod def create_channel( cls, host: str = "texttospeech.googleapis.com", credentials: credentials.Credentials = None, + credentials_file: str = None, scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, **kwargs ) -> grpc.Channel: """Create and return a gRPC channel object. @@ -134,17 +159,31 @@ def create_channel( credentials identify this application to the service. If none are specified, the client will attempt to ascertain the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. scopes (Optional[Sequence[str]]): A optional list of scopes needed for this service. These are only used when credentials are not specified and are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. kwargs (Optional[dict]): Keyword arguments, which are passed to the channel creation. Returns: grpc.Channel: A gRPC channel object. + + Raises: + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. """ scopes = scopes or cls.AUTH_SCOPES return grpc_helpers.create_channel( - host, credentials=credentials, scopes=scopes, **kwargs + host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + **kwargs ) @property @@ -158,7 +197,7 @@ def grpc_channel(self) -> grpc.Channel: # have one. if not hasattr(self, "_grpc_channel"): self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials + self._host, credentials=self._credentials, ) # Return the channel from cache. @@ -166,7 +205,7 @@ def grpc_channel(self) -> grpc.Channel: @property def list_voices( - self + self, ) -> Callable[[cloud_tts.ListVoicesRequest], cloud_tts.ListVoicesResponse]: r"""Return a callable for the list voices method over gRPC. @@ -192,7 +231,7 @@ def list_voices( @property def synthesize_speech( - self + self, ) -> Callable[ [cloud_tts.SynthesizeSpeechRequest], cloud_tts.SynthesizeSpeechResponse ]: 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 index e93dfb2f..368d6096 100644 --- 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 @@ -51,8 +51,10 @@ def create_channel( cls, host: str = "texttospeech.googleapis.com", credentials: credentials.Credentials = None, + credentials_file: Optional[str] = None, scopes: Optional[Sequence[str]] = None, - **kwargs + quota_project_id: Optional[str] = None, + **kwargs, ) -> aio.Channel: """Create and return a gRPC AsyncIO channel object. Args: @@ -62,9 +64,14 @@ def create_channel( credentials identify this application to the service. If none are specified, the client will attempt to ascertain the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. scopes (Optional[Sequence[str]]): A optional list of scopes needed for this service. These are only used when credentials are not specified and are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. kwargs (Optional[dict]): Keyword arguments, which are passed to the channel creation. Returns: @@ -72,7 +79,12 @@ def create_channel( """ scopes = scopes or cls.AUTH_SCOPES return grpc_helpers_async.create_channel( - host, credentials=credentials, scopes=scopes, **kwargs + host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + **kwargs, ) def __init__( @@ -80,9 +92,12 @@ def __init__( *, host: str = "texttospeech.googleapis.com", credentials: credentials.Credentials = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, channel: aio.Channel = None, api_mtls_endpoint: str = None, - client_cert_source: Callable[[], Tuple[bytes, bytes]] = None + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + quota_project_id=None, ) -> None: """Instantiate the transport. @@ -94,6 +109,12 @@ def __init__( are specified, the client will attempt to ascertain the credentials from the environment. This argument is ignored if ``channel`` is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. channel (Optional[aio.Channel]): A ``Channel`` instance through which to make calls. api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If @@ -104,10 +125,14 @@ def __init__( callback to provide client SSL certificate bytes and private key bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` is None. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. Raises: - google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. """ if channel: # Sanity check: Ensure that channel and credentials are not both @@ -137,12 +162,21 @@ def __init__( self._grpc_channel = type(self).create_channel( host, credentials=credentials, + credentials_file=credentials_file, ssl_credentials=ssl_credentials, - scopes=self.AUTH_SCOPES, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, ) # Run the base constructor. - super().__init__(host=host, credentials=credentials) + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + self._stubs = {} @property @@ -156,7 +190,7 @@ def grpc_channel(self) -> aio.Channel: # have one. if not hasattr(self, "_grpc_channel"): self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials + self._host, credentials=self._credentials, ) # Return the channel from cache. @@ -164,7 +198,7 @@ def grpc_channel(self) -> aio.Channel: @property def list_voices( - self + self, ) -> Callable[ [cloud_tts.ListVoicesRequest], Awaitable[cloud_tts.ListVoicesResponse] ]: @@ -192,7 +226,7 @@ def list_voices( @property def synthesize_speech( - self + self, ) -> Callable[ [cloud_tts.SynthesizeSpeechRequest], Awaitable[cloud_tts.SynthesizeSpeechResponse], diff --git a/google/cloud/texttospeech_v1/types/cloud_tts.py b/google/cloud/texttospeech_v1/types/cloud_tts.py index c62376d5..602ffa03 100644 --- a/google/cloud/texttospeech_v1/types/cloud_tts.py +++ b/google/cloud/texttospeech_v1/types/cloud_tts.py @@ -67,8 +67,8 @@ class ListVoicesRequest(proto.Message): 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-*" + 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. """ @@ -84,7 +84,7 @@ class ListVoicesResponse(proto.Message): The list of voices. """ - voices = proto.RepeatedField(proto.MESSAGE, number=1, message="Voice") + voices = proto.RepeatedField(proto.MESSAGE, number=1, message="Voice",) class Voice(proto.Message): @@ -106,8 +106,11 @@ class Voice(proto.Message): """ language_codes = proto.RepeatedField(proto.STRING, number=1) + name = proto.Field(proto.STRING, number=2) - ssml_gender = proto.Field(proto.ENUM, number=3, enum="SsmlVoiceGender") + + ssml_gender = proto.Field(proto.ENUM, number=3, enum="SsmlVoiceGender",) + natural_sample_rate_hertz = proto.Field(proto.INT32, number=4) @@ -127,9 +130,11 @@ class SynthesizeSpeechRequest(proto.Message): synthesized audio. """ - input = proto.Field(proto.MESSAGE, number=1, message="SynthesisInput") - voice = proto.Field(proto.MESSAGE, number=2, message="VoiceSelectionParams") - audio_config = proto.Field(proto.MESSAGE, number=3, message="AudioConfig") + input = proto.Field(proto.MESSAGE, number=1, message="SynthesisInput",) + + voice = proto.Field(proto.MESSAGE, number=2, message="VoiceSelectionParams",) + + audio_config = proto.Field(proto.MESSAGE, number=3, message="AudioConfig",) class SynthesisInput(proto.Message): @@ -149,8 +154,9 @@ class SynthesisInput(proto.Message): `SSML `__. """ - text = proto.Field(proto.STRING, number=1) - ssml = proto.Field(proto.STRING, number=2) + text = proto.Field(proto.STRING, number=1, oneof="input_source") + + ssml = proto.Field(proto.STRING, number=2, oneof="input_source") class VoiceSelectionParams(proto.Message): @@ -186,8 +192,10 @@ class VoiceSelectionParams(proto.Message): """ language_code = proto.Field(proto.STRING, number=1) + name = proto.Field(proto.STRING, number=2) - ssml_gender = proto.Field(proto.ENUM, number=3, enum="SsmlVoiceGender") + + ssml_gender = proto.Field(proto.ENUM, number=3, enum="SsmlVoiceGender",) class AudioConfig(proto.Message): @@ -237,11 +245,16 @@ class AudioConfig(proto.Message): for current supported profile ids. """ - audio_encoding = proto.Field(proto.ENUM, number=1, enum="AudioEncoding") + audio_encoding = proto.Field(proto.ENUM, number=1, enum="AudioEncoding",) + speaking_rate = proto.Field(proto.DOUBLE, number=2) + pitch = proto.Field(proto.DOUBLE, number=3) + volume_gain_db = proto.Field(proto.DOUBLE, number=4) + sample_rate_hertz = proto.Field(proto.INT32, number=5) + effects_profile_id = proto.RepeatedField(proto.STRING, number=6) diff --git a/google/cloud/texttospeech_v1beta1/__init__.py b/google/cloud/texttospeech_v1beta1/__init__.py index 4d285a25..f27e5613 100644 --- a/google/cloud/texttospeech_v1beta1/__init__.py +++ b/google/cloud/texttospeech_v1beta1/__init__.py @@ -24,6 +24,7 @@ from .types.cloud_tts import SynthesisInput from .types.cloud_tts import SynthesizeSpeechRequest from .types.cloud_tts import SynthesizeSpeechResponse +from .types.cloud_tts import Timepoint from .types.cloud_tts import Voice from .types.cloud_tts import VoiceSelectionParams @@ -37,6 +38,7 @@ "SynthesisInput", "SynthesizeSpeechRequest", "SynthesizeSpeechResponse", + "Timepoint", "Voice", "VoiceSelectionParams", "TextToSpeechClient", 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 989f886a..a63df317 100644 --- a/google/cloud/texttospeech_v1beta1/services/text_to_speech/__init__.py +++ b/google/cloud/texttospeech_v1beta1/services/text_to_speech/__init__.py @@ -18,4 +18,7 @@ from .client import TextToSpeechClient from .async_client import TextToSpeechAsyncClient -__all__ = ("TextToSpeechClient", "TextToSpeechAsyncClient") +__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 index f6fa7df9..7bd2c85e 100644 --- a/google/cloud/texttospeech_v1beta1/services/text_to_speech/async_client.py +++ b/google/cloud/texttospeech_v1beta1/services/text_to_speech/async_client.py @@ -88,7 +88,7 @@ def __init__( """ self._client = TextToSpeechClient( - credentials=credentials, transport=transport, client_options=client_options + credentials=credentials, transport=transport, client_options=client_options, ) async def list_voices( @@ -113,9 +113,9 @@ async def list_voices( 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-*" + will get supported "no-\*" (Norwegian) and "nb-\*" (Norwegian Bokmal) voices; specifying "zh" will also get - supported "cmn-*" voices; specifying "zh-hk" will also + 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 @@ -159,7 +159,7 @@ async def list_voices( ) # Send the request. - response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata) + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) # Done; return the response. return response @@ -243,7 +243,7 @@ async def synthesize_speech( ) # Send the request. - response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata) + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) # Done; return the response. return response @@ -252,8 +252,8 @@ async def synthesize_speech( try: _client_info = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution( - "google-cloud-texttospeech" - ).version + "google-cloud-texttospeech", + ).version, ) except pkg_resources.DistributionNotFound: _client_info = gapic_v1.client_info.ClientInfo() 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 f885936b..248f3e79 100644 --- a/google/cloud/texttospeech_v1beta1/services/text_to_speech/client.py +++ b/google/cloud/texttospeech_v1beta1/services/text_to_speech/client.py @@ -49,7 +49,7 @@ class TextToSpeechClientMeta(type): _transport_registry["grpc"] = TextToSpeechGrpcTransport _transport_registry["grpc_asyncio"] = TextToSpeechGrpcAsyncIOTransport - def get_transport_class(cls, label: str = None) -> Type[TextToSpeechTransport]: + def get_transport_class(cls, label: str = None,) -> Type[TextToSpeechTransport]: """Return an appropriate transport class. Args: @@ -192,19 +192,27 @@ def __init__( # instance provides an extensibility point for unusual situations. if isinstance(transport, TextToSpeechTransport): # transport is a TextToSpeechTransport instance. - if credentials: + if credentials or client_options.credentials_file: raise ValueError( "When providing a transport instance, " "provide its credentials directly." ) + if client_options.scopes: + raise ValueError( + "When providing a transport instance, " + "provide its scopes directly." + ) self._transport = transport else: Transport = type(self).get_transport_class(transport) self._transport = Transport( credentials=credentials, + credentials_file=client_options.credentials_file, host=client_options.api_endpoint, + scopes=client_options.scopes, api_mtls_endpoint=client_options.api_endpoint, client_cert_source=client_options.client_cert_source, + quota_project_id=client_options.quota_project_id, ) def list_voices( @@ -229,9 +237,9 @@ def list_voices( 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-*" + will get supported "no-\*" (Norwegian) and "nb-\*" (Norwegian Bokmal) voices; specifying "zh" will also get - supported "cmn-*" voices; specifying "zh-hk" will also + 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 @@ -252,28 +260,32 @@ def list_voices( # 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]): + has_flattened_params = any([language_code]) + if request is not None and has_flattened_params: raise ValueError( "If the `request` argument is set, then none of " "the individual field arguments should be set." ) - request = cloud_tts.ListVoicesRequest(request) + # Minor optimization to avoid making a copy if the user passes + # in a cloud_tts.ListVoicesRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, cloud_tts.ListVoicesRequest): + request = cloud_tts.ListVoicesRequest(request) - # If we have keyword arguments corresponding to fields on the - # request, apply these. + # If we have keyword arguments corresponding to fields on the + # request, apply these. - if language_code is not None: - request.language_code = language_code + 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.wrap_method( - self._transport.list_voices, default_timeout=None, client_info=_client_info - ) + rpc = self._transport._wrapped_methods[self._transport.list_voices] # Send the request. - response = rpc(request, retry=retry, timeout=timeout, metadata=metadata) + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) # Done; return the response. return response @@ -330,34 +342,36 @@ def synthesize_speech( # 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]): + has_flattened_params = any([input, voice, audio_config]) + if request is not None and has_flattened_params: raise ValueError( "If the `request` argument is set, then none of " "the individual field arguments should be set." ) - request = cloud_tts.SynthesizeSpeechRequest(request) + # Minor optimization to avoid making a copy if the user passes + # in a cloud_tts.SynthesizeSpeechRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, cloud_tts.SynthesizeSpeechRequest): + request = cloud_tts.SynthesizeSpeechRequest(request) - # If we have keyword arguments corresponding to fields on the - # request, apply these. + # If we have keyword arguments corresponding to fields on the + # request, apply these. - if 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 + 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.wrap_method( - self._transport.synthesize_speech, - default_timeout=None, - client_info=_client_info, - ) + rpc = self._transport._wrapped_methods[self._transport.synthesize_speech] # Send the request. - response = rpc(request, retry=retry, timeout=timeout, metadata=metadata) + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) # Done; return the response. return response @@ -366,8 +380,8 @@ def synthesize_speech( try: _client_info = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution( - "google-cloud-texttospeech" - ).version + "google-cloud-texttospeech", + ).version, ) except pkg_resources.DistributionNotFound: _client_info = gapic_v1.client_info.ClientInfo() 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 4e8c12da..fa11749e 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 @@ -17,13 +17,27 @@ import abc import typing +import pkg_resources from google import auth +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.cloud.texttospeech_v1beta1.types import cloud_tts +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() + + class TextToSpeechTransport(abc.ABC): """Abstract transport class for TextToSpeech.""" @@ -34,6 +48,9 @@ def __init__( *, host: str = "texttospeech.googleapis.com", credentials: credentials.Credentials = None, + credentials_file: typing.Optional[str] = None, + scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, + quota_project_id: typing.Optional[str] = None, **kwargs, ) -> None: """Instantiate the transport. @@ -45,6 +62,12 @@ def __init__( credentials identify the application to the service; if none are specified, the client will attempt to ascertain the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scope (Optional[Sequence[str]]): A list of scopes. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. if ":" not in host: @@ -53,15 +76,41 @@ def __init__( # If no credentials are provided, then determine the appropriate # defaults. - if credentials is None: - credentials, _ = auth.default(scopes=self.AUTH_SCOPES) + if credentials and credentials_file: + raise exceptions.DuplicateCredentialArgs( + "'credentials_file' and 'credentials' are mutually exclusive" + ) + + if credentials_file is not None: + credentials, _ = auth.load_credentials_from_file( + credentials_file, scopes=scopes, quota_project_id=quota_project_id + ) + + elif credentials is None: + credentials, _ = auth.default( + scopes=scopes, quota_project_id=quota_project_id + ) # Save the credentials. self._credentials = credentials + # Lifted into its own function so it can be stubbed out during tests. + self._prep_wrapped_messages() + + def _prep_wrapped_messages(self): + # Precompute the wrapped methods. + self._wrapped_methods = { + self.list_voices: gapic_v1.method.wrap_method( + self.list_voices, default_timeout=None, client_info=_client_info, + ), + self.synthesize_speech: gapic_v1.method.wrap_method( + self.synthesize_speech, default_timeout=None, client_info=_client_info, + ), + } + @property def list_voices( - self + self, ) -> typing.Callable[ [cloud_tts.ListVoicesRequest], typing.Union[ @@ -72,7 +121,7 @@ def list_voices( @property def synthesize_speech( - self + self, ) -> typing.Callable[ [cloud_tts.SynthesizeSpeechRequest], typing.Union[ 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 a516fbb8..44bec5b1 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 @@ -50,9 +50,12 @@ def __init__( *, host: str = "texttospeech.googleapis.com", credentials: credentials.Credentials = None, + credentials_file: str = None, + scopes: Sequence[str] = None, channel: grpc.Channel = None, api_mtls_endpoint: str = None, - client_cert_source: Callable[[], Tuple[bytes, bytes]] = None + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + quota_project_id: Optional[str] = None ) -> None: """Instantiate the transport. @@ -64,6 +67,11 @@ def __init__( are specified, the client will attempt to ascertain the credentials from the environment. This argument is ignored if ``channel`` is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. channel (Optional[grpc.Channel]): A ``Channel`` instance through which to make calls. api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If @@ -74,10 +82,14 @@ def __init__( callback to provide client SSL certificate bytes and private key bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` is None. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. Raises: - google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport - creation failed for any reason. + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. """ if channel: # Sanity check: Ensure that channel and credentials are not both @@ -94,7 +106,9 @@ def __init__( ) if credentials is None: - credentials, _ = auth.default(scopes=self.AUTH_SCOPES) + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) # Create SSL credentials with client_cert_source or application # default SSL credentials. @@ -110,20 +124,31 @@ def __init__( self._grpc_channel = type(self).create_channel( host, credentials=credentials, + credentials_file=credentials_file, ssl_credentials=ssl_credentials, - scopes=self.AUTH_SCOPES, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, ) - # Run the base constructor. - super().__init__(host=host, credentials=credentials) self._stubs = {} # type: Dict[str, Callable] + # Run the base constructor. + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + @classmethod def create_channel( cls, host: str = "texttospeech.googleapis.com", credentials: credentials.Credentials = None, + credentials_file: str = None, scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, **kwargs ) -> grpc.Channel: """Create and return a gRPC channel object. @@ -134,17 +159,31 @@ def create_channel( credentials identify this application to the service. If none are specified, the client will attempt to ascertain the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. scopes (Optional[Sequence[str]]): A optional list of scopes needed for this service. These are only used when credentials are not specified and are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. kwargs (Optional[dict]): Keyword arguments, which are passed to the channel creation. Returns: grpc.Channel: A gRPC channel object. + + Raises: + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. """ scopes = scopes or cls.AUTH_SCOPES return grpc_helpers.create_channel( - host, credentials=credentials, scopes=scopes, **kwargs + host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + **kwargs ) @property @@ -158,7 +197,7 @@ def grpc_channel(self) -> grpc.Channel: # have one. if not hasattr(self, "_grpc_channel"): self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials + self._host, credentials=self._credentials, ) # Return the channel from cache. @@ -166,7 +205,7 @@ def grpc_channel(self) -> grpc.Channel: @property def list_voices( - self + self, ) -> Callable[[cloud_tts.ListVoicesRequest], cloud_tts.ListVoicesResponse]: r"""Return a callable for the list voices method over gRPC. @@ -192,7 +231,7 @@ def list_voices( @property def synthesize_speech( - self + self, ) -> Callable[ [cloud_tts.SynthesizeSpeechRequest], cloud_tts.SynthesizeSpeechResponse ]: 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 index e937b24d..084455e4 100644 --- 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 @@ -51,8 +51,10 @@ def create_channel( cls, host: str = "texttospeech.googleapis.com", credentials: credentials.Credentials = None, + credentials_file: Optional[str] = None, scopes: Optional[Sequence[str]] = None, - **kwargs + quota_project_id: Optional[str] = None, + **kwargs, ) -> aio.Channel: """Create and return a gRPC AsyncIO channel object. Args: @@ -62,9 +64,14 @@ def create_channel( credentials identify this application to the service. If none are specified, the client will attempt to ascertain the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. scopes (Optional[Sequence[str]]): A optional list of scopes needed for this service. These are only used when credentials are not specified and are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. kwargs (Optional[dict]): Keyword arguments, which are passed to the channel creation. Returns: @@ -72,7 +79,12 @@ def create_channel( """ scopes = scopes or cls.AUTH_SCOPES return grpc_helpers_async.create_channel( - host, credentials=credentials, scopes=scopes, **kwargs + host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + **kwargs, ) def __init__( @@ -80,9 +92,12 @@ def __init__( *, host: str = "texttospeech.googleapis.com", credentials: credentials.Credentials = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, channel: aio.Channel = None, api_mtls_endpoint: str = None, - client_cert_source: Callable[[], Tuple[bytes, bytes]] = None + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + quota_project_id=None, ) -> None: """Instantiate the transport. @@ -94,6 +109,12 @@ def __init__( are specified, the client will attempt to ascertain the credentials from the environment. This argument is ignored if ``channel`` is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. channel (Optional[aio.Channel]): A ``Channel`` instance through which to make calls. api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If @@ -104,10 +125,14 @@ def __init__( callback to provide client SSL certificate bytes and private key bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` is None. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. Raises: - google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. """ if channel: # Sanity check: Ensure that channel and credentials are not both @@ -137,12 +162,21 @@ def __init__( self._grpc_channel = type(self).create_channel( host, credentials=credentials, + credentials_file=credentials_file, ssl_credentials=ssl_credentials, - scopes=self.AUTH_SCOPES, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, ) # Run the base constructor. - super().__init__(host=host, credentials=credentials) + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + self._stubs = {} @property @@ -156,7 +190,7 @@ def grpc_channel(self) -> aio.Channel: # have one. if not hasattr(self, "_grpc_channel"): self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials + self._host, credentials=self._credentials, ) # Return the channel from cache. @@ -164,7 +198,7 @@ def grpc_channel(self) -> aio.Channel: @property def list_voices( - self + self, ) -> Callable[ [cloud_tts.ListVoicesRequest], Awaitable[cloud_tts.ListVoicesResponse] ]: @@ -192,7 +226,7 @@ def list_voices( @property def synthesize_speech( - self + self, ) -> Callable[ [cloud_tts.SynthesizeSpeechRequest], Awaitable[cloud_tts.SynthesizeSpeechResponse], diff --git a/google/cloud/texttospeech_v1beta1/types/__init__.py b/google/cloud/texttospeech_v1beta1/types/__init__.py index 47b20ad9..cb459aa3 100644 --- a/google/cloud/texttospeech_v1beta1/types/__init__.py +++ b/google/cloud/texttospeech_v1beta1/types/__init__.py @@ -24,6 +24,7 @@ VoiceSelectionParams, AudioConfig, SynthesizeSpeechResponse, + Timepoint, ) @@ -36,4 +37,5 @@ "VoiceSelectionParams", "AudioConfig", "SynthesizeSpeechResponse", + "Timepoint", ) diff --git a/google/cloud/texttospeech_v1beta1/types/cloud_tts.py b/google/cloud/texttospeech_v1beta1/types/cloud_tts.py index 848d1559..90855911 100644 --- a/google/cloud/texttospeech_v1beta1/types/cloud_tts.py +++ b/google/cloud/texttospeech_v1beta1/types/cloud_tts.py @@ -31,6 +31,7 @@ "VoiceSelectionParams", "AudioConfig", "SynthesizeSpeechResponse", + "Timepoint", }, ) @@ -52,7 +53,9 @@ class AudioEncoding(proto.Enum): AUDIO_ENCODING_UNSPECIFIED = 0 LINEAR16 = 1 MP3 = 2 + MP3_64_KBPS = 4 OGG_OPUS = 3 + MULAW = 5 class ListVoicesRequest(proto.Message): @@ -67,8 +70,8 @@ class ListVoicesRequest(proto.Message): 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-*" + 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. """ @@ -84,7 +87,7 @@ class ListVoicesResponse(proto.Message): The list of voices. """ - voices = proto.RepeatedField(proto.MESSAGE, number=1, message="Voice") + voices = proto.RepeatedField(proto.MESSAGE, number=1, message="Voice",) class Voice(proto.Message): @@ -106,8 +109,11 @@ class Voice(proto.Message): """ language_codes = proto.RepeatedField(proto.STRING, number=1) + name = proto.Field(proto.STRING, number=2) - ssml_gender = proto.Field(proto.ENUM, number=3, enum="SsmlVoiceGender") + + ssml_gender = proto.Field(proto.ENUM, number=3, enum="SsmlVoiceGender",) + natural_sample_rate_hertz = proto.Field(proto.INT32, number=4) @@ -125,11 +131,27 @@ class SynthesizeSpeechRequest(proto.Message): audio_config (~.cloud_tts.AudioConfig): Required. The configuration of the synthesized audio. + enable_time_pointing (Sequence[~.cloud_tts.SynthesizeSpeechRequest.TimepointType]): + Whether and what timepoints should be + returned in the response. """ - input = proto.Field(proto.MESSAGE, number=1, message="SynthesisInput") - voice = proto.Field(proto.MESSAGE, number=2, message="VoiceSelectionParams") - audio_config = proto.Field(proto.MESSAGE, number=3, message="AudioConfig") + class TimepointType(proto.Enum): + r"""The type of timepoint information that is returned in the + response. + """ + TIMEPOINT_TYPE_UNSPECIFIED = 0 + SSML_MARK = 1 + + input = proto.Field(proto.MESSAGE, number=1, message="SynthesisInput",) + + voice = proto.Field(proto.MESSAGE, number=2, message="VoiceSelectionParams",) + + audio_config = proto.Field(proto.MESSAGE, number=3, message="AudioConfig",) + + enable_time_pointing = proto.RepeatedField( + proto.ENUM, number=4, enum=TimepointType, + ) class SynthesisInput(proto.Message): @@ -149,8 +171,9 @@ class SynthesisInput(proto.Message): `SSML `__. """ - text = proto.Field(proto.STRING, number=1) - ssml = proto.Field(proto.STRING, number=2) + text = proto.Field(proto.STRING, number=1, oneof="input_source") + + ssml = proto.Field(proto.STRING, number=2, oneof="input_source") class VoiceSelectionParams(proto.Message): @@ -186,8 +209,10 @@ class VoiceSelectionParams(proto.Message): """ language_code = proto.Field(proto.STRING, number=1) + name = proto.Field(proto.STRING, number=2) - ssml_gender = proto.Field(proto.ENUM, number=3, enum="SsmlVoiceGender") + + ssml_gender = proto.Field(proto.ENUM, number=3, enum="SsmlVoiceGender",) class AudioConfig(proto.Message): @@ -237,11 +262,16 @@ class AudioConfig(proto.Message): for current supported profile ids. """ - audio_encoding = proto.Field(proto.ENUM, number=1, enum="AudioEncoding") + audio_encoding = proto.Field(proto.ENUM, number=1, enum="AudioEncoding",) + speaking_rate = proto.Field(proto.DOUBLE, number=2) + pitch = proto.Field(proto.DOUBLE, number=3) + volume_gain_db = proto.Field(proto.DOUBLE, number=4) + sample_rate_hertz = proto.Field(proto.INT32, number=5) + effects_profile_id = proto.RepeatedField(proto.STRING, number=6) @@ -257,9 +287,37 @@ class SynthesizeSpeechResponse(proto.Message): include the WAV header. Note: as with all bytes fields, protobuffers use a pure binary representation, whereas JSON representations use base64. + timepoints (Sequence[~.cloud_tts.Timepoint]): + A link between a position in the original request input and + a corresponding time in the output audio. It's only + supported via ```` of SSML input. + audio_config (~.cloud_tts.AudioConfig): + The audio metadata of ``audio_content``. """ audio_content = proto.Field(proto.BYTES, number=1) + timepoints = proto.RepeatedField(proto.MESSAGE, number=2, message="Timepoint",) + + audio_config = proto.Field(proto.MESSAGE, number=4, message=AudioConfig,) + + +class Timepoint(proto.Message): + r"""This contains a mapping between a certain point in the input + text and a corresponding time in the output audio. + + Attributes: + mark_name (str): + Timepoint name as received from the client within ```` + tag. + time_seconds (float): + Time offset in seconds from the start of the + synthesized audio. + """ + + mark_name = proto.Field(proto.STRING, number=4) + + time_seconds = proto.Field(proto.DOUBLE, number=3) + __all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/noxfile.py b/noxfile.py index 0b098cdf..e0cbbab8 100644 --- a/noxfile.py +++ b/noxfile.py @@ -23,10 +23,10 @@ import nox -BLACK_VERSION = "black==19.3b0" +BLACK_VERSION = "black==19.10b0" BLACK_PATHS = ["docs", "google", "tests", "noxfile.py", "setup.py"] -DEFAULT_PYTHON_VERSION = "3.7" +DEFAULT_PYTHON_VERSION = "3.8" SYSTEM_TEST_PYTHON_VERSIONS = ["3.7"] UNIT_TEST_PYTHON_VERSIONS = ["3.6", "3.7", "3.8"] @@ -39,7 +39,9 @@ def lint(session): serious code quality issues. """ session.install("flake8", BLACK_VERSION) - session.run("black", "--check", *BLACK_PATHS) + session.run( + "black", "--check", *BLACK_PATHS, + ) session.run("flake8", "google", "tests") @@ -54,7 +56,9 @@ def blacken(session): check the state of the `gcp_ubuntu_config` we use for that Kokoro run. """ session.install(BLACK_VERSION) - session.run("black", *BLACK_PATHS) + session.run( + "black", *BLACK_PATHS, + ) @nox.session(python=DEFAULT_PYTHON_VERSION) @@ -67,6 +71,7 @@ def lint_setup_py(session): def default(session): # Install all test dependencies, then install this package in-place. session.install("asyncmock", "pytest-asyncio") + session.install("mock", "pytest", "pytest-cov") session.install("-e", ".") @@ -112,7 +117,9 @@ def system(session): # Install all test dependencies, then install this package into the # virtualenv's dist-packages. - session.install("mock", "pytest", "google-cloud-testutils") + session.install( + "mock", "pytest", "google-cloud-testutils", + ) session.install("-e", ".") # Run py.test against the system tests. diff --git a/samples/snippets/noxfile.py b/samples/snippets/noxfile.py index b23055f1..ba55d7ce 100644 --- a/samples/snippets/noxfile.py +++ b/samples/snippets/noxfile.py @@ -43,7 +43,7 @@ # to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a # build specific Cloud project. You can also use your own string # to use your own Cloud project. - 'gcloud_project_env': 'GCLOUD_PROJECT', + 'gcloud_project_env': 'GOOGLE_CLOUD_PROJECT', # 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT', # A dictionary you want to inject into your test. Don't put any @@ -72,7 +72,6 @@ def get_pytest_env_vars(): env_key = TEST_CONFIG['gcloud_project_env'] # This should error out if not set. ret['GOOGLE_CLOUD_PROJECT'] = os.environ[env_key] - ret['GCLOUD_PROJECT'] = os.environ[env_key] # Apply user supplied envs. ret.update(TEST_CONFIG['envs']) diff --git a/scripts/fixup_texttospeech_v1_keywords.py b/scripts/fixup_texttospeech_v1_keywords.py new file mode 100644 index 00000000..7c548aef --- /dev/null +++ b/scripts/fixup_texttospeech_v1_keywords.py @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import argparse +import os +import libcst as cst +import pathlib +import sys +from typing import (Any, Callable, Dict, List, Sequence, Tuple) + + +def partition( + predicate: Callable[[Any], bool], + iterator: Sequence[Any] +) -> Tuple[List[Any], List[Any]]: + """A stable, out-of-place partition.""" + results = ([], []) + + for i in iterator: + results[int(predicate(i))].append(i) + + # Returns trueList, falseList + return results[1], results[0] + + +class texttospeechCallTransformer(cst.CSTTransformer): + CTRL_PARAMS: Tuple[str] = ('retry', 'timeout', 'metadata') + METHOD_TO_PARAMS: Dict[str, Tuple[str]] = { + 'list_voices': ('language_code', ), + 'synthesize_speech': ('input', 'voice', 'audio_config', ), + + } + + def leave_Call(self, original: cst.Call, updated: cst.Call) -> cst.CSTNode: + try: + key = original.func.attr.value + kword_params = self.METHOD_TO_PARAMS[key] + except (AttributeError, KeyError): + # Either not a method from the API or too convoluted to be sure. + return updated + + # If the existing code is valid, keyword args come after positional args. + # Therefore, all positional args must map to the first parameters. + args, kwargs = partition(lambda a: not bool(a.keyword), updated.args) + if any(k.keyword.value == "request" for k in kwargs): + # We've already fixed this file, don't fix it again. + return updated + + kwargs, ctrl_kwargs = partition( + lambda a: not a.keyword.value in self.CTRL_PARAMS, + kwargs + ) + + args, ctrl_args = args[:len(kword_params)], args[len(kword_params):] + ctrl_kwargs.extend(cst.Arg(value=a.value, keyword=cst.Name(value=ctrl)) + for a, ctrl in zip(ctrl_args, self.CTRL_PARAMS)) + + request_arg = cst.Arg( + value=cst.Dict([ + cst.DictElement( + cst.SimpleString("'{}'".format(name)), + cst.Element(value=arg.value) + ) + # Note: the args + kwargs looks silly, but keep in mind that + # the control parameters had to be stripped out, and that + # those could have been passed positionally or by keyword. + for name, arg in zip(kword_params, args + kwargs)]), + keyword=cst.Name("request") + ) + + return updated.with_changes( + args=[request_arg] + ctrl_kwargs + ) + + +def fix_files( + in_dir: pathlib.Path, + out_dir: pathlib.Path, + *, + transformer=texttospeechCallTransformer(), +): + """Duplicate the input dir to the output dir, fixing file method calls. + + Preconditions: + * in_dir is a real directory + * out_dir is a real, empty directory + """ + pyfile_gen = ( + pathlib.Path(os.path.join(root, f)) + for root, _, files in os.walk(in_dir) + for f in files if os.path.splitext(f)[1] == ".py" + ) + + for fpath in pyfile_gen: + with open(fpath, 'r') as f: + src = f.read() + + # Parse the code and insert method call fixes. + tree = cst.parse_module(src) + updated = tree.visit(transformer) + + # Create the path and directory structure for the new file. + updated_path = out_dir.joinpath(fpath.relative_to(in_dir)) + updated_path.parent.mkdir(parents=True, exist_ok=True) + + # Generate the updated source file at the corresponding path. + with open(updated_path, 'w') as f: + f.write(updated.code) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description="""Fix up source that uses the texttospeech client library. + +The existing sources are NOT overwritten but are copied to output_dir with changes made. + +Note: This tool operates at a best-effort level at converting positional + parameters in client method calls to keyword based parameters. + Cases where it WILL FAIL include + A) * or ** expansion in a method call. + B) Calls via function or method alias (includes free function calls) + C) Indirect or dispatched calls (e.g. the method is looked up dynamically) + + These all constitute false negatives. The tool will also detect false + positives when an API method shares a name with another method. +""") + parser.add_argument( + '-d', + '--input-directory', + required=True, + dest='input_dir', + help='the input directory to walk for python files to fix up', + ) + parser.add_argument( + '-o', + '--output-directory', + required=True, + dest='output_dir', + help='the directory to output files fixed via un-flattening', + ) + args = parser.parse_args() + input_dir = pathlib.Path(args.input_dir) + output_dir = pathlib.Path(args.output_dir) + if not input_dir.is_dir(): + print( + f"input directory '{input_dir}' does not exist or is not a directory", + file=sys.stderr, + ) + sys.exit(-1) + + if not output_dir.is_dir(): + print( + f"output directory '{output_dir}' does not exist or is not a directory", + file=sys.stderr, + ) + sys.exit(-1) + + if os.listdir(output_dir): + print( + f"output directory '{output_dir}' is not empty", + file=sys.stderr, + ) + sys.exit(-1) + + fix_files(input_dir, output_dir) diff --git a/scripts/fixup_texttospeech_v1beta1_keywords.py b/scripts/fixup_texttospeech_v1beta1_keywords.py new file mode 100644 index 00000000..b11f7d87 --- /dev/null +++ b/scripts/fixup_texttospeech_v1beta1_keywords.py @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import argparse +import os +import libcst as cst +import pathlib +import sys +from typing import (Any, Callable, Dict, List, Sequence, Tuple) + + +def partition( + predicate: Callable[[Any], bool], + iterator: Sequence[Any] +) -> Tuple[List[Any], List[Any]]: + """A stable, out-of-place partition.""" + results = ([], []) + + for i in iterator: + results[int(predicate(i))].append(i) + + # Returns trueList, falseList + return results[1], results[0] + + +class texttospeechCallTransformer(cst.CSTTransformer): + CTRL_PARAMS: Tuple[str] = ('retry', 'timeout', 'metadata') + METHOD_TO_PARAMS: Dict[str, Tuple[str]] = { + 'list_voices': ('language_code', ), + 'synthesize_speech': ('input', 'voice', 'audio_config', 'enable_time_pointing', ), + + } + + def leave_Call(self, original: cst.Call, updated: cst.Call) -> cst.CSTNode: + try: + key = original.func.attr.value + kword_params = self.METHOD_TO_PARAMS[key] + except (AttributeError, KeyError): + # Either not a method from the API or too convoluted to be sure. + return updated + + # If the existing code is valid, keyword args come after positional args. + # Therefore, all positional args must map to the first parameters. + args, kwargs = partition(lambda a: not bool(a.keyword), updated.args) + if any(k.keyword.value == "request" for k in kwargs): + # We've already fixed this file, don't fix it again. + return updated + + kwargs, ctrl_kwargs = partition( + lambda a: not a.keyword.value in self.CTRL_PARAMS, + kwargs + ) + + args, ctrl_args = args[:len(kword_params)], args[len(kword_params):] + ctrl_kwargs.extend(cst.Arg(value=a.value, keyword=cst.Name(value=ctrl)) + for a, ctrl in zip(ctrl_args, self.CTRL_PARAMS)) + + request_arg = cst.Arg( + value=cst.Dict([ + cst.DictElement( + cst.SimpleString("'{}'".format(name)), + cst.Element(value=arg.value) + ) + # Note: the args + kwargs looks silly, but keep in mind that + # the control parameters had to be stripped out, and that + # those could have been passed positionally or by keyword. + for name, arg in zip(kword_params, args + kwargs)]), + keyword=cst.Name("request") + ) + + return updated.with_changes( + args=[request_arg] + ctrl_kwargs + ) + + +def fix_files( + in_dir: pathlib.Path, + out_dir: pathlib.Path, + *, + transformer=texttospeechCallTransformer(), +): + """Duplicate the input dir to the output dir, fixing file method calls. + + Preconditions: + * in_dir is a real directory + * out_dir is a real, empty directory + """ + pyfile_gen = ( + pathlib.Path(os.path.join(root, f)) + for root, _, files in os.walk(in_dir) + for f in files if os.path.splitext(f)[1] == ".py" + ) + + for fpath in pyfile_gen: + with open(fpath, 'r') as f: + src = f.read() + + # Parse the code and insert method call fixes. + tree = cst.parse_module(src) + updated = tree.visit(transformer) + + # Create the path and directory structure for the new file. + updated_path = out_dir.joinpath(fpath.relative_to(in_dir)) + updated_path.parent.mkdir(parents=True, exist_ok=True) + + # Generate the updated source file at the corresponding path. + with open(updated_path, 'w') as f: + f.write(updated.code) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description="""Fix up source that uses the texttospeech client library. + +The existing sources are NOT overwritten but are copied to output_dir with changes made. + +Note: This tool operates at a best-effort level at converting positional + parameters in client method calls to keyword based parameters. + Cases where it WILL FAIL include + A) * or ** expansion in a method call. + B) Calls via function or method alias (includes free function calls) + C) Indirect or dispatched calls (e.g. the method is looked up dynamically) + + These all constitute false negatives. The tool will also detect false + positives when an API method shares a name with another method. +""") + parser.add_argument( + '-d', + '--input-directory', + required=True, + dest='input_dir', + help='the input directory to walk for python files to fix up', + ) + parser.add_argument( + '-o', + '--output-directory', + required=True, + dest='output_dir', + help='the directory to output files fixed via un-flattening', + ) + args = parser.parse_args() + input_dir = pathlib.Path(args.input_dir) + output_dir = pathlib.Path(args.output_dir) + if not input_dir.is_dir(): + print( + f"input directory '{input_dir}' does not exist or is not a directory", + file=sys.stderr, + ) + sys.exit(-1) + + if not output_dir.is_dir(): + print( + f"output directory '{output_dir}' does not exist or is not a directory", + file=sys.stderr, + ) + sys.exit(-1) + + if os.listdir(output_dir): + print( + f"output directory '{output_dir}' is not empty", + file=sys.stderr, + ) + sys.exit(-1) + + fix_files(input_dir, output_dir) diff --git a/setup.py b/setup.py index c37d047b..17a63631 100644 --- a/setup.py +++ b/setup.py @@ -42,8 +42,8 @@ # 'Development Status :: 5 - Production/Stable' release_status = "Development Status :: 5 - Production/Stable" dependencies = [ - "google-api-core[grpc] >= 1.17.2, < 2.0.0dev", - "proto-plus >= 0.4.0", + "google-api-core[grpc] >= 1.22.0, < 2.0.0dev", + "proto-plus >= 1.4.0", "libcst >= 0.2.5", ] extras = {} diff --git a/synth.metadata b/synth.metadata index 793637ac..82726e67 100644 --- a/synth.metadata +++ b/synth.metadata @@ -3,30 +3,30 @@ { "git": { "name": ".", - "remote": "https://github.com/googleapis/python-texttospeech.git", - "sha": "08e6de92448d546f8aae466f77951d30704fba2d" + "remote": "git@github.com:tmatsuo/python-texttospeech.git", + "sha": "f69dc7feaa9933d4b0a734377270122e0843ffbf" } }, { "git": { "name": "googleapis", "remote": "https://github.com/googleapis/googleapis.git", - "sha": "55094be6405640329ddc93730962b9f7e68a0fc1", - "internalRef": "314438331" + "sha": "4d52dfb72078000b13de923c1dadec19f3a64ad1", + "internalRef": "324098852" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "cd522c3b4dde821766d95c80ae5aeb43d7a41170" + "sha": "39b527a39f5cd56d4882b3874fc08eed4756cebe" } }, { "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "cd522c3b4dde821766d95c80ae5aeb43d7a41170" + "sha": "39b527a39f5cd56d4882b3874fc08eed4756cebe" } } ], diff --git a/tests/unit/gapic/texttospeech_v1/__init__.py b/tests/unit/gapic/texttospeech_v1/__init__.py index e69de29b..8b137891 100644 --- a/tests/unit/gapic/texttospeech_v1/__init__.py +++ b/tests/unit/gapic/texttospeech_v1/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/unit/gapic/texttospeech_v1/test_text_to_speech.py b/tests/unit/gapic/texttospeech_v1/test_text_to_speech.py index e05ee7b5..a701fa85 100644 --- a/tests/unit/gapic/texttospeech_v1/test_text_to_speech.py +++ b/tests/unit/gapic/texttospeech_v1/test_text_to_speech.py @@ -22,9 +22,11 @@ from grpc.experimental import aio import math import pytest +from proto.marshal.rules.dates import DurationRule, TimestampRule from google import auth from google.api_core import client_options +from google.api_core import exceptions from google.api_core import grpc_helpers from google.api_core import grpc_helpers_async from google.auth import credentials @@ -40,6 +42,17 @@ def client_cert_source_callback(): return b"cert bytes", b"key bytes" +# If default endpoint is localhost, then default mtls endpoint will be the same. +# This method modifies the default endpoint so the client can produce a different +# mtls endpoint for endpoint testing purposes. +def modify_default_endpoint(client): + return ( + "foo.googleapis.com" + if ("localhost" in client.DEFAULT_ENDPOINT) + else client.DEFAULT_ENDPOINT + ) + + def test__get_default_mtls_endpoint(): api_endpoint = "example.googleapis.com" api_mtls_endpoint = "example.mtls.googleapis.com" @@ -101,6 +114,14 @@ def test_text_to_speech_client_get_transport_class(): ), ], ) +@mock.patch.object( + TextToSpeechClient, "DEFAULT_ENDPOINT", modify_default_endpoint(TextToSpeechClient) +) +@mock.patch.object( + TextToSpeechAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TextToSpeechAsyncClient), +) def test_text_to_speech_client_client_options( client_class, transport_class, transport_name ): @@ -121,95 +142,186 @@ def test_text_to_speech_client_client_options( patched.return_value = None client = client_class(client_options=options) patched.assert_called_once_with( - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, credentials=None, + credentials_file=None, host="squid.clam.whelk", + scopes=None, + api_mtls_endpoint="squid.clam.whelk", + client_cert_source=None, + quota_project_id=None, ) # 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, - ) + with mock.patch.dict(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( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + quota_project_id=None, + ) # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS 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, - ): + with mock.patch.dict(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, + credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, + scopes=None, + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=None, + quota_project_id=None, ) # 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, - ): + # "auto", and client_cert_source is provided. + with mock.patch.dict(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 = client_class(client_options=options) patched.assert_called_once_with( - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, credentials=None, - host=client.DEFAULT_ENDPOINT, + credentials_file=None, + host=client.DEFAULT_MTLS_ENDPOINT, + scopes=None, + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=client_cert_source_callback, + quota_project_id=None, ) + # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is + # "auto", and default_client_cert_source is provided. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + with mock.patch.object(transport_class, "__init__") as patched: + 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( + credentials=None, + credentials_file=None, + host=client.DEFAULT_MTLS_ENDPOINT, + scopes=None, + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=None, + quota_project_id=None, + ) + + # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is + # "auto", but client_cert_source and default_client_cert_source are None. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + 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( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + quota_project_id=None, + ) + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has # unsupported value. - os.environ["GOOGLE_API_USE_MTLS"] = "Unsupported" - with pytest.raises(MutualTLSChannelError): - client = client_class() + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + quota_project_id="octopus", + ) + + +@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_scopes( + client_class, transport_class, transport_name +): + # Check the case scopes are provided. + options = client_options.ClientOptions(scopes=["1", "2"],) + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=["1", "2"], + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + quota_project_id=None, + ) - del os.environ["GOOGLE_API_USE_MTLS"] + +@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_credentials_file( + client_class, transport_class, transport_name +): + # Check the case credentials file is provided. + options = client_options.ClientOptions(credentials_file="credentials.json") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file="credentials.json", + host=client.DEFAULT_ENDPOINT, + scopes=None, + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + quota_project_id=None, + ) def test_text_to_speech_client_client_options_from_dict(): @@ -219,21 +331,24 @@ def test_text_to_speech_client_client_options_from_dict(): 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, + credentials_file=None, host="squid.clam.whelk", + scopes=None, + api_mtls_endpoint="squid.clam.whelk", + client_cert_source=None, + quota_project_id=None, ) -def test_list_voices(transport: str = "grpc"): +def test_list_voices(transport: str = "grpc", request_type=cloud_tts.ListVoicesRequest): client = TextToSpeechClient( - credentials=credentials.AnonymousCredentials(), transport=transport + credentials=credentials.AnonymousCredentials(), transport=transport, ) # Everything is optional in proto3 as far as the runtime is concerned, # and we are mocking out the actual API, so just send an empty request. - request = cloud_tts.ListVoicesRequest() + request = request_type() # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object(type(client._transport.list_voices), "__call__") as call: @@ -246,16 +361,20 @@ def test_list_voices(transport: str = "grpc"): assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0] == request + assert args[0] == cloud_tts.ListVoicesRequest() # Establish that the response is the type that we expect. assert isinstance(response, cloud_tts.ListVoicesResponse) +def test_list_voices_from_dict(): + test_list_voices(request_type=dict) + + @pytest.mark.asyncio async def test_list_voices_async(transport: str = "grpc_asyncio"): client = TextToSpeechAsyncClient( - credentials=credentials.AnonymousCredentials(), transport=transport + credentials=credentials.AnonymousCredentials(), transport=transport, ) # Everything is optional in proto3 as far as the runtime is concerned, @@ -284,7 +403,7 @@ async def test_list_voices_async(transport: str = "grpc_asyncio"): def test_list_voices_flattened(): - client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) + 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: @@ -293,29 +412,30 @@ def test_list_voices_flattened(): # 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") + 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()) + 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" + cloud_tts.ListVoicesRequest(), language_code="language_code_value", ) @pytest.mark.asyncio async def test_list_voices_flattened_async(): - client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials()) + client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials(),) # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( @@ -329,35 +449,38 @@ async def test_list_voices_flattened_async(): ) # 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") + 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()) + 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" + cloud_tts.ListVoicesRequest(), language_code="language_code_value", ) -def test_synthesize_speech(transport: str = "grpc"): +def test_synthesize_speech( + transport: str = "grpc", request_type=cloud_tts.SynthesizeSpeechRequest +): client = TextToSpeechClient( - credentials=credentials.AnonymousCredentials(), transport=transport + credentials=credentials.AnonymousCredentials(), transport=transport, ) # Everything is optional in proto3 as far as the runtime is concerned, # and we are mocking out the actual API, so just send an empty request. - request = cloud_tts.SynthesizeSpeechRequest() + request = request_type() # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( @@ -365,7 +488,7 @@ def test_synthesize_speech(transport: str = "grpc"): ) as call: # Designate an appropriate return value for the call. call.return_value = cloud_tts.SynthesizeSpeechResponse( - audio_content=b"audio_content_blob" + audio_content=b"audio_content_blob", ) response = client.synthesize_speech(request) @@ -374,17 +497,22 @@ def test_synthesize_speech(transport: str = "grpc"): assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0] == request + assert args[0] == cloud_tts.SynthesizeSpeechRequest() # 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_from_dict(): + test_synthesize_speech(request_type=dict) + + @pytest.mark.asyncio async def test_synthesize_speech_async(transport: str = "grpc_asyncio"): client = TextToSpeechAsyncClient( - credentials=credentials.AnonymousCredentials(), transport=transport + credentials=credentials.AnonymousCredentials(), transport=transport, ) # Everything is optional in proto3 as far as the runtime is concerned, @@ -397,7 +525,7 @@ async def test_synthesize_speech_async(transport: str = "grpc_asyncio"): ) 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") + cloud_tts.SynthesizeSpeechResponse(audio_content=b"audio_content_blob",) ) response = await client.synthesize_speech(request) @@ -410,11 +538,12 @@ async def test_synthesize_speech_async(transport: str = "grpc_asyncio"): # 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()) + client = TextToSpeechClient(credentials=credentials.AnonymousCredentials(),) # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( @@ -437,17 +566,20 @@ def test_synthesize_speech_flattened(): # 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()) + client = TextToSpeechClient(credentials=credentials.AnonymousCredentials(),) # Attempting to call a method with both a request object and flattened # fields is an error. @@ -464,7 +596,7 @@ def test_synthesize_speech_flattened_error(): @pytest.mark.asyncio async def test_synthesize_speech_flattened_async(): - client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials()) + client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials(),) # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( @@ -490,10 +622,13 @@ async def test_synthesize_speech_flattened_async(): # 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 ) @@ -501,7 +636,7 @@ async def test_synthesize_speech_flattened_async(): @pytest.mark.asyncio async def test_synthesize_speech_flattened_error_async(): - client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials()) + client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials(),) # Attempting to call a method with both a request object and flattened # fields is an error. @@ -519,18 +654,37 @@ async def test_synthesize_speech_flattened_error_async(): def test_credentials_transport_error(): # It is an error to provide credentials and a transport instance. transport = transports.TextToSpeechGrpcTransport( - credentials=credentials.AnonymousCredentials() + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TextToSpeechClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.TextToSpeechGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TextToSpeechClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.TextToSpeechGrpcTransport( + credentials=credentials.AnonymousCredentials(), ) with pytest.raises(ValueError): client = TextToSpeechClient( - credentials=credentials.AnonymousCredentials(), transport=transport + client_options={"scopes": ["1", "2"]}, transport=transport, ) def test_transport_instance(): # A client may be instantiated with a custom transport instance. transport = transports.TextToSpeechGrpcTransport( - credentials=credentials.AnonymousCredentials() + credentials=credentials.AnonymousCredentials(), ) client = TextToSpeechClient(transport=transport) assert client._transport is transport @@ -539,13 +693,13 @@ def test_transport_instance(): def test_transport_get_channel(): # A client may be instantiated with a custom transport instance. transport = transports.TextToSpeechGrpcTransport( - credentials=credentials.AnonymousCredentials() + credentials=credentials.AnonymousCredentials(), ) channel = transport.grpc_channel assert channel transport = transports.TextToSpeechGrpcAsyncIOTransport( - credentials=credentials.AnonymousCredentials() + credentials=credentials.AnonymousCredentials(), ) channel = transport.grpc_channel assert channel @@ -553,31 +707,67 @@ def test_transport_get_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) + client = TextToSpeechClient(credentials=credentials.AnonymousCredentials(),) + assert isinstance(client._transport, transports.TextToSpeechGrpcTransport,) + + +def test_text_to_speech_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(exceptions.DuplicateCredentialArgs): + transport = transports.TextToSpeechTransport( + credentials=credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) def test_text_to_speech_base_transport(): # Instantiate the base transport. - transport = transports.TextToSpeechTransport( - credentials=credentials.AnonymousCredentials() - ) + with mock.patch( + "google.cloud.texttospeech_v1.services.text_to_speech.transports.TextToSpeechTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.TextToSpeechTransport( + credentials=credentials.AnonymousCredentials(), + ) # Every method on the transport should just blindly # raise NotImplementedError. - methods = ("list_voices", "synthesize_speech") + methods = ( + "list_voices", + "synthesize_speech", + ) for method in methods: with pytest.raises(NotImplementedError): getattr(transport, method)(request=object()) +def test_text_to_speech_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + auth, "load_credentials_from_file" + ) as load_creds, mock.patch( + "google.cloud.texttospeech_v1.services.text_to_speech.transports.TextToSpeechTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.TextToSpeechTransport( + credentials_file="credentials.json", quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=("https://www.googleapis.com/auth/cloud-platform",), + quota_project_id="octopus", + ) + + def test_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",) + scopes=("https://www.googleapis.com/auth/cloud-platform",), + quota_project_id=None, ) @@ -586,9 +776,12 @@ def test_text_to_speech_transport_auth_adc(): # ADC credentials. with mock.patch.object(auth, "default") as adc: adc.return_value = (credentials.AnonymousCredentials(), None) - transports.TextToSpeechGrpcTransport(host="squid.clam.whelk") + transports.TextToSpeechGrpcTransport( + host="squid.clam.whelk", quota_project_id="octopus" + ) adc.assert_called_once_with( - scopes=("https://www.googleapis.com/auth/cloud-platform",) + scopes=("https://www.googleapis.com/auth/cloud-platform",), + quota_project_id="octopus", ) @@ -673,8 +866,10 @@ def test_text_to_speech_grpc_transport_channel_mtls_with_client_cert_source( grpc_create_channel.assert_called_once_with( "mtls.squid.clam.whelk:443", credentials=mock_cred, - ssl_credentials=mock_ssl_cred, + credentials_file=None, scopes=("https://www.googleapis.com/auth/cloud-platform",), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, ) assert transport.grpc_channel == mock_grpc_channel @@ -706,8 +901,10 @@ def test_text_to_speech_grpc_asyncio_transport_channel_mtls_with_client_cert_sou grpc_create_channel.assert_called_once_with( "mtls.squid.clam.whelk:443", credentials=mock_cred, - ssl_credentials=mock_ssl_cred, + credentials_file=None, scopes=("https://www.googleapis.com/auth/cloud-platform",), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, ) assert transport.grpc_channel == mock_grpc_channel @@ -741,8 +938,10 @@ def test_text_to_speech_grpc_transport_channel_mtls_with_adc( grpc_create_channel.assert_called_once_with( "mtls.squid.clam.whelk:443", credentials=mock_cred, - ssl_credentials=mock_ssl_cred, + credentials_file=None, scopes=("https://www.googleapis.com/auth/cloud-platform",), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, ) assert transport.grpc_channel == mock_grpc_channel @@ -776,7 +975,9 @@ def test_text_to_speech_grpc_asyncio_transport_channel_mtls_with_adc( grpc_create_channel.assert_called_once_with( "mtls.squid.clam.whelk:443", credentials=mock_cred, - ssl_credentials=mock_ssl_cred, + credentials_file=None, scopes=("https://www.googleapis.com/auth/cloud-platform",), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, ) assert transport.grpc_channel == mock_grpc_channel diff --git a/tests/unit/gapic/texttospeech_v1beta1/__init__.py b/tests/unit/gapic/texttospeech_v1beta1/__init__.py index e69de29b..8b137891 100644 --- a/tests/unit/gapic/texttospeech_v1beta1/__init__.py +++ b/tests/unit/gapic/texttospeech_v1beta1/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/unit/gapic/texttospeech_v1beta1/test_text_to_speech.py b/tests/unit/gapic/texttospeech_v1beta1/test_text_to_speech.py index 6e81f325..d125ffa5 100644 --- a/tests/unit/gapic/texttospeech_v1beta1/test_text_to_speech.py +++ b/tests/unit/gapic/texttospeech_v1beta1/test_text_to_speech.py @@ -22,9 +22,11 @@ from grpc.experimental import aio import math import pytest +from proto.marshal.rules.dates import DurationRule, TimestampRule from google import auth from google.api_core import client_options +from google.api_core import exceptions from google.api_core import grpc_helpers from google.api_core import grpc_helpers_async from google.auth import credentials @@ -42,6 +44,17 @@ def client_cert_source_callback(): return b"cert bytes", b"key bytes" +# If default endpoint is localhost, then default mtls endpoint will be the same. +# This method modifies the default endpoint so the client can produce a different +# mtls endpoint for endpoint testing purposes. +def modify_default_endpoint(client): + return ( + "foo.googleapis.com" + if ("localhost" in client.DEFAULT_ENDPOINT) + else client.DEFAULT_ENDPOINT + ) + + def test__get_default_mtls_endpoint(): api_endpoint = "example.googleapis.com" api_mtls_endpoint = "example.mtls.googleapis.com" @@ -103,6 +116,14 @@ def test_text_to_speech_client_get_transport_class(): ), ], ) +@mock.patch.object( + TextToSpeechClient, "DEFAULT_ENDPOINT", modify_default_endpoint(TextToSpeechClient) +) +@mock.patch.object( + TextToSpeechAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TextToSpeechAsyncClient), +) def test_text_to_speech_client_client_options( client_class, transport_class, transport_name ): @@ -123,95 +144,186 @@ def test_text_to_speech_client_client_options( patched.return_value = None client = client_class(client_options=options) patched.assert_called_once_with( - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, credentials=None, + credentials_file=None, host="squid.clam.whelk", + scopes=None, + api_mtls_endpoint="squid.clam.whelk", + client_cert_source=None, + quota_project_id=None, ) # 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, - ) + with mock.patch.dict(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( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + quota_project_id=None, + ) # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS 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, - ): + with mock.patch.dict(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, + credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, + scopes=None, + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=None, + quota_project_id=None, ) # 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, - ): + # "auto", and client_cert_source is provided. + with mock.patch.dict(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 = client_class(client_options=options) patched.assert_called_once_with( - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, credentials=None, - host=client.DEFAULT_ENDPOINT, + credentials_file=None, + host=client.DEFAULT_MTLS_ENDPOINT, + scopes=None, + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=client_cert_source_callback, + quota_project_id=None, ) + # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is + # "auto", and default_client_cert_source is provided. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + with mock.patch.object(transport_class, "__init__") as patched: + 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( + credentials=None, + credentials_file=None, + host=client.DEFAULT_MTLS_ENDPOINT, + scopes=None, + api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, + client_cert_source=None, + quota_project_id=None, + ) + + # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is + # "auto", but client_cert_source and default_client_cert_source are None. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + 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( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + quota_project_id=None, + ) + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has # unsupported value. - os.environ["GOOGLE_API_USE_MTLS"] = "Unsupported" - with pytest.raises(MutualTLSChannelError): - client = client_class() + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() - del os.environ["GOOGLE_API_USE_MTLS"] + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + quota_project_id="octopus", + ) + + +@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_scopes( + client_class, transport_class, transport_name +): + # Check the case scopes are provided. + options = client_options.ClientOptions(scopes=["1", "2"],) + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=["1", "2"], + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + quota_project_id=None, + ) + + +@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_credentials_file( + client_class, transport_class, transport_name +): + # Check the case credentials file is provided. + options = client_options.ClientOptions(credentials_file="credentials.json") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file="credentials.json", + host=client.DEFAULT_ENDPOINT, + scopes=None, + api_mtls_endpoint=client.DEFAULT_ENDPOINT, + client_cert_source=None, + quota_project_id=None, + ) def test_text_to_speech_client_client_options_from_dict(): @@ -221,21 +333,24 @@ def test_text_to_speech_client_client_options_from_dict(): 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, + credentials_file=None, host="squid.clam.whelk", + scopes=None, + api_mtls_endpoint="squid.clam.whelk", + client_cert_source=None, + quota_project_id=None, ) -def test_list_voices(transport: str = "grpc"): +def test_list_voices(transport: str = "grpc", request_type=cloud_tts.ListVoicesRequest): client = TextToSpeechClient( - credentials=credentials.AnonymousCredentials(), transport=transport + credentials=credentials.AnonymousCredentials(), transport=transport, ) # Everything is optional in proto3 as far as the runtime is concerned, # and we are mocking out the actual API, so just send an empty request. - request = cloud_tts.ListVoicesRequest() + request = request_type() # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object(type(client._transport.list_voices), "__call__") as call: @@ -248,16 +363,20 @@ def test_list_voices(transport: str = "grpc"): assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0] == request + assert args[0] == cloud_tts.ListVoicesRequest() # Establish that the response is the type that we expect. assert isinstance(response, cloud_tts.ListVoicesResponse) +def test_list_voices_from_dict(): + test_list_voices(request_type=dict) + + @pytest.mark.asyncio async def test_list_voices_async(transport: str = "grpc_asyncio"): client = TextToSpeechAsyncClient( - credentials=credentials.AnonymousCredentials(), transport=transport + credentials=credentials.AnonymousCredentials(), transport=transport, ) # Everything is optional in proto3 as far as the runtime is concerned, @@ -286,7 +405,7 @@ async def test_list_voices_async(transport: str = "grpc_asyncio"): def test_list_voices_flattened(): - client = TextToSpeechClient(credentials=credentials.AnonymousCredentials()) + 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: @@ -295,29 +414,30 @@ def test_list_voices_flattened(): # 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") + 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()) + 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" + cloud_tts.ListVoicesRequest(), language_code="language_code_value", ) @pytest.mark.asyncio async def test_list_voices_flattened_async(): - client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials()) + client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials(),) # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( @@ -331,35 +451,38 @@ async def test_list_voices_flattened_async(): ) # 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") + 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()) + 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" + cloud_tts.ListVoicesRequest(), language_code="language_code_value", ) -def test_synthesize_speech(transport: str = "grpc"): +def test_synthesize_speech( + transport: str = "grpc", request_type=cloud_tts.SynthesizeSpeechRequest +): client = TextToSpeechClient( - credentials=credentials.AnonymousCredentials(), transport=transport + credentials=credentials.AnonymousCredentials(), transport=transport, ) # Everything is optional in proto3 as far as the runtime is concerned, # and we are mocking out the actual API, so just send an empty request. - request = cloud_tts.SynthesizeSpeechRequest() + request = request_type() # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( @@ -367,7 +490,7 @@ def test_synthesize_speech(transport: str = "grpc"): ) as call: # Designate an appropriate return value for the call. call.return_value = cloud_tts.SynthesizeSpeechResponse( - audio_content=b"audio_content_blob" + audio_content=b"audio_content_blob", ) response = client.synthesize_speech(request) @@ -376,17 +499,22 @@ def test_synthesize_speech(transport: str = "grpc"): assert len(call.mock_calls) == 1 _, args, _ = call.mock_calls[0] - assert args[0] == request + assert args[0] == cloud_tts.SynthesizeSpeechRequest() # 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_from_dict(): + test_synthesize_speech(request_type=dict) + + @pytest.mark.asyncio async def test_synthesize_speech_async(transport: str = "grpc_asyncio"): client = TextToSpeechAsyncClient( - credentials=credentials.AnonymousCredentials(), transport=transport + credentials=credentials.AnonymousCredentials(), transport=transport, ) # Everything is optional in proto3 as far as the runtime is concerned, @@ -399,7 +527,7 @@ async def test_synthesize_speech_async(transport: str = "grpc_asyncio"): ) 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") + cloud_tts.SynthesizeSpeechResponse(audio_content=b"audio_content_blob",) ) response = await client.synthesize_speech(request) @@ -412,11 +540,12 @@ async def test_synthesize_speech_async(transport: str = "grpc_asyncio"): # 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()) + client = TextToSpeechClient(credentials=credentials.AnonymousCredentials(),) # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( @@ -439,17 +568,20 @@ def test_synthesize_speech_flattened(): # 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()) + client = TextToSpeechClient(credentials=credentials.AnonymousCredentials(),) # Attempting to call a method with both a request object and flattened # fields is an error. @@ -466,7 +598,7 @@ def test_synthesize_speech_flattened_error(): @pytest.mark.asyncio async def test_synthesize_speech_flattened_async(): - client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials()) + client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials(),) # Mock the actual call within the gRPC stub, and fake the request. with mock.patch.object( @@ -492,10 +624,13 @@ async def test_synthesize_speech_flattened_async(): # 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 ) @@ -503,7 +638,7 @@ async def test_synthesize_speech_flattened_async(): @pytest.mark.asyncio async def test_synthesize_speech_flattened_error_async(): - client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials()) + client = TextToSpeechAsyncClient(credentials=credentials.AnonymousCredentials(),) # Attempting to call a method with both a request object and flattened # fields is an error. @@ -521,18 +656,37 @@ async def test_synthesize_speech_flattened_error_async(): def test_credentials_transport_error(): # It is an error to provide credentials and a transport instance. transport = transports.TextToSpeechGrpcTransport( - credentials=credentials.AnonymousCredentials() + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TextToSpeechClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.TextToSpeechGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TextToSpeechClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.TextToSpeechGrpcTransport( + credentials=credentials.AnonymousCredentials(), ) with pytest.raises(ValueError): client = TextToSpeechClient( - credentials=credentials.AnonymousCredentials(), transport=transport + client_options={"scopes": ["1", "2"]}, transport=transport, ) def test_transport_instance(): # A client may be instantiated with a custom transport instance. transport = transports.TextToSpeechGrpcTransport( - credentials=credentials.AnonymousCredentials() + credentials=credentials.AnonymousCredentials(), ) client = TextToSpeechClient(transport=transport) assert client._transport is transport @@ -541,13 +695,13 @@ def test_transport_instance(): def test_transport_get_channel(): # A client may be instantiated with a custom transport instance. transport = transports.TextToSpeechGrpcTransport( - credentials=credentials.AnonymousCredentials() + credentials=credentials.AnonymousCredentials(), ) channel = transport.grpc_channel assert channel transport = transports.TextToSpeechGrpcAsyncIOTransport( - credentials=credentials.AnonymousCredentials() + credentials=credentials.AnonymousCredentials(), ) channel = transport.grpc_channel assert channel @@ -555,31 +709,67 @@ def test_transport_get_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) + client = TextToSpeechClient(credentials=credentials.AnonymousCredentials(),) + assert isinstance(client._transport, transports.TextToSpeechGrpcTransport,) + + +def test_text_to_speech_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(exceptions.DuplicateCredentialArgs): + transport = transports.TextToSpeechTransport( + credentials=credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) def test_text_to_speech_base_transport(): # Instantiate the base transport. - transport = transports.TextToSpeechTransport( - credentials=credentials.AnonymousCredentials() - ) + with mock.patch( + "google.cloud.texttospeech_v1beta1.services.text_to_speech.transports.TextToSpeechTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.TextToSpeechTransport( + credentials=credentials.AnonymousCredentials(), + ) # Every method on the transport should just blindly # raise NotImplementedError. - methods = ("list_voices", "synthesize_speech") + methods = ( + "list_voices", + "synthesize_speech", + ) for method in methods: with pytest.raises(NotImplementedError): getattr(transport, method)(request=object()) +def test_text_to_speech_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + auth, "load_credentials_from_file" + ) as load_creds, mock.patch( + "google.cloud.texttospeech_v1beta1.services.text_to_speech.transports.TextToSpeechTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.TextToSpeechTransport( + credentials_file="credentials.json", quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=("https://www.googleapis.com/auth/cloud-platform",), + quota_project_id="octopus", + ) + + def test_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",) + scopes=("https://www.googleapis.com/auth/cloud-platform",), + quota_project_id=None, ) @@ -588,9 +778,12 @@ def test_text_to_speech_transport_auth_adc(): # ADC credentials. with mock.patch.object(auth, "default") as adc: adc.return_value = (credentials.AnonymousCredentials(), None) - transports.TextToSpeechGrpcTransport(host="squid.clam.whelk") + transports.TextToSpeechGrpcTransport( + host="squid.clam.whelk", quota_project_id="octopus" + ) adc.assert_called_once_with( - scopes=("https://www.googleapis.com/auth/cloud-platform",) + scopes=("https://www.googleapis.com/auth/cloud-platform",), + quota_project_id="octopus", ) @@ -675,8 +868,10 @@ def test_text_to_speech_grpc_transport_channel_mtls_with_client_cert_source( grpc_create_channel.assert_called_once_with( "mtls.squid.clam.whelk:443", credentials=mock_cred, - ssl_credentials=mock_ssl_cred, + credentials_file=None, scopes=("https://www.googleapis.com/auth/cloud-platform",), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, ) assert transport.grpc_channel == mock_grpc_channel @@ -708,8 +903,10 @@ def test_text_to_speech_grpc_asyncio_transport_channel_mtls_with_client_cert_sou grpc_create_channel.assert_called_once_with( "mtls.squid.clam.whelk:443", credentials=mock_cred, - ssl_credentials=mock_ssl_cred, + credentials_file=None, scopes=("https://www.googleapis.com/auth/cloud-platform",), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, ) assert transport.grpc_channel == mock_grpc_channel @@ -743,8 +940,10 @@ def test_text_to_speech_grpc_transport_channel_mtls_with_adc( grpc_create_channel.assert_called_once_with( "mtls.squid.clam.whelk:443", credentials=mock_cred, - ssl_credentials=mock_ssl_cred, + credentials_file=None, scopes=("https://www.googleapis.com/auth/cloud-platform",), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, ) assert transport.grpc_channel == mock_grpc_channel @@ -778,7 +977,9 @@ def test_text_to_speech_grpc_asyncio_transport_channel_mtls_with_adc( grpc_create_channel.assert_called_once_with( "mtls.squid.clam.whelk:443", credentials=mock_cred, - ssl_credentials=mock_ssl_cred, + credentials_file=None, scopes=("https://www.googleapis.com/auth/cloud-platform",), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, ) assert transport.grpc_channel == mock_grpc_channel