diff --git a/.flake8 b/.flake8
index d1134d72..ed931638 100644
--- a/.flake8
+++ b/.flake8
@@ -16,7 +16,7 @@
# Generated by synthtool. DO NOT EDIT!
[flake8]
-ignore = E203, E266, E501, W503, F401, F841
+ignore = E203, E266, E501, W503
exclude =
# Exclude generated code.
**/proto/**
diff --git a/.kokoro/docker/docs/fetch_gpg_keys.sh b/.kokoro/docker/docs/fetch_gpg_keys.sh
index f027b73f..d653dd86 100755
--- a/.kokoro/docker/docs/fetch_gpg_keys.sh
+++ b/.kokoro/docker/docs/fetch_gpg_keys.sh
@@ -14,6 +14,8 @@
# limitations under the License.
# A script to fetch gpg keys with retry.
+# Avoid jinja parsing the file.
+#
function retry {
if [[ "${#}" -le 1 ]]; then
@@ -39,3 +41,5 @@ retry 3 gpg --keyserver ha.pool.sks-keyservers.net --recv-keys \
# 3.8.0 (Ćukasz Langa)
retry 3 gpg --keyserver ha.pool.sks-keyservers.net --recv-keys \
E3FF2839C048B25C084DEBE9B26995E310250568
+
+#
diff --git a/.kokoro/docs/common.cfg b/.kokoro/docs/common.cfg
index 0a3c3c6a..35fcc764 100644
--- a/.kokoro/docs/common.cfg
+++ b/.kokoro/docs/common.cfg
@@ -18,7 +18,6 @@ env_vars: {
key: "TRAMPOLINE_IMAGE"
value: "gcr.io/cloud-devrel-kokoro-resources/python-lib-docs"
}
-
env_vars: {
key: "TRAMPOLINE_BUILD_FILE"
value: "github/python-texttospeech/.kokoro/publish-docs.sh"
diff --git a/.kokoro/publish-docs.sh b/.kokoro/publish-docs.sh
index f15d94d9..8acb14e8 100755
--- a/.kokoro/publish-docs.sh
+++ b/.kokoro/publish-docs.sh
@@ -56,8 +56,7 @@ python3 -m docuploader create-metadata \
--distribution-name=$(python3 setup.py --name) \
--product-page=$(jq --raw-output '.product_documentation // empty' .repo-metadata.json) \
--github-repository=$(jq --raw-output '.repo // empty' .repo-metadata.json) \
- --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json) \
- --serving-path="/python/docs/reference/$(jq --raw-output '.name // empty' .repo-metadata.json)/$(jq --raw-output '.distribution_name // empty' .repo-metadata.json)/latest"
+ --issue-tracker=$(jq --raw-output '.issue_tracker // empty' .repo-metadata.json)
cat docs.metadata
diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html
index 228529ef..6316a537 100644
--- a/docs/_templates/layout.html
+++ b/docs/_templates/layout.html
@@ -21,8 +21,8 @@
- 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