diff --git a/packages/google-cloud-config/google/cloud/config_v1/services/config/async_client.py b/packages/google-cloud-config/google/cloud/config_v1/services/config/async_client.py index 799e2a9596ee..76f56e350084 100644 --- a/packages/google-cloud-config/google/cloud/config_v1/services/config/async_client.py +++ b/packages/google-cloud-config/google/cloud/config_v1/services/config/async_client.py @@ -60,7 +60,9 @@ from .client import ConfigClient from .transports.base import DEFAULT_CLIENT_INFO, ConfigTransport +from .transports.grpc import ConfigGrpcTransport from .transports.grpc_asyncio import ConfigGrpcAsyncIOTransport +from .transports.rest import ConfigRestTransport try: from google.api_core import client_logging # type: ignore @@ -284,11 +286,25 @@ def __init__( google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport creation failed for any reason. """ + transport_provided = isinstance(transport, ConfigTransport) + + if ( + transport_provided + and isinstance(transport, (ConfigGrpcTransport, ConfigRestTransport)) + ) or ( + isinstance(transport, str) and transport in ["grpc", "rest"] + ): + raise ValueError( + "The `ConfigAsyncClient` does not support sync transports. " + "Please use `ConfigClient` instead." + ) + self._client = ConfigClient( credentials=credentials, transport=transport, client_options=client_options, client_info=client_info, + _allow_async_transport=True, ) if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( diff --git a/packages/google-cloud-config/google/cloud/config_v1/services/config/client.py b/packages/google-cloud-config/google/cloud/config_v1/services/config/client.py index a05a7cbc1368..153b00898434 100644 --- a/packages/google-cloud-config/google/cloud/config_v1/services/config/client.py +++ b/packages/google-cloud-config/google/cloud/config_v1/services/config/client.py @@ -779,6 +779,7 @@ def __init__( ] = None, client_options: Optional[Union[client_options_lib.ClientOptions, dict]] = None, client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + _allow_async_transport: bool = False, ) -> None: """Instantiates the config client. @@ -870,6 +871,18 @@ def __init__( # Ordinarily, we provide the transport, but allowing a custom transport # instance provides an extensibility point for unusual situations. transport_provided = isinstance(transport, ConfigTransport) + + # Raise an exception if the async transport is provided. + if ( + not _allow_async_transport + and transport_provided + and isinstance(transport, ConfigGrpcAsyncIOTransport) + ): + raise ValueError( + "The `ConfigClient` does not support async transports. " + "Please use `ConfigAsyncClient` instead." + ) + if transport_provided: # transport is a ConfigTransport instance. if credentials or self._client_options.credentials_file or api_key_value: @@ -909,6 +922,18 @@ def __init__( if isinstance(transport, str) or transport is None else cast(Callable[..., ConfigTransport], transport) ) + + # Raise an exception if the async transport is provided. + if ( + not _allow_async_transport + and isinstance(transport, str) + and transport == "grpc_asyncio" + ): + raise ValueError( + "The `ConfigClient` does not support async transports. " + "Please use `ConfigAsyncClient` instead." + ) + # initialize with the provided callable or the passed in class self._transport = transport_init( credentials=credentials, diff --git a/packages/google-cloud-config/tests/unit/gapic/config_v1/test_client_validation.py b/packages/google-cloud-config/tests/unit/gapic/config_v1/test_client_validation.py new file mode 100644 index 000000000000..4db609d27091 --- /dev/null +++ b/packages/google-cloud-config/tests/unit/gapic/config_v1/test_client_validation.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# Copyright 2025 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 pytest +from unittest import mock +from google.cloud.config_v1 import ConfigClient, ConfigAsyncClient +from google.cloud.config_v1.services.config import transports +from google.auth import credentials as ga_credentials + +def test_config_client_validates_grpc_asyncio_string(): + creds = ga_credentials.AnonymousCredentials() + with pytest.raises(ValueError) as excinfo: + ConfigClient(credentials=creds, transport="grpc_asyncio") + assert "ConfigClient" in str(excinfo.value) + assert "async transports" in str(excinfo.value) + +def test_config_client_validates_grpc_asyncio_object(): + creds = ga_credentials.AnonymousCredentials() + transport = transports.ConfigGrpcAsyncIOTransport(credentials=creds) + with pytest.raises(ValueError) as excinfo: + ConfigClient(transport=transport) + assert "ConfigClient" in str(excinfo.value) + assert "async transports" in str(excinfo.value) + +def test_config_client_allows_custom_transport(): + creds = ga_credentials.AnonymousCredentials() + class CustomTransport(transports.ConfigTransport): + pass + transport = CustomTransport(credentials=creds) + # Should not raise + ConfigClient(transport=transport) + +def test_config_async_client_validates_grpc_string(): + creds = ga_credentials.AnonymousCredentials() + with pytest.raises(ValueError) as excinfo: + ConfigAsyncClient(credentials=creds, transport="grpc") + assert "ConfigAsyncClient" in str(excinfo.value) + assert "sync transports" in str(excinfo.value) + +def test_config_async_client_validates_rest_string(): + creds = ga_credentials.AnonymousCredentials() + with pytest.raises(ValueError) as excinfo: + ConfigAsyncClient(credentials=creds, transport="rest") + assert "ConfigAsyncClient" in str(excinfo.value) + assert "sync transports" in str(excinfo.value) + +def test_config_async_client_validates_grpc_object(): + creds = ga_credentials.AnonymousCredentials() + transport = transports.ConfigGrpcTransport(credentials=creds) + with pytest.raises(ValueError) as excinfo: + ConfigAsyncClient(transport=transport) + assert "ConfigAsyncClient" in str(excinfo.value) + assert "sync transports" in str(excinfo.value) + +def test_config_async_client_allows_custom_transport(): + creds = ga_credentials.AnonymousCredentials() + class CustomTransport(transports.ConfigTransport): + pass + transport = CustomTransport(credentials=creds) + # Should not raise + ConfigAsyncClient(transport=transport) + +def test_config_async_client_initialization_happy_path(): + creds = ga_credentials.AnonymousCredentials() + # Should not raise + client = ConfigAsyncClient(credentials=creds) + assert isinstance(client._client, ConfigClient) diff --git a/packages/google-cloud-config/tests/unit/gapic/config_v1/test_config.py b/packages/google-cloud-config/tests/unit/gapic/config_v1/test_config.py index 2bc00850fb8c..5d5edef6203b 100644 --- a/packages/google-cloud-config/tests/unit/gapic/config_v1/test_config.py +++ b/packages/google-cloud-config/tests/unit/gapic/config_v1/test_config.py @@ -25421,7 +25421,6 @@ def test_config_http_transport_client_cert_source_for_mtls(): "transport_name", [ "grpc", - "grpc_asyncio", "rest", ], ) @@ -25444,7 +25443,6 @@ def test_config_host_no_port(transport_name): "transport_name", [ "grpc", - "grpc_asyncio", "rest", ], )