From 28097536e1a47063a5d3211e9c1c498a1f06c724 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Thu, 5 Sep 2024 15:08:36 -0400 Subject: [PATCH] feat: implement async rest transport constructor (#2123) --- .github/workflows/tests.yaml | 3 +- gapic/generator/generator.py | 3 +- .../%sub/services/%service/client.py.j2 | 10 ++ .../%service/transports/rest_asyncio.py.j2 | 83 +++++++++++++- .../%name_%version/%sub/test_%service.py.j2 | 16 ++- .../gapic/%name_%version/%sub/test_macros.j2 | 47 ++++---- noxfile.py | 51 +++++++++ .../unit/gapic/asset_v1/test_asset_service.py | 31 +++--- .../credentials_v1/test_iam_credentials.py | 31 +++--- .../unit/gapic/eventarc_v1/test_eventarc.py | 31 +++--- .../logging_v2/test_config_service_v2.py | 23 ++-- .../logging_v2/test_logging_service_v2.py | 23 ++-- .../logging_v2/test_metrics_service_v2.py | 23 ++-- .../redis_v1/services/cloud_redis/client.py | 2 + .../cloud_redis/transports/rest_asyncio.py | 102 ++++++++++++++++++ .../unit/gapic/redis_v1/test_cloud_redis.py | 38 ++++--- tests/integration/redis_v1.yaml | 11 +- 17 files changed, 380 insertions(+), 148 deletions(-) create mode 100755 tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest_asyncio.py diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 8be4f041d2..4c00a2bb32 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -140,7 +140,8 @@ jobs: strategy: matrix: python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] - variant: ['', _alternative_templates, _mixins, _alternative_templates_mixins] + # TODO(https://github.com/googleapis/gapic-generator-python/issues/2121) Remove `_w_rest_async` variant when async rest is GA. + variant: ['', _alternative_templates, _mixins, _alternative_templates_mixins, _w_rest_async] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/gapic/generator/generator.py b/gapic/generator/generator.py index cb0dff456b..27ad4b7ff2 100644 --- a/gapic/generator/generator.py +++ b/gapic/generator/generator.py @@ -294,8 +294,7 @@ def _render_template( ('transport' in template_name and not self._is_desired_transport(template_name, opts)) or - # TODO: Remove the following conditions once support for async rest transport is GA: - # See related issue: https://github.com/googleapis/gapic-generator-python/issues/2121. + # TODO(https://github.com/googleapis/gapic-generator-python/issues/2121): Remove this condition when async rest is GA. ('async_client' in template_name and 'grpc' not in opts.transport and not api_schema.all_library_settings[api_schema.naming.proto_package].python_settings.experimental_features.rest_async_io_enabled) or diff --git a/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 b/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 index 6c45fdd728..168955c8f6 100644 --- a/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 +++ b/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2 @@ -1,3 +1,5 @@ +{# TODO(https://github.com/googleapis/gapic-generator-python/issues/2121): Remove the following variable (and the condition later in this file) for async rest transport once support for it is GA. #} +{% set rest_async_io_enabled = api.all_library_settings[api.naming.proto_package].python_settings.experimental_features.rest_async_io_enabled %} {% extends '_base.py.j2' %} {% block content %} @@ -62,6 +64,10 @@ from .transports.grpc_asyncio import {{ service.grpc_asyncio_transport_name }} {% endif %} {% if 'rest' in opts.transport %} from .transports.rest import {{ service.name }}RestTransport +{# TODO(https://github.com/googleapis/gapic-generator-python/issues/2121): Remove this condition when async rest is GA. #} +{% if rest_async_io_enabled %} +from .transports.rest_asyncio import Async{{ service.name }}RestTransport +{% endif %}{# if rest_async_io_enabled #} {% endif %} @@ -79,6 +85,10 @@ class {{ service.client_name }}Meta(type): {% endif %} {% if "rest" in opts.transport %} _transport_registry["rest"] = {{ service.name }}RestTransport + {# TODO(https://github.com/googleapis/gapic-generator-python/issues/2121): Remove this condition when async rest is GA. #} + {% if rest_async_io_enabled %} + _transport_registry["rest_asyncio"] = Async{{ service.name }}RestTransport + {% endif %}{# if rest_async_io_enabled #} {% endif %} def get_transport_class(cls, 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 d73591c472..ab87c32c76 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 @@ -1,5 +1,80 @@ -{# TODO: Remove the following condition for async rest transport once support for it is GA: - # {% if rest_async_io_enabled %} - # See related issue: https://github.com/googleapis/gapic-generator-python/issues/2121. -#} +{# TODO(https://github.com/googleapis/gapic-generator-python/issues/2121): Remove the following variable (and the condition later in this file) for async rest transport once support for it is GA. #} {% set rest_async_io_enabled = api.all_library_settings[api.naming.proto_package].python_settings.experimental_features.rest_async_io_enabled %} +{% extends '_base.py.j2' %} + +{% block content %} + +from google.api_core import gapic_v1 + +from typing import Any, Optional + +from .rest_base import _Base{{ service.name }}RestTransport + +from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + +{# 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, + grpc_version=None, + rest_version=None, +) + +class Async{{service.name}}RestTransport(_Base{{ service.name }}RestTransport): + """Asynchronous REST backend transport for {{ service.name }}. + + {{ service.meta.doc|rst(width=72, indent=4) }} + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + """ + def __init__(self, *, + host: str{% if service.host %} = '{{ service.host }}'{% endif %}, + {# TODO (https://github.com/googleapis/gapic-generator-python/issues/2129): Update the default type for credentials. #} + credentials: Optional[Any] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + url_scheme: str = 'https', + ) -> None: + """Instantiate the transport. + + {% if not opts.rest_numeric_enums %} + NOTE: This async REST transport functionality is currently in a beta + state (preview). We welcome your feedback via a GitHub issue in + this library's repository. Thank you! + {% endif %} + + Args: + host ({% if service.host %}Optional[str]{% else %}str{% endif %}): + {{ ' ' }}The hostname to connect to {% if service.host %}(default: '{{ service.host }}'){% endif %}. + {# TODO (https://github.com/googleapis/gapic-generator-python/issues/2129): Update the default type for credentials. #} + credentials (Optional[Any]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + url_scheme (str): the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=False, + url_scheme=url_scheme, + api_audience=None + ) + + @property + def kind(self) -> str: + return "rest_asyncio" + +{% endblock %} diff --git a/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 b/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 index 8a64754a42..6f467a56a6 100644 --- a/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 +++ b/gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2 @@ -1,3 +1,5 @@ +{# TODO(https://github.com/googleapis/gapic-generator-python/issues/2121): Remove the following variable (and the condition later in this file) for async rest transport once support for it is GA. #} +{% set rest_async_io_enabled = api.all_library_settings[api.naming.proto_package].python_settings.experimental_features.rest_async_io_enabled %} {% extends "_base.py.j2" %} {% block content %} @@ -1041,9 +1043,17 @@ def test_transport_adc(transport_class): transport_class() adc.assert_called_once() -{{ test_macros.transport_kind_test(service, opts) }} - -{{ test_macros.transport_kind_test(service, opts, is_async=True) }} +{% set configs = [] %} +{% for transport in opts.transport %} + {% do configs.append({'service':service, 'transport':transport, 'is_async':false}) %} +{# TODO(https://github.com/googleapis/gapic-generator-python/issues/2121): Remove this condition when async rest is GA. #} +{% if 'grpc' in transport or rest_async_io_enabled %} + {% do configs.append({'service':service, 'transport':transport, 'is_async':true}) %} +{% endif %} +{% endfor %} +{% for conf in configs %} +{{ test_macros.transport_kind_test(**conf) }} +{% endfor %} {% if 'grpc' in opts.transport %} def test_transport_grpc_default(): 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 b73e8eac81..2f2fb3b74f 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 @@ -1877,33 +1877,24 @@ def test_{{ method_name }}_empty_call(): assert args[0] == {{ method.input.ident }}() {% endmacro %} +{% macro get_credentials(is_async=False) %} +{{- 'async_anonymous_credentials()' if is_async else 'ga_credentials.AnonymousCredentials()' -}} +{% endmacro %} -{% macro transport_kind_test(service, opts, is_async=False) %} -@pytest.mark.parametrize("transport_name", [ - {% if is_async %} - {% if "grpc" in opts.transport %} - "grpc_asyncio", - {% endif %} - {% else %}{# if not is_async #} - {% if "grpc" in opts.transport%} - "grpc", - {% endif %} - {% if "rest" in opts.transport %} - "rest", - {% endif %} - {% endif %}{# is_async #} -]) -{% if is_async %} -@pytest.mark.asyncio -async def test_transport_kind_async(transport_name): - transport = {{ service.async_client_name }}.get_transport_class(transport_name)( - credentials=async_anonymous_credentials(), - ) -{% else %} -def test_transport_kind(transport_name): - transport = {{ service.client_name }}.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), +{% macro get_client(service, is_async) %} +{{- service.async_client_name if is_async else service.client_name -}} +{% endmacro %} + +{% macro get_transport_name(transport, is_async=False)%} +{{- transport + ("_asyncio" if is_async else "") -}} +{% endmacro %} + +{% macro transport_kind_test(service, transport, is_async) %} +{% set transport_name = get_transport_name(transport, is_async) %} +def test_transport_kind_{{ transport_name }}(): + transport = {{ get_client(service, is_async) }}.get_transport_class("{{ transport_name }}")( + credentials={{get_credentials(is_async)}} ) -{% endif %} - assert transport.kind == transport_name -{% endmacro %} \ No newline at end of file + assert transport.kind == "{{ transport_name }}" + +{% endmacro %} diff --git a/noxfile.py b/noxfile.py index 3ab6c7b392..0a28618b4e 100644 --- a/noxfile.py +++ b/noxfile.py @@ -174,12 +174,31 @@ def fragment(session, use_ads_templates=False): def fragment_alternative_templates(session): fragment(session, use_ads_templates=True) +# `_add_python_settings` consumes a path to a temporary directory (str; i.e. tmp_dir) and +# python settings (Dict; i.e. python settings) and modifies the service yaml within +# tmp_dir to include python settings. The primary purpose of this function is to modify +# the service yaml and include `rest_async_io_enabled=True` to test the async rest +# optional feature. +def _add_python_settings(tmp_dir, python_settings): + return f""" +import yaml +from pathlib import Path +temp_file_path = Path(f"{tmp_dir}/showcase_v1beta1.yaml") +with temp_file_path.open('r') as file: + data = yaml.safe_load(file) + data['publishing']['library_settings'] = {python_settings} + +with temp_file_path.open('w') as file: + yaml.safe_dump(data, file, default_flow_style=False, sort_keys=False) +""" +# TODO(https://github.com/googleapis/gapic-generator-python/issues/2121): `rest_async_io_enabled` must be removed once async rest is GA. @contextmanager def showcase_library( session, templates="DEFAULT", other_opts: typing.Iterable[str] = (), include_service_yaml=True, retry_config=True, + rest_async_io_enabled=False ): """Install the generated library into the session for showcase tests.""" @@ -220,6 +239,25 @@ def showcase_library( external=True, silent=True, ) + # TODO(https://github.com/googleapis/gapic-generator-python/issues/2121): The section below updates the showcase service yaml + # to test experimental async rest transport. It must be removed once support for async rest is GA. + if rest_async_io_enabled: + # Install pyYAML for yaml. + session.install("pyYAML") + + python_settings = [ + { + 'version': 'google.showcase.v1beta1', + 'python_settings': { + 'experimental_features': { + 'rest_async_io_enabled': True + } + } + } + ] + update_service_yaml = _add_python_settings(tmp_dir, python_settings) + session.run("python", "-c" f"{update_service_yaml}") + # END TODO section to remove. if retry_config: session.run( "curl", @@ -392,6 +430,19 @@ def showcase_unit( run_showcase_unit_tests(session) +# TODO: `showcase_unit_w_rest_async` nox session runs showcase unit tests with the +# experimental async rest transport and must be removed once support for async rest is GA. +# See related issue: https://github.com/googleapis/gapic-generator-python/issues/2121. +@nox.session(python=ALL_PYTHON) +def showcase_unit_w_rest_async( + session, templates="DEFAULT", other_opts: typing.Iterable[str] = (), +): + """Run the generated unit tests with async rest transport against the Showcase library.""" + with showcase_library(session, templates=templates, other_opts=other_opts, rest_async_io_enabled=True) as lib: + session.chdir(lib) + run_showcase_unit_tests(session) + + @nox.session(python=ALL_PYTHON) def showcase_unit_alternative_templates(session): with showcase_library( diff --git a/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py b/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py index f952581a0e..b7bb1fad34 100755 --- a/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py +++ b/tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py @@ -16405,26 +16405,25 @@ def test_transport_adc(transport_class): transport_class() adc.assert_called_once() -@pytest.mark.parametrize("transport_name", [ - "grpc", - "rest", -]) -def test_transport_kind(transport_name): - transport = AssetServiceClient.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), +def test_transport_kind_grpc(): + transport = AssetServiceClient.get_transport_class("grpc")( + credentials=ga_credentials.AnonymousCredentials() ) - assert transport.kind == transport_name + assert transport.kind == "grpc" -@pytest.mark.parametrize("transport_name", [ - "grpc_asyncio", -]) -@pytest.mark.asyncio -async def test_transport_kind_async(transport_name): - transport = AssetServiceAsyncClient.get_transport_class(transport_name)( - credentials=async_anonymous_credentials(), +def test_transport_kind_grpc_asyncio(): + transport = AssetServiceAsyncClient.get_transport_class("grpc_asyncio")( + credentials=async_anonymous_credentials() + ) + assert transport.kind == "grpc_asyncio" + + +def test_transport_kind_rest(): + transport = AssetServiceClient.get_transport_class("rest")( + credentials=ga_credentials.AnonymousCredentials() ) - assert transport.kind == transport_name + assert transport.kind == "rest" def test_transport_grpc_default(): diff --git a/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py b/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py index 2e96e2aebf..37bcaed5d0 100755 --- a/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py +++ b/tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py @@ -3490,26 +3490,25 @@ def test_transport_adc(transport_class): transport_class() adc.assert_called_once() -@pytest.mark.parametrize("transport_name", [ - "grpc", - "rest", -]) -def test_transport_kind(transport_name): - transport = IAMCredentialsClient.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), +def test_transport_kind_grpc(): + transport = IAMCredentialsClient.get_transport_class("grpc")( + credentials=ga_credentials.AnonymousCredentials() ) - assert transport.kind == transport_name + assert transport.kind == "grpc" -@pytest.mark.parametrize("transport_name", [ - "grpc_asyncio", -]) -@pytest.mark.asyncio -async def test_transport_kind_async(transport_name): - transport = IAMCredentialsAsyncClient.get_transport_class(transport_name)( - credentials=async_anonymous_credentials(), +def test_transport_kind_grpc_asyncio(): + transport = IAMCredentialsAsyncClient.get_transport_class("grpc_asyncio")( + credentials=async_anonymous_credentials() + ) + assert transport.kind == "grpc_asyncio" + + +def test_transport_kind_rest(): + transport = IAMCredentialsClient.get_transport_class("rest")( + credentials=ga_credentials.AnonymousCredentials() ) - assert transport.kind == transport_name + assert transport.kind == "rest" def test_transport_grpc_default(): diff --git a/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py b/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py index 225cf30b7a..5e67d9980c 100755 --- a/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py +++ b/tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py @@ -13837,26 +13837,25 @@ def test_transport_adc(transport_class): transport_class() adc.assert_called_once() -@pytest.mark.parametrize("transport_name", [ - "grpc", - "rest", -]) -def test_transport_kind(transport_name): - transport = EventarcClient.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), +def test_transport_kind_grpc(): + transport = EventarcClient.get_transport_class("grpc")( + credentials=ga_credentials.AnonymousCredentials() ) - assert transport.kind == transport_name + assert transport.kind == "grpc" -@pytest.mark.parametrize("transport_name", [ - "grpc_asyncio", -]) -@pytest.mark.asyncio -async def test_transport_kind_async(transport_name): - transport = EventarcAsyncClient.get_transport_class(transport_name)( - credentials=async_anonymous_credentials(), +def test_transport_kind_grpc_asyncio(): + transport = EventarcAsyncClient.get_transport_class("grpc_asyncio")( + credentials=async_anonymous_credentials() + ) + assert transport.kind == "grpc_asyncio" + + +def test_transport_kind_rest(): + transport = EventarcClient.get_transport_class("rest")( + credentials=ga_credentials.AnonymousCredentials() ) - assert transport.kind == transport_name + assert transport.kind == "rest" def test_transport_grpc_default(): diff --git a/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py b/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py index 86f42cc044..17c35b4b5b 100755 --- a/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py +++ b/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py @@ -12335,25 +12335,18 @@ def test_transport_adc(transport_class): transport_class() adc.assert_called_once() -@pytest.mark.parametrize("transport_name", [ - "grpc", -]) -def test_transport_kind(transport_name): - transport = ConfigServiceV2Client.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), +def test_transport_kind_grpc(): + transport = ConfigServiceV2Client.get_transport_class("grpc")( + credentials=ga_credentials.AnonymousCredentials() ) - assert transport.kind == transport_name + assert transport.kind == "grpc" -@pytest.mark.parametrize("transport_name", [ - "grpc_asyncio", -]) -@pytest.mark.asyncio -async def test_transport_kind_async(transport_name): - transport = ConfigServiceV2AsyncClient.get_transport_class(transport_name)( - credentials=async_anonymous_credentials(), +def test_transport_kind_grpc_asyncio(): + transport = ConfigServiceV2AsyncClient.get_transport_class("grpc_asyncio")( + credentials=async_anonymous_credentials() ) - assert transport.kind == transport_name + assert transport.kind == "grpc_asyncio" def test_transport_grpc_default(): diff --git a/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py b/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py index 7602beb75d..c8b9bfa333 100755 --- a/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py +++ b/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py @@ -3099,25 +3099,18 @@ def test_transport_adc(transport_class): transport_class() adc.assert_called_once() -@pytest.mark.parametrize("transport_name", [ - "grpc", -]) -def test_transport_kind(transport_name): - transport = LoggingServiceV2Client.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), +def test_transport_kind_grpc(): + transport = LoggingServiceV2Client.get_transport_class("grpc")( + credentials=ga_credentials.AnonymousCredentials() ) - assert transport.kind == transport_name + assert transport.kind == "grpc" -@pytest.mark.parametrize("transport_name", [ - "grpc_asyncio", -]) -@pytest.mark.asyncio -async def test_transport_kind_async(transport_name): - transport = LoggingServiceV2AsyncClient.get_transport_class(transport_name)( - credentials=async_anonymous_credentials(), +def test_transport_kind_grpc_asyncio(): + transport = LoggingServiceV2AsyncClient.get_transport_class("grpc_asyncio")( + credentials=async_anonymous_credentials() ) - assert transport.kind == transport_name + assert transport.kind == "grpc_asyncio" def test_transport_grpc_default(): diff --git a/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py b/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py index 8ac1d16bae..1c391fcfc3 100755 --- a/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py +++ b/tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py @@ -2906,25 +2906,18 @@ def test_transport_adc(transport_class): transport_class() adc.assert_called_once() -@pytest.mark.parametrize("transport_name", [ - "grpc", -]) -def test_transport_kind(transport_name): - transport = MetricsServiceV2Client.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), +def test_transport_kind_grpc(): + transport = MetricsServiceV2Client.get_transport_class("grpc")( + credentials=ga_credentials.AnonymousCredentials() ) - assert transport.kind == transport_name + assert transport.kind == "grpc" -@pytest.mark.parametrize("transport_name", [ - "grpc_asyncio", -]) -@pytest.mark.asyncio -async def test_transport_kind_async(transport_name): - transport = MetricsServiceV2AsyncClient.get_transport_class(transport_name)( - credentials=async_anonymous_credentials(), +def test_transport_kind_grpc_asyncio(): + transport = MetricsServiceV2AsyncClient.get_transport_class("grpc_asyncio")( + credentials=async_anonymous_credentials() ) - assert transport.kind == transport_name + assert transport.kind == "grpc_asyncio" def test_transport_grpc_default(): diff --git a/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py b/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py index 925ff8da9f..2771c20739 100755 --- a/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py +++ b/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py @@ -49,6 +49,7 @@ from .transports.grpc import CloudRedisGrpcTransport from .transports.grpc_asyncio import CloudRedisGrpcAsyncIOTransport from .transports.rest import CloudRedisRestTransport +from .transports.rest_asyncio import AsyncCloudRedisRestTransport class CloudRedisClientMeta(type): @@ -62,6 +63,7 @@ class CloudRedisClientMeta(type): _transport_registry["grpc"] = CloudRedisGrpcTransport _transport_registry["grpc_asyncio"] = CloudRedisGrpcAsyncIOTransport _transport_registry["rest"] = CloudRedisRestTransport + _transport_registry["rest_asyncio"] = AsyncCloudRedisRestTransport def get_transport_class(cls, label: Optional[str] = None, 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 new file mode 100755 index 0000000000..5127656c34 --- /dev/null +++ b/tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest_asyncio.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from google.api_core import gapic_v1 + +from typing import Any, Optional + +from .rest_base import _BaseCloudRedisRestTransport + +from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO + +DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version, + grpc_version=None, + rest_version=None, +) + +class AsyncCloudRedisRestTransport(_BaseCloudRedisRestTransport): + """Asynchronous REST backend transport for CloudRedis. + + Configures and manages Cloud Memorystore for Redis instances + + Google Cloud Memorystore for Redis v1 + + The ``redis.googleapis.com`` service implements the Google Cloud + Memorystore for Redis API and defines the following resource model + for managing Redis instances: + + - The service works with a collection of cloud projects, named: + ``/projects/*`` + - Each project has a collection of available locations, named: + ``/locations/*`` + - Each location has a collection of Redis instances, named: + ``/instances/*`` + - As such, Redis instances are resources of the form: + ``/projects/{project_id}/locations/{location_id}/instances/{instance_id}`` + + Note that location_id must be referring to a GCP ``region``; for + example: + + - ``projects/redpepper-1290/locations/us-central1/instances/my-redis`` + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends JSON representations of protocol buffers over HTTP/1.1 + """ + def __init__(self, *, + host: str = 'redis.googleapis.com', + credentials: Optional[Any] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + url_scheme: str = 'https', + ) -> None: + """Instantiate the transport. + + NOTE: This async REST transport functionality is currently in a beta + state (preview). We welcome your feedback via a GitHub issue in + this library's repository. Thank you! + + Args: + host (Optional[str]): + The hostname to connect to (default: 'redis.googleapis.com'). + credentials (Optional[Any]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you are developing + your own client library. + url_scheme (str): the protocol scheme for the API endpoint. Normally + "https", but for testing or local servers, + "http" can be specified. + """ + # Run the base constructor + super().__init__( + host=host, + credentials=credentials, + client_info=client_info, + always_use_jwt_access=False, + url_scheme=url_scheme, + api_audience=None + ) + + @property + def kind(self) -> str: + return "rest_asyncio" 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 d9bacbf441..906e41d327 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 @@ -8403,26 +8403,32 @@ def test_transport_adc(transport_class): transport_class() adc.assert_called_once() -@pytest.mark.parametrize("transport_name", [ - "grpc", - "rest", -]) -def test_transport_kind(transport_name): - transport = CloudRedisClient.get_transport_class(transport_name)( - credentials=ga_credentials.AnonymousCredentials(), +def test_transport_kind_grpc(): + transport = CloudRedisClient.get_transport_class("grpc")( + credentials=ga_credentials.AnonymousCredentials() ) - assert transport.kind == transport_name + assert transport.kind == "grpc" -@pytest.mark.parametrize("transport_name", [ - "grpc_asyncio", -]) -@pytest.mark.asyncio -async def test_transport_kind_async(transport_name): - transport = CloudRedisAsyncClient.get_transport_class(transport_name)( - credentials=async_anonymous_credentials(), +def test_transport_kind_grpc_asyncio(): + transport = CloudRedisAsyncClient.get_transport_class("grpc_asyncio")( + credentials=async_anonymous_credentials() + ) + assert transport.kind == "grpc_asyncio" + + +def test_transport_kind_rest(): + transport = CloudRedisClient.get_transport_class("rest")( + credentials=ga_credentials.AnonymousCredentials() + ) + assert transport.kind == "rest" + + +def test_transport_kind_rest_asyncio(): + transport = CloudRedisAsyncClient.get_transport_class("rest_asyncio")( + credentials=async_anonymous_credentials() ) - assert transport.kind == transport_name + assert transport.kind == "rest_asyncio" def test_transport_grpc_default(): diff --git a/tests/integration/redis_v1.yaml b/tests/integration/redis_v1.yaml index 499c13d4e4..47440ccdad 100644 --- a/tests/integration/redis_v1.yaml +++ b/tests/integration/redis_v1.yaml @@ -67,4 +67,13 @@ authentication: - selector: 'google.longrunning.Operations.*' oauth: canonical_scopes: |- - https://www.googleapis.com/auth/cloud-platform \ No newline at end of file + https://www.googleapis.com/auth/cloud-platform + +# TODO(https://github.com/googleapis/gapic-generator-python/issues/2121): Remove this section +# when async rest is GA. +publishing: + library_settings: + - version: 'google.cloud.redis.v1' + python_settings: + experimental_features: + rest_async_io_enabled: true