diff --git a/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest_asyncio.py.j2 b/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest_asyncio.py.j2 index 39e2f24141..95cc2509b3 100644 --- a/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest_asyncio.py.j2 +++ b/gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest_asyncio.py.j2 @@ -6,7 +6,7 @@ {% block content %} try: - from google.auth.aio.transport.sessions import AsyncAuthorizedSession # type: ignore + from google.auth.aio.transport.sessions import AsyncAuthorizedSession # type: ignore except ImportError as e: # pragma: NO COVER {# TODO(https://github.com/googleapis/google-auth-library-python/pull/1577): Update the version of google-auth once the linked PR is merged. #} raise ImportError("async rest transport requires google.auth >= 2.x.x") from e @@ -17,6 +17,7 @@ from google.api_core import exceptions as core_exceptions from google.api_core import gapic_v1 from google.api_core import retry_async as retries +import dataclasses from typing import Any, Callable, Tuple, Optional, Sequence, Union {{ shared_macros.operations_mixin_imports(api, service, opts) }} @@ -25,6 +26,11 @@ from .rest_base import _Base{{ service.name }}RestTransport from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO +try: + OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.AsyncRetry, object, None] # type: ignore + {# TODO (https://github.com/googleapis/gapic-generator-python/issues/2128): Update `rest_version` to include the transport dependency version. #} DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, @@ -32,6 +38,12 @@ DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( rest_version=None, ) +{# TODO: Add an `_interceptor` property once implemented #} +@dataclasses.dataclass +class Async{{service.name}}RestStub: + _session: AsyncAuthorizedSession + _host: str + class Async{{service.name}}RestTransport(_Base{{ service.name }}RestTransport): """Asynchronous REST backend transport for {{ service.name }}. @@ -92,14 +104,29 @@ class Async{{service.name}}RestTransport(_Base{{ service.name }}RestTransport): {{ shared_macros.wrap_async_method_macro()|indent(4) }} {% for method in service.methods.values()|sort(attribute="name") %} + class _{{method.name}}(_Base{{ service.name }}RestTransport._Base{{method.name}}, Async{{service.name}}RestStub): + def __hash__(self): + return hash("Async{{service.name}}RestTransport.{{method.name}}") + + async def __call__(self, + request: {{method.input.ident}}, *, + retry: OptionalRetry=gapic_v1.method.DEFAULT, + timeout: Optional[float]=None, + metadata: Sequence[Tuple[str, str]]=(), + {# TODO(b/362949446): Update the return type as we implement this for different method types. #} + ) -> None: + raise NotImplementedError( + "Method {{ method.name }} is not available over REST transport" + ) - {# TODO(b/362949446) Return a callable once the class is implemented. #} + {% endfor %} + {% for method in service.methods.values()|sort(attribute="name") %} {# TODO(https://github.com/googleapis/gapic-generator-python/issues/2154): Remove `type: ignore`. #} @property def {{method.transport_safe_name|snake_case}}(self) -> Callable[ [{{method.input.ident}}], {{method.output.ident}}]: - return # type: ignore + return self._{{method.name}}(self._session, self._host) # type: ignore {% endfor %} diff --git a/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_macros.j2 b/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_macros.j2 index 310bf993fa..6641f68823 100644 --- a/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_macros.j2 +++ b/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_macros.j2 @@ -1989,7 +1989,6 @@ def test_unsupported_parameter_rest_asyncio(): # which are not supported for rest transport. #} {% macro rest_method_not_implemented_error(service, method, transport, is_async) %} -{% if not is_async %}{# TODO(b/362949446): Remove this guard once a not implemented __call__ class method is added to async rest for every wrapper.Method. #} {% set await_prefix = get_await_prefix(is_async) %} {% set async_prefix = get_async_prefix(is_async) %} {% set async_decorator = get_async_decorator(is_async) %} @@ -2015,7 +2014,6 @@ def test_unsupported_parameter_rest_asyncio(): in str(not_implemented_error.value) ) -{% endif %}{# if is_async #} {% endmacro %} {# initialize_client_with_transport_test adds coverage for transport clients. diff --git a/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest_asyncio.py b/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest_asyncio.py index 6f102a418b..b11ed22abe 100755 --- a/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest_asyncio.py +++ b/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest_asyncio.py @@ -14,7 +14,7 @@ # limitations under the License. # try: - from google.auth.aio.transport.sessions import AsyncAuthorizedSession # type: ignore + from google.auth.aio.transport.sessions import AsyncAuthorizedSession # type: ignore except ImportError as e: # pragma: NO COVER raise ImportError("async rest transport requires google.auth >= 2.x.x") from e @@ -24,6 +24,7 @@ from google.api_core import gapic_v1 from google.api_core import retry_async as retries +import dataclasses from typing import Any, Callable, Tuple, Optional, Sequence, Union @@ -35,12 +36,22 @@ from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO +try: + OptionalRetry = Union[retries.AsyncRetry, gapic_v1.method._MethodDefault, None] +except AttributeError: # pragma: NO COVER + OptionalRetry = Union[retries.AsyncRetry, object, None] # type: ignore + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, grpc_version=None, rest_version=None, ) +@dataclasses.dataclass +class AsyncCloudRedisRestStub: + _session: AsyncAuthorizedSession + _host: str + class AsyncCloudRedisRestTransport(_BaseCloudRedisRestTransport): """Asynchronous REST backend transport for CloudRedis. @@ -179,71 +190,225 @@ def _wrap_method(self, func, *args, **kwargs): kwargs["kind"] = self.kind return gapic_v1.method_async.wrap_method(func, *args, **kwargs) + class _CreateInstance(_BaseCloudRedisRestTransport._BaseCreateInstance, AsyncCloudRedisRestStub): + def __hash__(self): + return hash("AsyncCloudRedisRestTransport.CreateInstance") + + async def __call__(self, + request: cloud_redis.CreateInstanceRequest, *, + retry: OptionalRetry=gapic_v1.method.DEFAULT, + timeout: Optional[float]=None, + metadata: Sequence[Tuple[str, str]]=(), + ) -> None: + raise NotImplementedError( + "Method CreateInstance is not available over REST transport" + ) + + class _DeleteInstance(_BaseCloudRedisRestTransport._BaseDeleteInstance, AsyncCloudRedisRestStub): + def __hash__(self): + return hash("AsyncCloudRedisRestTransport.DeleteInstance") + + async def __call__(self, + request: cloud_redis.DeleteInstanceRequest, *, + retry: OptionalRetry=gapic_v1.method.DEFAULT, + timeout: Optional[float]=None, + metadata: Sequence[Tuple[str, str]]=(), + ) -> None: + raise NotImplementedError( + "Method DeleteInstance is not available over REST transport" + ) + + class _ExportInstance(_BaseCloudRedisRestTransport._BaseExportInstance, AsyncCloudRedisRestStub): + def __hash__(self): + return hash("AsyncCloudRedisRestTransport.ExportInstance") + + async def __call__(self, + request: cloud_redis.ExportInstanceRequest, *, + retry: OptionalRetry=gapic_v1.method.DEFAULT, + timeout: Optional[float]=None, + metadata: Sequence[Tuple[str, str]]=(), + ) -> None: + raise NotImplementedError( + "Method ExportInstance is not available over REST transport" + ) + + class _FailoverInstance(_BaseCloudRedisRestTransport._BaseFailoverInstance, AsyncCloudRedisRestStub): + def __hash__(self): + return hash("AsyncCloudRedisRestTransport.FailoverInstance") + + async def __call__(self, + request: cloud_redis.FailoverInstanceRequest, *, + retry: OptionalRetry=gapic_v1.method.DEFAULT, + timeout: Optional[float]=None, + metadata: Sequence[Tuple[str, str]]=(), + ) -> None: + raise NotImplementedError( + "Method FailoverInstance is not available over REST transport" + ) + + class _GetInstance(_BaseCloudRedisRestTransport._BaseGetInstance, AsyncCloudRedisRestStub): + def __hash__(self): + return hash("AsyncCloudRedisRestTransport.GetInstance") + + async def __call__(self, + request: cloud_redis.GetInstanceRequest, *, + retry: OptionalRetry=gapic_v1.method.DEFAULT, + timeout: Optional[float]=None, + metadata: Sequence[Tuple[str, str]]=(), + ) -> None: + raise NotImplementedError( + "Method GetInstance is not available over REST transport" + ) + + class _GetInstanceAuthString(_BaseCloudRedisRestTransport._BaseGetInstanceAuthString, AsyncCloudRedisRestStub): + def __hash__(self): + return hash("AsyncCloudRedisRestTransport.GetInstanceAuthString") + + async def __call__(self, + request: cloud_redis.GetInstanceAuthStringRequest, *, + retry: OptionalRetry=gapic_v1.method.DEFAULT, + timeout: Optional[float]=None, + metadata: Sequence[Tuple[str, str]]=(), + ) -> None: + raise NotImplementedError( + "Method GetInstanceAuthString is not available over REST transport" + ) + + class _ImportInstance(_BaseCloudRedisRestTransport._BaseImportInstance, AsyncCloudRedisRestStub): + def __hash__(self): + return hash("AsyncCloudRedisRestTransport.ImportInstance") + + async def __call__(self, + request: cloud_redis.ImportInstanceRequest, *, + retry: OptionalRetry=gapic_v1.method.DEFAULT, + timeout: Optional[float]=None, + metadata: Sequence[Tuple[str, str]]=(), + ) -> None: + raise NotImplementedError( + "Method ImportInstance is not available over REST transport" + ) + + class _ListInstances(_BaseCloudRedisRestTransport._BaseListInstances, AsyncCloudRedisRestStub): + def __hash__(self): + return hash("AsyncCloudRedisRestTransport.ListInstances") + + async def __call__(self, + request: cloud_redis.ListInstancesRequest, *, + retry: OptionalRetry=gapic_v1.method.DEFAULT, + timeout: Optional[float]=None, + metadata: Sequence[Tuple[str, str]]=(), + ) -> None: + raise NotImplementedError( + "Method ListInstances is not available over REST transport" + ) + + class _RescheduleMaintenance(_BaseCloudRedisRestTransport._BaseRescheduleMaintenance, AsyncCloudRedisRestStub): + def __hash__(self): + return hash("AsyncCloudRedisRestTransport.RescheduleMaintenance") + + async def __call__(self, + request: cloud_redis.RescheduleMaintenanceRequest, *, + retry: OptionalRetry=gapic_v1.method.DEFAULT, + timeout: Optional[float]=None, + metadata: Sequence[Tuple[str, str]]=(), + ) -> None: + raise NotImplementedError( + "Method RescheduleMaintenance is not available over REST transport" + ) + + class _UpdateInstance(_BaseCloudRedisRestTransport._BaseUpdateInstance, AsyncCloudRedisRestStub): + def __hash__(self): + return hash("AsyncCloudRedisRestTransport.UpdateInstance") + + async def __call__(self, + request: cloud_redis.UpdateInstanceRequest, *, + retry: OptionalRetry=gapic_v1.method.DEFAULT, + timeout: Optional[float]=None, + metadata: Sequence[Tuple[str, str]]=(), + ) -> None: + raise NotImplementedError( + "Method UpdateInstance is not available over REST transport" + ) + + class _UpgradeInstance(_BaseCloudRedisRestTransport._BaseUpgradeInstance, AsyncCloudRedisRestStub): + def __hash__(self): + return hash("AsyncCloudRedisRestTransport.UpgradeInstance") + + async def __call__(self, + request: cloud_redis.UpgradeInstanceRequest, *, + retry: OptionalRetry=gapic_v1.method.DEFAULT, + timeout: Optional[float]=None, + metadata: Sequence[Tuple[str, str]]=(), + ) -> None: + raise NotImplementedError( + "Method UpgradeInstance is not available over REST transport" + ) + @property def create_instance(self) -> Callable[ [cloud_redis.CreateInstanceRequest], operations_pb2.Operation]: - return # type: ignore + return self._CreateInstance(self._session, self._host) # type: ignore @property def delete_instance(self) -> Callable[ [cloud_redis.DeleteInstanceRequest], operations_pb2.Operation]: - return # type: ignore + return self._DeleteInstance(self._session, self._host) # type: ignore @property def export_instance(self) -> Callable[ [cloud_redis.ExportInstanceRequest], operations_pb2.Operation]: - return # type: ignore + return self._ExportInstance(self._session, self._host) # type: ignore @property def failover_instance(self) -> Callable[ [cloud_redis.FailoverInstanceRequest], operations_pb2.Operation]: - return # type: ignore + return self._FailoverInstance(self._session, self._host) # type: ignore @property def get_instance(self) -> Callable[ [cloud_redis.GetInstanceRequest], cloud_redis.Instance]: - return # type: ignore + return self._GetInstance(self._session, self._host) # type: ignore @property def get_instance_auth_string(self) -> Callable[ [cloud_redis.GetInstanceAuthStringRequest], cloud_redis.InstanceAuthString]: - return # type: ignore + return self._GetInstanceAuthString(self._session, self._host) # type: ignore @property def import_instance(self) -> Callable[ [cloud_redis.ImportInstanceRequest], operations_pb2.Operation]: - return # type: ignore + return self._ImportInstance(self._session, self._host) # type: ignore @property def list_instances(self) -> Callable[ [cloud_redis.ListInstancesRequest], cloud_redis.ListInstancesResponse]: - return # type: ignore + return self._ListInstances(self._session, self._host) # type: ignore @property def reschedule_maintenance(self) -> Callable[ [cloud_redis.RescheduleMaintenanceRequest], operations_pb2.Operation]: - return # type: ignore + return self._RescheduleMaintenance(self._session, self._host) # type: ignore @property def update_instance(self) -> Callable[ [cloud_redis.UpdateInstanceRequest], operations_pb2.Operation]: - return # type: ignore + return self._UpdateInstance(self._session, self._host) # type: ignore @property def upgrade_instance(self) -> Callable[ [cloud_redis.UpgradeInstanceRequest], operations_pb2.Operation]: - return # type: ignore + return self._UpgradeInstance(self._session, self._host) # type: ignore @property def kind(self) -> str: diff --git a/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py b/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py index 66ab6c7534..78a7592bfb 100755 --- a/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py +++ b/tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py @@ -8388,6 +8388,204 @@ def test_transport_kind_rest_asyncio(): assert transport.kind == "rest_asyncio" +@pytest.mark.asyncio +async def test_list_instances_rest_asyncio_error(): + if not HAS_GOOGLE_AUTH_AIO: + pytest.skip("google-auth > 2.x.x is required for async rest transport.") + + client = CloudRedisAsyncClient( + credentials=async_anonymous_credentials(), + transport="rest_asyncio" + ) + + with pytest.raises(NotImplementedError) as not_implemented_error: + await client.list_instances({}) + assert ( + "Method ListInstances is not available over REST transport" + in str(not_implemented_error.value) + ) + + +@pytest.mark.asyncio +async def test_get_instance_rest_asyncio_error(): + if not HAS_GOOGLE_AUTH_AIO: + pytest.skip("google-auth > 2.x.x is required for async rest transport.") + + client = CloudRedisAsyncClient( + credentials=async_anonymous_credentials(), + transport="rest_asyncio" + ) + + with pytest.raises(NotImplementedError) as not_implemented_error: + await client.get_instance({}) + assert ( + "Method GetInstance is not available over REST transport" + in str(not_implemented_error.value) + ) + + +@pytest.mark.asyncio +async def test_get_instance_auth_string_rest_asyncio_error(): + if not HAS_GOOGLE_AUTH_AIO: + pytest.skip("google-auth > 2.x.x is required for async rest transport.") + + client = CloudRedisAsyncClient( + credentials=async_anonymous_credentials(), + transport="rest_asyncio" + ) + + with pytest.raises(NotImplementedError) as not_implemented_error: + await client.get_instance_auth_string({}) + assert ( + "Method GetInstanceAuthString is not available over REST transport" + in str(not_implemented_error.value) + ) + + +@pytest.mark.asyncio +async def test_create_instance_rest_asyncio_error(): + if not HAS_GOOGLE_AUTH_AIO: + pytest.skip("google-auth > 2.x.x is required for async rest transport.") + + client = CloudRedisAsyncClient( + credentials=async_anonymous_credentials(), + transport="rest_asyncio" + ) + + with pytest.raises(NotImplementedError) as not_implemented_error: + await client.create_instance({}) + assert ( + "Method CreateInstance is not available over REST transport" + in str(not_implemented_error.value) + ) + + +@pytest.mark.asyncio +async def test_update_instance_rest_asyncio_error(): + if not HAS_GOOGLE_AUTH_AIO: + pytest.skip("google-auth > 2.x.x is required for async rest transport.") + + client = CloudRedisAsyncClient( + credentials=async_anonymous_credentials(), + transport="rest_asyncio" + ) + + with pytest.raises(NotImplementedError) as not_implemented_error: + await client.update_instance({}) + assert ( + "Method UpdateInstance is not available over REST transport" + in str(not_implemented_error.value) + ) + + +@pytest.mark.asyncio +async def test_upgrade_instance_rest_asyncio_error(): + if not HAS_GOOGLE_AUTH_AIO: + pytest.skip("google-auth > 2.x.x is required for async rest transport.") + + client = CloudRedisAsyncClient( + credentials=async_anonymous_credentials(), + transport="rest_asyncio" + ) + + with pytest.raises(NotImplementedError) as not_implemented_error: + await client.upgrade_instance({}) + assert ( + "Method UpgradeInstance is not available over REST transport" + in str(not_implemented_error.value) + ) + + +@pytest.mark.asyncio +async def test_import_instance_rest_asyncio_error(): + if not HAS_GOOGLE_AUTH_AIO: + pytest.skip("google-auth > 2.x.x is required for async rest transport.") + + client = CloudRedisAsyncClient( + credentials=async_anonymous_credentials(), + transport="rest_asyncio" + ) + + with pytest.raises(NotImplementedError) as not_implemented_error: + await client.import_instance({}) + assert ( + "Method ImportInstance is not available over REST transport" + in str(not_implemented_error.value) + ) + + +@pytest.mark.asyncio +async def test_export_instance_rest_asyncio_error(): + if not HAS_GOOGLE_AUTH_AIO: + pytest.skip("google-auth > 2.x.x is required for async rest transport.") + + client = CloudRedisAsyncClient( + credentials=async_anonymous_credentials(), + transport="rest_asyncio" + ) + + with pytest.raises(NotImplementedError) as not_implemented_error: + await client.export_instance({}) + assert ( + "Method ExportInstance is not available over REST transport" + in str(not_implemented_error.value) + ) + + +@pytest.mark.asyncio +async def test_failover_instance_rest_asyncio_error(): + if not HAS_GOOGLE_AUTH_AIO: + pytest.skip("google-auth > 2.x.x is required for async rest transport.") + + client = CloudRedisAsyncClient( + credentials=async_anonymous_credentials(), + transport="rest_asyncio" + ) + + with pytest.raises(NotImplementedError) as not_implemented_error: + await client.failover_instance({}) + assert ( + "Method FailoverInstance is not available over REST transport" + in str(not_implemented_error.value) + ) + + +@pytest.mark.asyncio +async def test_delete_instance_rest_asyncio_error(): + if not HAS_GOOGLE_AUTH_AIO: + pytest.skip("google-auth > 2.x.x is required for async rest transport.") + + client = CloudRedisAsyncClient( + credentials=async_anonymous_credentials(), + transport="rest_asyncio" + ) + + with pytest.raises(NotImplementedError) as not_implemented_error: + await client.delete_instance({}) + assert ( + "Method DeleteInstance is not available over REST transport" + in str(not_implemented_error.value) + ) + + +@pytest.mark.asyncio +async def test_reschedule_maintenance_rest_asyncio_error(): + if not HAS_GOOGLE_AUTH_AIO: + pytest.skip("google-auth > 2.x.x is required for async rest transport.") + + client = CloudRedisAsyncClient( + credentials=async_anonymous_credentials(), + transport="rest_asyncio" + ) + + with pytest.raises(NotImplementedError) as not_implemented_error: + await client.reschedule_maintenance({}) + assert ( + "Method RescheduleMaintenance is not available over REST transport" + in str(not_implemented_error.value) + ) + + def test_initialize_client_w_rest_asyncio(): if not HAS_GOOGLE_AUTH_AIO: pytest.skip("google-auth > 2.x.x is required for async rest transport.")