feat: add support for asynchronous long running operations (#724)

* feat: implement `OperationsRestAsyncTransport` to support long running operations (#700)

* feat: Add OperationsRestAsyncTransport to support long running operations

* update TODO comment

* update TODO comment

* address feedback

* address feedback

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

* fix mypy and lint issues

* minor fix

* add no cover

* fix no cover tag

* link coverage issue

* silence coverage issue

* fix statement name error

* address PR feedback

* address PR feedback

* address PR comments

---------

Co-authored-by: ohmayr <omairnaveed@ymail.com>
Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>

* feat: implement async client for LROs (#707)

* feat: implement `AbstractOperationsAsyncClient` to support long running operations

* remove coverage guards

* address presubmit failures

* fix coverage for cancel operation

* tests cleanup

* fix incorrect tests

* file bugs

* add auth import

* address PR comments

* address PR comments

* fix unit tests and address more comments

* disable retry parameter

* add retry parameter

* address PR comments

---------

Co-authored-by: ohmayr <omairnaveed@ymail.com>
Co-authored-by: ohmayr <omairn@google.com>

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

---------

Co-authored-by: Anthonios Partheniou <partheniou@google.com>
Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
diff --git a/google/api_core/client_info.py b/google/api_core/client_info.py
index 4832679..90926be 100644
--- a/google/api_core/client_info.py
+++ b/google/api_core/client_info.py
@@ -57,7 +57,8 @@
         user_agent (Optional[str]): Prefix to the user agent header. This is
             used to supply information such as application name or partner tool.
             Recommended format: ``application-or-tool-ID/major.minor.version``.
-        rest_version (Optional[str]): The requests library version.
+        rest_version (Optional[str]): A string with labeled versions of the
+            dependencies used for REST transport.
     """
 
     def __init__(
diff --git a/google/api_core/gapic_v1/client_info.py b/google/api_core/gapic_v1/client_info.py
index 2de1be7..4516f33 100644
--- a/google/api_core/gapic_v1/client_info.py
+++ b/google/api_core/gapic_v1/client_info.py
@@ -45,6 +45,8 @@
         user_agent (Optional[str]): Prefix to the user agent header. This is
             used to supply information such as application name or partner tool.
             Recommended format: ``application-or-tool-ID/major.minor.version``.
+        rest_version (Optional[str]): A string with labeled versions of the
+            dependencies used for REST transport.
     """
 
     def to_grpc_metadata(self):
diff --git a/google/api_core/operations_v1/__init__.py b/google/api_core/operations_v1/__init__.py
index 8b75426..4db32a4 100644
--- a/google/api_core/operations_v1/__init__.py
+++ b/google/api_core/operations_v1/__init__.py
@@ -14,9 +14,7 @@
 
 """Package for interacting with the google.longrunning.operations meta-API."""
 
-from google.api_core.operations_v1.abstract_operations_client import (
-    AbstractOperationsClient,
-)
+from google.api_core.operations_v1.abstract_operations_client import AbstractOperationsClient
 from google.api_core.operations_v1.operations_async_client import OperationsAsyncClient
 from google.api_core.operations_v1.operations_client import OperationsClient
 from google.api_core.operations_v1.transports.rest import OperationsRestTransport
@@ -25,5 +23,18 @@
     "AbstractOperationsClient",
     "OperationsAsyncClient",
     "OperationsClient",
-    "OperationsRestTransport",
+    "OperationsRestTransport"
 ]
+
+try:
+    from google.api_core.operations_v1.transports.rest_asyncio import (
+        AsyncOperationsRestTransport,
+    )
+    from google.api_core.operations_v1.operations_rest_client_async import AsyncOperationsRestClient
+
+    __all__ += ["AsyncOperationsRestClient", "AsyncOperationsRestTransport"]
+except ImportError:
+    # This import requires the `async_rest` extra.
+    # Don't raise an exception if `AsyncOperationsRestTransport` cannot be imported
+    # as other transports are still available.
+    pass
diff --git a/google/api_core/operations_v1/abstract_operations_base_client.py b/google/api_core/operations_v1/abstract_operations_base_client.py
new file mode 100644
index 0000000..160c2a8
--- /dev/null
+++ b/google/api_core/operations_v1/abstract_operations_base_client.py
@@ -0,0 +1,370 @@
+# -*- 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 collections import OrderedDict
+import os
+import re
+from typing import Dict, Optional, Type, Union
+
+from google.api_core import client_options as client_options_lib  # type: ignore
+from google.api_core import gapic_v1  # type: ignore
+from google.api_core.operations_v1.transports.base import (
+    DEFAULT_CLIENT_INFO,
+    OperationsTransport,
+)
+from google.api_core.operations_v1.transports.rest import OperationsRestTransport
+
+try:
+    from google.api_core.operations_v1.transports.rest_asyncio import (
+        AsyncOperationsRestTransport,
+    )
+
+    HAS_ASYNC_REST_DEPENDENCIES = True
+except ImportError as e:
+    HAS_ASYNC_REST_DEPENDENCIES = False
+    ASYNC_REST_EXCEPTION = e
+
+from google.auth import credentials as ga_credentials  # type: ignore
+from google.auth.exceptions import MutualTLSChannelError  # type: ignore
+from google.auth.transport import mtls  # type: ignore
+
+
+class AbstractOperationsBaseClientMeta(type):
+    """Metaclass for the Operations Base client.
+
+    This provides base class-level methods for building and retrieving
+    support objects (e.g. transport) without polluting the client instance
+    objects.
+    """
+
+    _transport_registry = OrderedDict()  # type: Dict[str, Type[OperationsTransport]]
+    _transport_registry["rest"] = OperationsRestTransport
+    if HAS_ASYNC_REST_DEPENDENCIES:
+        _transport_registry["rest_asyncio"] = AsyncOperationsRestTransport
+
+    def get_transport_class(
+        cls,
+        label: Optional[str] = None,
+    ) -> Type[OperationsTransport]:
+        """Returns an appropriate transport class.
+
+        Args:
+            label: The name of the desired transport. If none is
+                provided, then the first transport in the registry is used.
+
+        Returns:
+            The transport class to use.
+        """
+        # If a specific transport is requested, return that one.
+        if (
+            label == "rest_asyncio" and not HAS_ASYNC_REST_DEPENDENCIES
+        ):  # pragma: NO COVER
+            raise ASYNC_REST_EXCEPTION
+
+        if label:
+            return cls._transport_registry[label]
+
+        # No transport is requested; return the default (that is, the first one
+        # in the dictionary).
+        return next(iter(cls._transport_registry.values()))
+
+
+class AbstractOperationsBaseClient(metaclass=AbstractOperationsBaseClientMeta):
+    """Manages long-running operations with an API service.
+
+    When an API method normally takes long time to complete, it can be
+    designed to return [Operation][google.api_core.operations_v1.Operation] to the
+    client, and the client can use this interface to receive the real
+    response asynchronously by polling the operation resource, or pass
+    the operation resource to another API (such as Google Cloud Pub/Sub
+    API) to receive the response. Any API service that returns
+    long-running operations should implement the ``Operations``
+    interface so developers can have a consistent client experience.
+    """
+
+    @staticmethod
+    def _get_default_mtls_endpoint(api_endpoint):
+        """Converts api endpoint to mTLS endpoint.
+
+        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
+        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
+        Args:
+            api_endpoint (Optional[str]): the api endpoint to convert.
+        Returns:
+            str: converted mTLS api endpoint.
+        """
+        if not api_endpoint:
+            return api_endpoint
+
+        mtls_endpoint_re = re.compile(
+            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
+        )
+
+        m = mtls_endpoint_re.match(api_endpoint)
+        name, mtls, sandbox, googledomain = m.groups()
+        if mtls or not googledomain:
+            return api_endpoint
+
+        if sandbox:
+            return api_endpoint.replace(
+                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
+            )
+
+        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")
+
+    DEFAULT_ENDPOINT = "longrunning.googleapis.com"
+    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
+        DEFAULT_ENDPOINT
+    )
+
+    @classmethod
+    def from_service_account_info(cls, info: dict, *args, **kwargs):
+        """
+        This class method should be overridden by the subclasses.
+
+        Args:
+            info (dict): The service account private key info.
+            args: Additional arguments to pass to the constructor.
+            kwargs: Additional arguments to pass to the constructor.
+
+        Raises:
+            NotImplementedError: If the method is called on the base class.
+        """
+        raise NotImplementedError("`from_service_account_info` is not implemented.")
+
+    @classmethod
+    def from_service_account_file(cls, filename: str, *args, **kwargs):
+        """
+        This class method should be overridden by the subclasses.
+
+        Args:
+            filename (str): The path to the service account private key json
+                file.
+            args: Additional arguments to pass to the constructor.
+            kwargs: Additional arguments to pass to the constructor.
+
+        Raises:
+            NotImplementedError: If the method is called on the base class.
+        """
+        raise NotImplementedError("`from_service_account_file` is not implemented.")
+
+    from_service_account_json = from_service_account_file
+
+    @property
+    def transport(self) -> OperationsTransport:
+        """Returns the transport used by the client instance.
+
+        Returns:
+            OperationsTransport: The transport used by the client
+                instance.
+        """
+        return self._transport
+
+    @staticmethod
+    def common_billing_account_path(
+        billing_account: str,
+    ) -> str:
+        """Returns a fully-qualified billing_account string."""
+        return "billingAccounts/{billing_account}".format(
+            billing_account=billing_account,
+        )
+
+    @staticmethod
+    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
+        """Parse a billing_account path into its component segments."""
+        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
+        return m.groupdict() if m else {}
+
+    @staticmethod
+    def common_folder_path(
+        folder: str,
+    ) -> str:
+        """Returns a fully-qualified folder string."""
+        return "folders/{folder}".format(
+            folder=folder,
+        )
+
+    @staticmethod
+    def parse_common_folder_path(path: str) -> Dict[str, str]:
+        """Parse a folder path into its component segments."""
+        m = re.match(r"^folders/(?P<folder>.+?)$", path)
+        return m.groupdict() if m else {}
+
+    @staticmethod
+    def common_organization_path(
+        organization: str,
+    ) -> str:
+        """Returns a fully-qualified organization string."""
+        return "organizations/{organization}".format(
+            organization=organization,
+        )
+
+    @staticmethod
+    def parse_common_organization_path(path: str) -> Dict[str, str]:
+        """Parse a organization path into its component segments."""
+        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
+        return m.groupdict() if m else {}
+
+    @staticmethod
+    def common_project_path(
+        project: str,
+    ) -> str:
+        """Returns a fully-qualified project string."""
+        return "projects/{project}".format(
+            project=project,
+        )
+
+    @staticmethod
+    def parse_common_project_path(path: str) -> Dict[str, str]:
+        """Parse a project path into its component segments."""
+        m = re.match(r"^projects/(?P<project>.+?)$", path)
+        return m.groupdict() if m else {}
+
+    @staticmethod
+    def common_location_path(
+        project: str,
+        location: str,
+    ) -> str:
+        """Returns a fully-qualified location string."""
+        return "projects/{project}/locations/{location}".format(
+            project=project,
+            location=location,
+        )
+
+    @staticmethod
+    def parse_common_location_path(path: str) -> Dict[str, str]:
+        """Parse a location path into its component segments."""
+        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
+        return m.groupdict() if m else {}
+
+    def __init__(
+        self,
+        *,
+        credentials: Optional[ga_credentials.Credentials] = None,
+        transport: Union[str, OperationsTransport, None] = None,
+        client_options: Optional[client_options_lib.ClientOptions] = None,
+        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
+    ) -> None:
+        """Instantiates the operations client.
+
+        Args:
+            credentials (Optional[google.auth.credentials.Credentials]): 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.
+            transport (Union[str, OperationsTransport]): The
+                transport to use. If set to None, a transport is chosen
+                automatically.
+            client_options (google.api_core.client_options.ClientOptions): Custom options for the
+                client. It won't take effect if a ``transport`` instance is provided.
+                (1) The ``api_endpoint`` property can be used to override the
+                default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT
+                environment variable can also be used to override the endpoint:
+                "always" (always use the default mTLS endpoint), "never" (always
+                use the default regular endpoint) and "auto" (auto switch to the
+                default mTLS endpoint if client certificate is present, this is
+                the default value). However, the ``api_endpoint`` property takes
+                precedence if provided.
+                (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
+                is "true", then the ``client_cert_source`` property can be used
+                to provide client certificate for mutual TLS transport. If
+                not provided, the default SSL client certificate will be used if
+                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
+                set, no client certificate will be used.
+            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're developing
+                your own client library.
+
+        Raises:
+            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
+                creation failed for any reason.
+        """
+        if isinstance(client_options, dict):
+            client_options = client_options_lib.from_dict(client_options)
+        if client_options is None:
+            client_options = client_options_lib.ClientOptions()
+
+        # Create SSL credentials for mutual TLS if needed.
+        use_client_cert = os.getenv(
+            "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
+        ).lower()
+        if use_client_cert not in ("true", "false"):
+            raise ValueError(
+                "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
+            )
+        client_cert_source_func = None
+        is_mtls = False
+        if use_client_cert == "true":
+            if client_options.client_cert_source:
+                is_mtls = True
+                client_cert_source_func = client_options.client_cert_source
+            else:
+                is_mtls = mtls.has_default_client_cert_source()
+                if is_mtls:
+                    client_cert_source_func = mtls.default_client_cert_source()
+                else:
+                    client_cert_source_func = None
+
+        # Figure out which api endpoint to use.
+        if client_options.api_endpoint is not None:
+            api_endpoint = client_options.api_endpoint
+        else:
+            use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
+            if use_mtls_env == "never":
+                api_endpoint = self.DEFAULT_ENDPOINT
+            elif use_mtls_env == "always":
+                api_endpoint = self.DEFAULT_MTLS_ENDPOINT
+            elif use_mtls_env == "auto":
+                if is_mtls:
+                    api_endpoint = self.DEFAULT_MTLS_ENDPOINT
+                else:
+                    api_endpoint = self.DEFAULT_ENDPOINT
+            else:
+                raise MutualTLSChannelError(
+                    "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted "
+                    "values: never, auto, always"
+                )
+
+        # Save or instantiate the transport.
+        # Ordinarily, we provide the transport, but allowing a custom transport
+        # instance provides an extensibility point for unusual situations.
+        if isinstance(transport, OperationsTransport):
+            # transport is a OperationsTransport instance.
+            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=api_endpoint,
+                scopes=client_options.scopes,
+                client_cert_source_for_mtls=client_cert_source_func,
+                quota_project_id=client_options.quota_project_id,
+                client_info=client_info,
+                always_use_jwt_access=True,
+            )
diff --git a/google/api_core/operations_v1/abstract_operations_client.py b/google/api_core/operations_v1/abstract_operations_client.py
index 38f532a..fc44536 100644
--- a/google/api_core/operations_v1/abstract_operations_client.py
+++ b/google/api_core/operations_v1/abstract_operations_client.py
@@ -13,10 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-from collections import OrderedDict
-import os
-import re
-from typing import Dict, Optional, Sequence, Tuple, Type, Union
+from typing import Optional, Sequence, Tuple, Union
 
 from google.api_core import client_options as client_options_lib  # type: ignore
 from google.api_core import gapic_v1  # type: ignore
@@ -26,10 +23,10 @@
     DEFAULT_CLIENT_INFO,
     OperationsTransport,
 )
-from google.api_core.operations_v1.transports.rest import OperationsRestTransport
+from google.api_core.operations_v1.abstract_operations_base_client import (
+    AbstractOperationsBaseClient,
+)
 from google.auth import credentials as ga_credentials  # type: ignore
-from google.auth.exceptions import MutualTLSChannelError  # type: ignore
-from google.auth.transport import mtls  # type: ignore
 from google.longrunning import operations_pb2
 from google.oauth2 import service_account  # type: ignore
 import grpc
@@ -37,40 +34,7 @@
 OptionalRetry = Union[retries.Retry, object]
 
 
-class AbstractOperationsClientMeta(type):
-    """Metaclass for the Operations client.
-
-    This provides class-level methods for building and retrieving
-    support objects (e.g. transport) without polluting the client instance
-    objects.
-    """
-
-    _transport_registry = OrderedDict()  # type: Dict[str, Type[OperationsTransport]]
-    _transport_registry["rest"] = OperationsRestTransport
-
-    def get_transport_class(
-        cls,
-        label: Optional[str] = None,
-    ) -> Type[OperationsTransport]:
-        """Returns an appropriate transport class.
-
-        Args:
-            label: The name of the desired transport. If none is
-                provided, then the first transport in the registry is used.
-
-        Returns:
-            The transport class to use.
-        """
-        # If a specific transport is requested, return that one.
-        if label:
-            return cls._transport_registry[label]
-
-        # No transport is requested; return the default (that is, the first one
-        # in the dictionary).
-        return next(iter(cls._transport_registry.values()))
-
-
-class AbstractOperationsClient(metaclass=AbstractOperationsClientMeta):
+class AbstractOperationsClient(AbstractOperationsBaseClient):
     """Manages long-running operations with an API service.
 
     When an API method normally takes long time to complete, it can be
@@ -83,165 +47,6 @@
     interface so developers can have a consistent client experience.
     """
 
-    @staticmethod
-    def _get_default_mtls_endpoint(api_endpoint):
-        """Converts api endpoint to mTLS endpoint.
-
-        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
-        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
-        Args:
-            api_endpoint (Optional[str]): the api endpoint to convert.
-        Returns:
-            str: converted mTLS api endpoint.
-        """
-        if not api_endpoint:
-            return api_endpoint
-
-        mtls_endpoint_re = re.compile(
-            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
-        )
-
-        m = mtls_endpoint_re.match(api_endpoint)
-        name, mtls, sandbox, googledomain = m.groups()
-        if mtls or not googledomain:
-            return api_endpoint
-
-        if sandbox:
-            return api_endpoint.replace(
-                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
-            )
-
-        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")
-
-    DEFAULT_ENDPOINT = "longrunning.googleapis.com"
-    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore
-        DEFAULT_ENDPOINT
-    )
-
-    @classmethod
-    def from_service_account_info(cls, info: dict, *args, **kwargs):
-        """Creates an instance of this client using the provided credentials
-            info.
-
-        Args:
-            info (dict): The service account private key info.
-            args: Additional arguments to pass to the constructor.
-            kwargs: Additional arguments to pass to the constructor.
-
-        Returns:
-            AbstractOperationsClient: The constructed client.
-        """
-        credentials = service_account.Credentials.from_service_account_info(info)
-        kwargs["credentials"] = credentials
-        return cls(*args, **kwargs)
-
-    @classmethod
-    def from_service_account_file(cls, filename: str, *args, **kwargs):
-        """Creates an instance of this client using the provided credentials
-            file.
-
-        Args:
-            filename (str): The path to the service account private key json
-                file.
-            args: Additional arguments to pass to the constructor.
-            kwargs: Additional arguments to pass to the constructor.
-
-        Returns:
-            AbstractOperationsClient: The constructed client.
-        """
-        credentials = service_account.Credentials.from_service_account_file(filename)
-        kwargs["credentials"] = credentials
-        return cls(*args, **kwargs)
-
-    from_service_account_json = from_service_account_file
-
-    @property
-    def transport(self) -> OperationsTransport:
-        """Returns the transport used by the client instance.
-
-        Returns:
-            OperationsTransport: The transport used by the client
-                instance.
-        """
-        return self._transport
-
-    @staticmethod
-    def common_billing_account_path(
-        billing_account: str,
-    ) -> str:
-        """Returns a fully-qualified billing_account string."""
-        return "billingAccounts/{billing_account}".format(
-            billing_account=billing_account,
-        )
-
-    @staticmethod
-    def parse_common_billing_account_path(path: str) -> Dict[str, str]:
-        """Parse a billing_account path into its component segments."""
-        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path)
-        return m.groupdict() if m else {}
-
-    @staticmethod
-    def common_folder_path(
-        folder: str,
-    ) -> str:
-        """Returns a fully-qualified folder string."""
-        return "folders/{folder}".format(
-            folder=folder,
-        )
-
-    @staticmethod
-    def parse_common_folder_path(path: str) -> Dict[str, str]:
-        """Parse a folder path into its component segments."""
-        m = re.match(r"^folders/(?P<folder>.+?)$", path)
-        return m.groupdict() if m else {}
-
-    @staticmethod
-    def common_organization_path(
-        organization: str,
-    ) -> str:
-        """Returns a fully-qualified organization string."""
-        return "organizations/{organization}".format(
-            organization=organization,
-        )
-
-    @staticmethod
-    def parse_common_organization_path(path: str) -> Dict[str, str]:
-        """Parse a organization path into its component segments."""
-        m = re.match(r"^organizations/(?P<organization>.+?)$", path)
-        return m.groupdict() if m else {}
-
-    @staticmethod
-    def common_project_path(
-        project: str,
-    ) -> str:
-        """Returns a fully-qualified project string."""
-        return "projects/{project}".format(
-            project=project,
-        )
-
-    @staticmethod
-    def parse_common_project_path(path: str) -> Dict[str, str]:
-        """Parse a project path into its component segments."""
-        m = re.match(r"^projects/(?P<project>.+?)$", path)
-        return m.groupdict() if m else {}
-
-    @staticmethod
-    def common_location_path(
-        project: str,
-        location: str,
-    ) -> str:
-        """Returns a fully-qualified location string."""
-        return "projects/{project}/locations/{location}".format(
-            project=project,
-            location=location,
-        )
-
-    @staticmethod
-    def parse_common_location_path(path: str) -> Dict[str, str]:
-        """Parse a location path into its component segments."""
-        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
-        return m.groupdict() if m else {}
-
     def __init__(
         self,
         *,
@@ -287,80 +92,49 @@
             google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
                 creation failed for any reason.
         """
-        if isinstance(client_options, dict):
-            client_options = client_options_lib.from_dict(client_options)
-        if client_options is None:
-            client_options = client_options_lib.ClientOptions()
+        super().__init__(
+            credentials=credentials,
+            transport=transport,
+            client_options=client_options,
+            client_info=client_info,
+        )
 
-        # Create SSL credentials for mutual TLS if needed.
-        use_client_cert = os.getenv(
-            "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
-        ).lower()
-        if use_client_cert not in ("true", "false"):
-            raise ValueError(
-                "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
-            )
-        client_cert_source_func = None
-        is_mtls = False
-        if use_client_cert == "true":
-            if client_options.client_cert_source:
-                is_mtls = True
-                client_cert_source_func = client_options.client_cert_source
-            else:
-                is_mtls = mtls.has_default_client_cert_source()
-                if is_mtls:
-                    client_cert_source_func = mtls.default_client_cert_source()
-                else:
-                    client_cert_source_func = None
+    @classmethod
+    def from_service_account_info(cls, info: dict, *args, **kwargs):
+        """Creates an instance of this client using the provided credentials
+            info.
 
-        # Figure out which api endpoint to use.
-        if client_options.api_endpoint is not None:
-            api_endpoint = client_options.api_endpoint
-        else:
-            use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
-            if use_mtls_env == "never":
-                api_endpoint = self.DEFAULT_ENDPOINT
-            elif use_mtls_env == "always":
-                api_endpoint = self.DEFAULT_MTLS_ENDPOINT
-            elif use_mtls_env == "auto":
-                if is_mtls:
-                    api_endpoint = self.DEFAULT_MTLS_ENDPOINT
-                else:
-                    api_endpoint = self.DEFAULT_ENDPOINT
-            else:
-                raise MutualTLSChannelError(
-                    "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted "
-                    "values: never, auto, always"
-                )
+        Args:
+            info (dict): The service account private key info.
+            args: Additional arguments to pass to the constructor.
+            kwargs: Additional arguments to pass to the constructor.
 
-        # Save or instantiate the transport.
-        # Ordinarily, we provide the transport, but allowing a custom transport
-        # instance provides an extensibility point for unusual situations.
-        if isinstance(transport, OperationsTransport):
-            # transport is a OperationsTransport instance.
-            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=api_endpoint,
-                scopes=client_options.scopes,
-                client_cert_source_for_mtls=client_cert_source_func,
-                quota_project_id=client_options.quota_project_id,
-                client_info=client_info,
-                always_use_jwt_access=True,
-            )
+        Returns:
+            AbstractOperationsClient: The constructed client.
+        """
+        credentials = service_account.Credentials.from_service_account_info(info)
+        kwargs["credentials"] = credentials
+        return cls(*args, **kwargs)
+
+    @classmethod
+    def from_service_account_file(cls, filename: str, *args, **kwargs):
+        """Creates an instance of this client using the provided credentials
+            file.
+
+        Args:
+            filename (str): The path to the service account private key json
+                file.
+            args: Additional arguments to pass to the constructor.
+            kwargs: Additional arguments to pass to the constructor.
+
+        Returns:
+            AbstractOperationsClient: The constructed client.
+        """
+        credentials = service_account.Credentials.from_service_account_file(filename)
+        kwargs["credentials"] = credentials
+        return cls(*args, **kwargs)
+
+    from_service_account_json = from_service_account_file
 
     def list_operations(
         self,
diff --git a/google/api_core/operations_v1/operations_rest_client_async.py b/google/api_core/operations_v1/operations_rest_client_async.py
new file mode 100644
index 0000000..7ab0cd3
--- /dev/null
+++ b/google/api_core/operations_v1/operations_rest_client_async.py
@@ -0,0 +1,345 @@
+# -*- 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 typing import Optional, Sequence, Tuple, Union
+
+from google.api_core import client_options as client_options_lib  # type: ignore
+from google.api_core import gapic_v1  # type: ignore
+from google.api_core.operations_v1 import pagers_async as pagers
+from google.api_core.operations_v1.transports.base import (
+    DEFAULT_CLIENT_INFO,
+    OperationsTransport,
+)
+from google.api_core.operations_v1.abstract_operations_base_client import (
+    AbstractOperationsBaseClient,
+)
+from google.longrunning import operations_pb2
+
+try:
+    from google.auth.aio import credentials as ga_credentials  # type: ignore
+except ImportError as e:  # pragma: NO COVER
+    raise ImportError(
+        "The `async_rest` extra of `google-api-core` is required to use long-running operations.  Install it by running "
+        "`pip install google-api-core[async_rest]`."
+    ) from e
+
+
+class AsyncOperationsRestClient(AbstractOperationsBaseClient):
+    """Manages long-running operations with a REST API service for the asynchronous client.
+
+    When an API method normally takes long time to complete, it can be
+    designed to return [Operation][google.api_core.operations_v1.Operation] to the
+    client, and the client can use this interface to receive the real
+    response asynchronously by polling the operation resource, or pass
+    the operation resource to another API (such as Google Cloud Pub/Sub
+    API) to receive the response. Any API service that returns
+    long-running operations should implement the ``Operations``
+    interface so developers can have a consistent client experience.
+    """
+
+    def __init__(
+        self,
+        *,
+        credentials: Optional[ga_credentials.Credentials] = None,
+        transport: Union[str, OperationsTransport, None] = None,
+        client_options: Optional[client_options_lib.ClientOptions] = None,
+        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
+    ) -> None:
+        """Instantiates the operations client.
+
+        Args:
+            credentials (Optional[google.auth.aio.credentials.Credentials]): 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.
+            transport (Union[str, OperationsTransport]): The
+                transport to use. If set to None, this defaults to 'rest_asyncio'.
+            client_options (google.api_core.client_options.ClientOptions): Custom options for the
+                client. It won't take effect if a ``transport`` instance is provided.
+                (1) The ``api_endpoint`` property can be used to override the
+                default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT
+                environment variable can also be used to override the endpoint:
+                "always" (always use the default mTLS endpoint), "never" (always
+                use the default regular endpoint) and "auto" (auto switch to the
+                default mTLS endpoint if client certificate is present, this is
+                the default value). However, the ``api_endpoint`` property takes
+                precedence if provided.
+                (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable
+                is "true", then the ``client_cert_source`` property can be used
+                to provide client certificate for mutual TLS transport. If
+                not provided, the default SSL client certificate will be used if
+                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not
+                set, no client certificate will be used.
+            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're developing
+                your own client library.
+
+        Raises:
+            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
+                creation failed for any reason.
+        """
+        super().__init__(
+            credentials=credentials,  # type: ignore
+            # NOTE: If a transport is not provided, we force the client to use the async
+            # REST transport.
+            transport=transport or "rest_asyncio",
+            client_options=client_options,
+            client_info=client_info,
+        )
+
+    async def get_operation(
+        self,
+        name: str,
+        *,
+        # TODO(https://github.com/googleapis/python-api-core/issues/722): Leverage `retry`
+        # to allow configuring retryable error codes.
+        retry=gapic_v1.method_async.DEFAULT,
+        timeout: Optional[float] = None,
+        metadata: Sequence[Tuple[str, str]] = (),
+    ) -> operations_pb2.Operation:
+        r"""Gets the latest state of a long-running operation.
+        Clients can use this method to poll the operation result
+        at intervals as recommended by the API service.
+
+        Args:
+            name (str):
+                The name of the operation resource.
+            timeout (float): The timeout for this request.
+            metadata (Sequence[Tuple[str, str]]): Strings which should be
+                sent along with the request as metadata.
+
+        Returns:
+            google.longrunning.operations_pb2.Operation:
+                This resource represents a long-
+                running operation that is the result of a
+                network API call.
+
+        """
+
+        request = operations_pb2.GetOperationRequest(name=name)
+
+        # Wrap the RPC method; this adds retry and timeout information,
+        # and friendly error handling.
+        rpc = self._transport._wrapped_methods[self._transport.get_operation]
+
+        # Certain fields should be provided within the metadata header;
+        # add these here.
+        metadata = tuple(metadata or ()) + (
+            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
+        )
+
+        # Send the request.
+        response = await rpc(
+            request,
+            retry=retry,
+            timeout=timeout,
+            metadata=metadata,
+        )
+
+        # Done; return the response.
+        return response
+
+    async def list_operations(
+        self,
+        name: str,
+        filter_: Optional[str] = None,
+        *,
+        page_size: Optional[int] = None,
+        page_token: Optional[str] = None,
+        # TODO(https://github.com/googleapis/python-api-core/issues/722): Leverage `retry`
+        # to allow configuring retryable error codes.
+        retry=gapic_v1.method_async.DEFAULT,
+        timeout: Optional[float] = None,
+        metadata: Sequence[Tuple[str, str]] = (),
+    ) -> pagers.ListOperationsAsyncPager:
+        r"""Lists operations that match the specified filter in the request.
+        If the server doesn't support this method, it returns
+        ``UNIMPLEMENTED``.
+
+        NOTE: the ``name`` binding allows API services to override the
+        binding to use different resource name schemes, such as
+        ``users/*/operations``. To override the binding, API services
+        can add a binding such as ``"/v1/{name=users/*}/operations"`` to
+        their service configuration. For backwards compatibility, the
+        default name includes the operations collection id, however
+        overriding users must ensure the name binding is the parent
+        resource, without the operations collection id.
+
+        Args:
+            name (str):
+                The name of the operation's parent
+                resource.
+            filter_ (str):
+                The standard list filter.
+                This corresponds to the ``filter`` field
+                on the ``request`` instance; if ``request`` is provided, this
+                should not be set.
+            timeout (float): The timeout for this request.
+            metadata (Sequence[Tuple[str, str]]): Strings which should be
+                sent along with the request as metadata.
+
+        Returns:
+            google.api_core.operations_v1.pagers.ListOperationsPager:
+                The response message for
+                [Operations.ListOperations][google.api_core.operations_v1.Operations.ListOperations].
+
+                Iterating over this object will yield results and
+                resolve additional pages automatically.
+
+        """
+        # Create a protobuf request object.
+        request = operations_pb2.ListOperationsRequest(name=name, filter=filter_)
+        if page_size is not None:
+            request.page_size = page_size
+        if page_token is not None:
+            request.page_token = page_token
+
+        # Wrap the RPC method; this adds retry and timeout information,
+        # and friendly error handling.
+        rpc = self._transport._wrapped_methods[self._transport.list_operations]
+
+        # Certain fields should be provided within the metadata header;
+        # add these here.
+        metadata = tuple(metadata or ()) + (
+            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
+        )
+
+        # Send the request.
+        response = await rpc(
+            request,
+            retry=retry,
+            timeout=timeout,
+            metadata=metadata,
+        )
+
+        # This method is paged; wrap the response in a pager, which provides
+        # an `__iter__` convenience method.
+        response = pagers.ListOperationsAsyncPager(
+            method=rpc,
+            request=request,
+            response=response,
+            metadata=metadata,
+        )
+
+        # Done; return the response.
+        return response
+
+    async def delete_operation(
+        self,
+        name: str,
+        *,
+        # TODO(https://github.com/googleapis/python-api-core/issues/722): Leverage `retry`
+        # to allow configuring retryable error codes.
+        retry=gapic_v1.method_async.DEFAULT,
+        timeout: Optional[float] = None,
+        metadata: Sequence[Tuple[str, str]] = (),
+    ) -> None:
+        r"""Deletes a long-running operation. This method indicates that the
+        client is no longer interested in the operation result. It does
+        not cancel the operation. If the server doesn't support this
+        method, it returns ``google.rpc.Code.UNIMPLEMENTED``.
+
+        Args:
+            name (str):
+                The name of the operation resource to
+                be deleted.
+
+                This corresponds to the ``name`` field
+                on the ``request`` instance; if ``request`` is provided, this
+                should not be set.
+            timeout (float): The timeout for this request.
+            metadata (Sequence[Tuple[str, str]]): Strings which should be
+                sent along with the request as metadata.
+        """
+        # Create the request object.
+        request = operations_pb2.DeleteOperationRequest(name=name)
+
+        # Wrap the RPC method; this adds retry and timeout information,
+        # and friendly error handling.
+        rpc = self._transport._wrapped_methods[self._transport.delete_operation]
+
+        # Certain fields should be provided within the metadata header;
+        # add these here.
+        metadata = tuple(metadata or ()) + (
+            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
+        )
+
+        # Send the request.
+        await rpc(
+            request,
+            retry=retry,
+            timeout=timeout,
+            metadata=metadata,
+        )
+
+    async def cancel_operation(
+        self,
+        name: Optional[str] = None,
+        *,
+        # TODO(https://github.com/googleapis/python-api-core/issues/722): Leverage `retry`
+        # to allow configuring retryable error codes.
+        retry=gapic_v1.method_async.DEFAULT,
+        timeout: Optional[float] = None,
+        metadata: Sequence[Tuple[str, str]] = (),
+    ) -> None:
+        r"""Starts asynchronous cancellation on a long-running operation.
+        The server makes a best effort to cancel the operation, but
+        success is not guaranteed. If the server doesn't support this
+        method, it returns ``google.rpc.Code.UNIMPLEMENTED``. Clients
+        can use
+        [Operations.GetOperation][google.api_core.operations_v1.Operations.GetOperation]
+        or other methods to check whether the cancellation succeeded or
+        whether the operation completed despite cancellation. On
+        successful cancellation, the operation is not deleted; instead,
+        it becomes an operation with an
+        [Operation.error][google.api_core.operations_v1.Operation.error] value with
+        a [google.rpc.Status.code][google.rpc.Status.code] of 1,
+        corresponding to ``Code.CANCELLED``.
+
+        Args:
+            name (str):
+                The name of the operation resource to
+                be cancelled.
+
+                This corresponds to the ``name`` field
+                on the ``request`` instance; if ``request`` is provided, this
+                should not be set.
+            timeout (float): The timeout for this request.
+            metadata (Sequence[Tuple[str, str]]): Strings which should be
+                sent along with the request as metadata.
+        """
+        # Create the request object.
+        request = operations_pb2.CancelOperationRequest(name=name)
+
+        # Wrap the RPC method; this adds retry and timeout information,
+        # and friendly error handling.
+        rpc = self._transport._wrapped_methods[self._transport.cancel_operation]
+
+        # Certain fields should be provided within the metadata header;
+        # add these here.
+        metadata = tuple(metadata or ()) + (
+            gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)),
+        )
+
+        # Send the request.
+        await rpc(
+            request,
+            retry=retry,
+            timeout=timeout,
+            metadata=metadata,
+        )
diff --git a/google/api_core/operations_v1/pagers.py b/google/api_core/operations_v1/pagers.py
index b8a4775..132f1c6 100644
--- a/google/api_core/operations_v1/pagers.py
+++ b/google/api_core/operations_v1/pagers.py
@@ -14,7 +14,6 @@
 # limitations under the License.
 #
 from typing import (
-    Any,
     Callable,
     Iterator,
     Sequence,
@@ -22,9 +21,10 @@
 )
 
 from google.longrunning import operations_pb2
+from google.api_core.operations_v1.pagers_base import ListOperationsPagerBase
 
 
-class ListOperationsPager:
+class ListOperationsPager(ListOperationsPagerBase):
     """A pager for iterating through ``list_operations`` requests.
 
     This class thinly wraps an initial
@@ -50,25 +50,9 @@
         *,
         metadata: Sequence[Tuple[str, str]] = ()
     ):
-        """Instantiate the pager.
-
-        Args:
-            method (Callable): The method that was originally called, and
-                which instantiated this pager.
-            request (google.longrunning.operations_pb2.ListOperationsRequest):
-                The initial request object.
-            response (google.longrunning.operations_pb2.ListOperationsResponse):
-                The initial response object.
-            metadata (Sequence[Tuple[str, str]]): Strings which should be
-                sent along with the request as metadata.
-        """
-        self._method = method
-        self._request = request
-        self._response = response
-        self._metadata = metadata
-
-    def __getattr__(self, name: str) -> Any:
-        return getattr(self._response, name)
+        super().__init__(
+            method=method, request=request, response=response, metadata=metadata
+        )
 
     @property
     def pages(self) -> Iterator[operations_pb2.ListOperationsResponse]:
@@ -81,6 +65,3 @@
     def __iter__(self) -> Iterator[operations_pb2.Operation]:
         for page in self.pages:
             yield from page.operations
-
-    def __repr__(self) -> str:
-        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)
diff --git a/google/api_core/operations_v1/pagers_async.py b/google/api_core/operations_v1/pagers_async.py
new file mode 100644
index 0000000..e2909dd
--- /dev/null
+++ b/google/api_core/operations_v1/pagers_async.py
@@ -0,0 +1,71 @@
+# -*- 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 typing import (
+    Callable,
+    AsyncIterator,
+    Sequence,
+    Tuple,
+)
+
+from google.longrunning import operations_pb2
+from google.api_core.operations_v1.pagers_base import ListOperationsPagerBase
+
+
+class ListOperationsAsyncPager(ListOperationsPagerBase):
+    """A pager for iterating through ``list_operations`` requests.
+
+    This class thinly wraps an initial
+    :class:`google.longrunning.operations_pb2.ListOperationsResponse` object, and
+    provides an ``__iter__`` method to iterate through its
+    ``operations`` field.
+
+    If there are more pages, the ``__iter__`` method will make additional
+    ``ListOperations`` requests and continue to iterate
+    through the ``operations`` field on the
+    corresponding responses.
+
+    All the usual :class:`google.longrunning.operations_pb2.ListOperationsResponse`
+    attributes are available on the pager. If multiple requests are made, only
+    the most recent response is retained, and thus used for attribute lookup.
+    """
+
+    def __init__(
+        self,
+        method: Callable[..., operations_pb2.ListOperationsResponse],
+        request: operations_pb2.ListOperationsRequest,
+        response: operations_pb2.ListOperationsResponse,
+        *,
+        metadata: Sequence[Tuple[str, str]] = ()
+    ):
+        super().__init__(
+            method=method, request=request, response=response, metadata=metadata
+        )
+
+    @property
+    async def pages(self) -> AsyncIterator[operations_pb2.ListOperationsResponse]:
+        yield self._response
+        while self._response.next_page_token:
+            self._request.page_token = self._response.next_page_token
+            self._response = await self._method(self._request, metadata=self._metadata)
+            yield self._response
+
+    def __aiter__(self) -> AsyncIterator[operations_pb2.Operation]:
+        async def async_generator():
+            async for page in self.pages:
+                for operation in page.operations:
+                    yield operation
+
+        return async_generator()
diff --git a/google/api_core/operations_v1/pagers_base.py b/google/api_core/operations_v1/pagers_base.py
new file mode 100644
index 0000000..24caf74
--- /dev/null
+++ b/google/api_core/operations_v1/pagers_base.py
@@ -0,0 +1,73 @@
+# -*- 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 typing import (
+    Any,
+    Callable,
+    Sequence,
+    Tuple,
+)
+
+from google.longrunning import operations_pb2
+
+
+class ListOperationsPagerBase:
+    """A pager for iterating through ``list_operations`` requests.
+
+    This class thinly wraps an initial
+    :class:`google.longrunning.operations_pb2.ListOperationsResponse` object, and
+    provides an ``__iter__`` method to iterate through its
+    ``operations`` field.
+
+    If there are more pages, the ``__iter__`` method will make additional
+    ``ListOperations`` requests and continue to iterate
+    through the ``operations`` field on the
+    corresponding responses.
+
+    All the usual :class:`google.longrunning.operations_pb2.ListOperationsResponse`
+    attributes are available on the pager. If multiple requests are made, only
+    the most recent response is retained, and thus used for attribute lookup.
+    """
+
+    def __init__(
+        self,
+        method: Callable[..., operations_pb2.ListOperationsResponse],
+        request: operations_pb2.ListOperationsRequest,
+        response: operations_pb2.ListOperationsResponse,
+        *,
+        metadata: Sequence[Tuple[str, str]] = ()
+    ):
+        """Instantiate the pager.
+
+        Args:
+            method (Callable): The method that was originally called, and
+                which instantiated this pager.
+            request (google.longrunning.operations_pb2.ListOperationsRequest):
+                The initial request object.
+            response (google.longrunning.operations_pb2.ListOperationsResponse):
+                The initial response object.
+            metadata (Sequence[Tuple[str, str]]): Strings which should be
+                sent along with the request as metadata.
+        """
+        self._method = method
+        self._request = request
+        self._response = response
+        self._metadata = metadata
+
+    def __getattr__(self, name: str) -> Any:
+        return getattr(self._response, name)
+
+    def __repr__(self) -> str:
+        return "{0}<{1!r}>".format(self.__class__.__name__, self._response)
diff --git a/google/api_core/operations_v1/transports/__init__.py b/google/api_core/operations_v1/transports/__init__.py
index df53e15..8c24ce6 100644
--- a/google/api_core/operations_v1/transports/__init__.py
+++ b/google/api_core/operations_v1/transports/__init__.py
@@ -14,16 +14,26 @@
 # limitations under the License.
 #
 from collections import OrderedDict
+from typing import cast, Dict, Tuple
 
 from .base import OperationsTransport
 from .rest import OperationsRestTransport
 
-
 # Compile a registry of transports.
-_transport_registry = OrderedDict()
-_transport_registry["rest"] = OperationsRestTransport
+_transport_registry: Dict[str, OperationsTransport] = OrderedDict()
+_transport_registry["rest"] = cast(OperationsTransport, OperationsRestTransport)
 
-__all__ = (
-    "OperationsTransport",
-    "OperationsRestTransport",
-)
+__all__: Tuple[str, ...] = ("OperationsTransport", "OperationsRestTransport")
+
+try:
+    from .rest_asyncio import AsyncOperationsRestTransport
+
+    __all__ += ("AsyncOperationsRestTransport",)
+    _transport_registry["rest_asyncio"] = cast(
+        OperationsTransport, AsyncOperationsRestTransport
+    )
+except ImportError:
+    # This import requires the `async_rest` extra.
+    # Don't raise an exception if `AsyncOperationsRestTransport` cannot be imported
+    # as other transports are still available.
+    pass
diff --git a/google/api_core/operations_v1/transports/base.py b/google/api_core/operations_v1/transports/base.py
index fb1d4fc..50e1376 100644
--- a/google/api_core/operations_v1/transports/base.py
+++ b/google/api_core/operations_v1/transports/base.py
@@ -14,6 +14,7 @@
 # limitations under the License.
 #
 import abc
+import re
 from typing import Awaitable, Callable, Optional, Sequence, Union
 
 import google.api_core  # type: ignore
@@ -25,10 +26,13 @@
 from google.auth import credentials as ga_credentials  # type: ignore
 from google.longrunning import operations_pb2
 from google.oauth2 import service_account  # type: ignore
-from google.protobuf import empty_pb2  # type: ignore
+import google.protobuf
+from google.protobuf import empty_pb2, json_format  # type: ignore
 from grpc import Compression
 
 
+PROTOBUF_VERSION = google.protobuf.__version__
+
 DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
     gapic_version=version.__version__,
 )
@@ -45,12 +49,14 @@
         self,
         *,
         host: str = DEFAULT_HOST,
+        # TODO(https://github.com/googleapis/python-api-core/issues/709): update type hint for credentials to include `google.auth.aio.Credentials`.
         credentials: Optional[ga_credentials.Credentials] = None,
         credentials_file: Optional[str] = None,
         scopes: Optional[Sequence[str]] = None,
         quota_project_id: Optional[str] = None,
         client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
         always_use_jwt_access: Optional[bool] = False,
+        url_scheme="https",
         **kwargs,
     ) -> None:
         """Instantiate the transport.
@@ -76,10 +82,23 @@
                 your own client library.
             always_use_jwt_access (Optional[bool]): Whether self signed JWT should
                 be used for service account credentials.
+            url_scheme: the protocol scheme for the API endpoint.  Normally
+                "https", but for testing or local servers,
+                "http" can be specified.
         """
+        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
+        if maybe_url_match is None:
+            raise ValueError(
+                f"Unexpected hostname structure: {host}"
+            )  # pragma: NO COVER
+
+        url_match_items = maybe_url_match.groupdict()
+
+        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host
+
         # Save the hostname. Default to port 443 (HTTPS) if none is specified.
         if ":" not in host:
-            host += ":443"
+            host += ":443"  # pragma: NO COVER
         self._host = host
 
         scopes_kwargs = {"scopes": scopes, "default_scopes": self.AUTH_SCOPES}
@@ -189,6 +208,37 @@
         """
         raise NotImplementedError()
 
+    def _convert_protobuf_message_to_dict(
+        self, message: google.protobuf.message.Message
+    ):
+        r"""Converts protobuf message to a dictionary.
+
+        When the dictionary is encoded to JSON, it conforms to proto3 JSON spec.
+
+        Args:
+            message(google.protobuf.message.Message): The protocol buffers message
+                instance to serialize.
+
+        Returns:
+            A dict representation of the protocol buffer message.
+        """
+        # TODO(https://github.com/googleapis/python-api-core/issues/643): For backwards compatibility
+        # with protobuf 3.x 4.x, Remove once support for protobuf 3.x and 4.x is dropped.
+        if PROTOBUF_VERSION[0:2] in ["3.", "4."]:
+            result = json_format.MessageToDict(
+                message,
+                preserving_proto_field_name=True,
+                including_default_value_fields=True,  # type: ignore # backward compatibility
+            )
+        else:
+            result = json_format.MessageToDict(
+                message,
+                preserving_proto_field_name=True,
+                always_print_fields_with_no_presence=True,
+            )
+
+        return result
+
     @property
     def list_operations(
         self,
diff --git a/google/api_core/operations_v1/transports/rest.py b/google/api_core/operations_v1/transports/rest.py
index f37bb34..766a668 100644
--- a/google/api_core/operations_v1/transports/rest.py
+++ b/google/api_core/operations_v1/transports/rest.py
@@ -14,7 +14,6 @@
 # limitations under the License.
 #
 
-import re
 from typing import Callable, Dict, Optional, Sequence, Tuple, Union
 
 from requests import __version__ as requests_version
@@ -41,7 +40,7 @@
 DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
     gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
     grpc_version=None,
-    rest_version=requests_version,
+    rest_version=f"requests@{requests_version}",
 )
 
 
@@ -123,16 +122,6 @@
         # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
         # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
         # credentials object
-        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
-        if maybe_url_match is None:
-            raise ValueError(
-                f"Unexpected hostname structure: {host}"
-            )  # pragma: NO COVER
-
-        url_match_items = maybe_url_match.groupdict()
-
-        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host
-
         super().__init__(
             host=host,
             credentials=credentials,
@@ -144,6 +133,7 @@
         )
         if client_cert_source_for_mtls:
             self._session.configure_mtls_channel(client_cert_source_for_mtls)
+        # TODO(https://github.com/googleapis/python-api-core/issues/720): Add wrap logic directly to the property methods for callables.
         self._prep_wrapped_messages(client_info)
         self._http_options = http_options or {}
         self._path_prefix = path_prefix
@@ -152,6 +142,8 @@
         self,
         request: operations_pb2.ListOperationsRequest,
         *,
+        # TODO(https://github.com/googleapis/python-api-core/issues/723): Leverage `retry`
+        # to allow configuring retryable error codes.
         retry: OptionalRetry = gapic_v1.method.DEFAULT,
         timeout: Optional[float] = None,
         compression: Optional[grpc.Compression] = gapic_v1.method.DEFAULT,
@@ -206,6 +198,7 @@
         # Send the request
         headers = dict(metadata)
         headers["Content-Type"] = "application/json"
+        # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
         response = getattr(self._session, method)(
             "{host}{uri}".format(host=self._host, uri=uri),
             timeout=timeout,
@@ -227,6 +220,8 @@
         self,
         request: operations_pb2.GetOperationRequest,
         *,
+        # TODO(https://github.com/googleapis/python-api-core/issues/723): Leverage `retry`
+        # to allow configuring retryable error codes.
         retry: OptionalRetry = gapic_v1.method.DEFAULT,
         timeout: Optional[float] = None,
         compression: Optional[grpc.Compression] = gapic_v1.method.DEFAULT,
@@ -282,6 +277,7 @@
         # Send the request
         headers = dict(metadata)
         headers["Content-Type"] = "application/json"
+        # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
         response = getattr(self._session, method)(
             "{host}{uri}".format(host=self._host, uri=uri),
             timeout=timeout,
@@ -303,6 +299,8 @@
         self,
         request: operations_pb2.DeleteOperationRequest,
         *,
+        # TODO(https://github.com/googleapis/python-api-core/issues/723): Leverage `retry`
+        # to allow configuring retryable error codes.
         retry: OptionalRetry = gapic_v1.method.DEFAULT,
         timeout: Optional[float] = None,
         compression: Optional[grpc.Compression] = gapic_v1.method.DEFAULT,
@@ -351,6 +349,7 @@
         # Send the request
         headers = dict(metadata)
         headers["Content-Type"] = "application/json"
+        # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
         response = getattr(self._session, method)(
             "{host}{uri}".format(host=self._host, uri=uri),
             timeout=timeout,
@@ -369,6 +368,8 @@
         self,
         request: operations_pb2.CancelOperationRequest,
         *,
+        # TODO(https://github.com/googleapis/python-api-core/issues/723): Leverage `retry`
+        # to allow configuring retryable error codes.
         retry: OptionalRetry = gapic_v1.method.DEFAULT,
         timeout: Optional[float] = None,
         compression: Optional[grpc.Compression] = gapic_v1.method.DEFAULT,
@@ -426,6 +427,7 @@
         # Send the request
         headers = dict(metadata)
         headers["Content-Type"] = "application/json"
+        # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
         response = getattr(self._session, method)(
             "{host}{uri}".format(host=self._host, uri=uri),
             timeout=timeout,
@@ -441,38 +443,6 @@
 
         return empty_pb2.Empty()
 
-    def _convert_protobuf_message_to_dict(
-        self, message: google.protobuf.message.Message
-    ):
-        r"""Converts protobuf message to a dictionary.
-
-        When the dictionary is encoded to JSON, it conforms to proto3 JSON spec.
-
-        Args:
-            message(google.protobuf.message.Message): The protocol buffers message
-                instance to serialize.
-
-        Returns:
-            A dict representation of the protocol buffer message.
-        """
-        # For backwards compatibility with protobuf 3.x 4.x
-        # Remove once support for protobuf 3.x and 4.x is dropped
-        # https://github.com/googleapis/python-api-core/issues/643
-        if PROTOBUF_VERSION[0:2] in ["3.", "4."]:
-            result = json_format.MessageToDict(
-                message,
-                preserving_proto_field_name=True,
-                including_default_value_fields=True,  # type: ignore # backward compatibility
-            )
-        else:
-            result = json_format.MessageToDict(
-                message,
-                preserving_proto_field_name=True,
-                always_print_fields_with_no_presence=True,
-            )
-
-        return result
-
     @property
     def list_operations(
         self,
diff --git a/google/api_core/operations_v1/transports/rest_asyncio.py b/google/api_core/operations_v1/transports/rest_asyncio.py
new file mode 100644
index 0000000..71c20eb
--- /dev/null
+++ b/google/api_core/operations_v1/transports/rest_asyncio.py
@@ -0,0 +1,560 @@
+# -*- 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.
+#
+
+import json
+from typing import Any, Callable, Coroutine, Dict, Optional, Sequence, Tuple
+
+from google.auth import __version__ as auth_version
+
+try:
+    from google.auth.aio.transport.sessions import AsyncAuthorizedSession  # type: ignore
+except ImportError as e:  # pragma: NO COVER
+    raise ImportError(
+        "The `async_rest` extra of `google-api-core` is required to use long-running operations.  Install it by running "
+        "`pip install google-api-core[async_rest]`."
+    ) from e
+
+from google.api_core import exceptions as core_exceptions  # type: ignore
+from google.api_core import gapic_v1  # type: ignore
+from google.api_core import path_template  # type: ignore
+from google.api_core import rest_helpers  # type: ignore
+from google.api_core import retry_async as retries_async  # type: ignore
+from google.auth.aio import credentials as ga_credentials_async  # type: ignore
+from google.longrunning import operations_pb2  # type: ignore
+from google.protobuf import empty_pb2  # type: ignore
+from google.protobuf import json_format  # type: ignore
+
+from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO, OperationsTransport
+
+DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
+    gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
+    grpc_version=None,
+    rest_version=f"google-auth@{auth_version}",
+)
+
+
+class AsyncOperationsRestTransport(OperationsTransport):
+    """Asynchronous REST backend transport for Operations.
+
+    Manages async long-running operations with an API service.
+
+    When an API method normally takes long time to complete, it can be
+    designed to return [Operation][google.api_core.operations_v1.Operation] to the
+    client, and the client can use this interface to receive the real
+    response asynchronously by polling the operation resource, or pass
+    the operation resource to another API (such as Google Cloud Pub/Sub
+    API) to receive the response. Any API service that returns
+    long-running operations should implement the ``Operations``
+    interface so developers can have a consistent client experience.
+
+    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 = "longrunning.googleapis.com",
+        credentials: Optional[ga_credentials_async.Credentials] = None,
+        credentials_file: Optional[str] = None,
+        scopes: Optional[Sequence[str]] = None,
+        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
+        quota_project_id: Optional[str] = None,
+        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
+        always_use_jwt_access: Optional[bool] = False,
+        url_scheme: str = "https",
+        http_options: Optional[Dict] = None,
+        path_prefix: str = "v1",
+        # TODO(https://github.com/googleapis/python-api-core/issues/715): Add docstring for `credentials_file` to async REST transport.
+        # TODO(https://github.com/googleapis/python-api-core/issues/716): Add docstring for `scopes` to async REST transport.
+        # TODO(https://github.com/googleapis/python-api-core/issues/717): Add docstring for `quota_project_id` to async REST transport.
+        # TODO(https://github.com/googleapis/python-api-core/issues/718): Add docstring for `client_cert_source` to async REST transport.
+    ) -> None:
+        """Instantiate the transport.
+
+        Args:
+            host (Optional[str]):
+                 The hostname to connect to.
+            credentials (Optional[google.auth.aio.credentials.Credentials]): 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're developing
+                your own client library.
+            always_use_jwt_access (Optional[bool]): Whether self signed JWT should
+                be used for service account credentials.
+            url_scheme: the protocol scheme for the API endpoint.  Normally
+                "https", but for testing or local servers,
+                "http" can be specified.
+            http_options: a dictionary of http_options for transcoding, to override
+                the defaults from operations.proto.  Each method has an entry
+                with the corresponding http rules as value.
+            path_prefix: path prefix (usually represents API version). Set to
+                "v1" by default.
+
+        """
+        unsupported_params = {
+            # TODO(https://github.com/googleapis/python-api-core/issues/715): Add support for `credentials_file` to async REST transport.
+            "google.api_core.client_options.ClientOptions.credentials_file": credentials_file,
+            # TODO(https://github.com/googleapis/python-api-core/issues/716): Add support for `scopes` to async REST transport.
+            "google.api_core.client_options.ClientOptions.scopes": scopes,
+            # TODO(https://github.com/googleapis/python-api-core/issues/717): Add support for `quota_project_id` to async REST transport.
+            "google.api_core.client_options.ClientOptions.quota_project_id": quota_project_id,
+            # TODO(https://github.com/googleapis/python-api-core/issues/718): Add support for `client_cert_source` to async REST transport.
+            "google.api_core.client_options.ClientOptions.client_cert_source": client_cert_source_for_mtls,
+            # TODO(https://github.com/googleapis/python-api-core/issues/718): Add support for `client_cert_source` to async REST transport.
+            "google.api_core.client_options.ClientOptions.client_cert_source": client_cert_source_for_mtls,
+        }
+        provided_unsupported_params = [
+            name for name, value in unsupported_params.items() if value is not None
+        ]
+        if provided_unsupported_params:
+            raise core_exceptions.AsyncRestUnsupportedParameterError(
+                f"The following provided parameters are not supported for `transport=rest_asyncio`: {', '.join(provided_unsupported_params)}"
+            )
+
+        super().__init__(
+            host=host,
+            # TODO(https://github.com/googleapis/python-api-core/issues/709): Remove `type: ignore` when the linked issue is resolved.
+            credentials=credentials,  # type: ignore
+            client_info=client_info,
+            # TODO(https://github.com/googleapis/python-api-core/issues/725): Set always_use_jwt_access token when supported.
+            always_use_jwt_access=False,
+        )
+        # TODO(https://github.com/googleapis/python-api-core/issues/708): add support for
+        # `default_host` in AsyncAuthorizedSession for feature parity with the synchronous
+        # code.
+        # TODO(https://github.com/googleapis/python-api-core/issues/709): Remove `type: ignore` when the linked issue is resolved.
+        self._session = AsyncAuthorizedSession(self._credentials)  # type: ignore
+        # TODO(https://github.com/googleapis/python-api-core/issues/720): Add wrap logic directly to the property methods for callables.
+        self._prep_wrapped_messages(client_info)
+        self._http_options = http_options or {}
+        self._path_prefix = path_prefix
+
+    def _prep_wrapped_messages(self, client_info):
+        # Precompute the wrapped methods.
+        self._wrapped_methods = {
+            self.list_operations: gapic_v1.method_async.wrap_method(
+                self.list_operations,
+                default_retry=retries_async.AsyncRetry(
+                    initial=0.5,
+                    maximum=10.0,
+                    multiplier=2.0,
+                    predicate=retries_async.if_exception_type(
+                        core_exceptions.ServiceUnavailable,
+                    ),
+                    deadline=10.0,
+                ),
+                default_timeout=10.0,
+                client_info=client_info,
+                kind="rest_asyncio",
+            ),
+            self.get_operation: gapic_v1.method_async.wrap_method(
+                self.get_operation,
+                default_retry=retries_async.AsyncRetry(
+                    initial=0.5,
+                    maximum=10.0,
+                    multiplier=2.0,
+                    predicate=retries_async.if_exception_type(
+                        core_exceptions.ServiceUnavailable,
+                    ),
+                    deadline=10.0,
+                ),
+                default_timeout=10.0,
+                client_info=client_info,
+                kind="rest_asyncio",
+            ),
+            self.delete_operation: gapic_v1.method_async.wrap_method(
+                self.delete_operation,
+                default_retry=retries_async.AsyncRetry(
+                    initial=0.5,
+                    maximum=10.0,
+                    multiplier=2.0,
+                    predicate=retries_async.if_exception_type(
+                        core_exceptions.ServiceUnavailable,
+                    ),
+                    deadline=10.0,
+                ),
+                default_timeout=10.0,
+                client_info=client_info,
+                kind="rest_asyncio",
+            ),
+            self.cancel_operation: gapic_v1.method_async.wrap_method(
+                self.cancel_operation,
+                default_retry=retries_async.AsyncRetry(
+                    initial=0.5,
+                    maximum=10.0,
+                    multiplier=2.0,
+                    predicate=retries_async.if_exception_type(
+                        core_exceptions.ServiceUnavailable,
+                    ),
+                    deadline=10.0,
+                ),
+                default_timeout=10.0,
+                client_info=client_info,
+                kind="rest_asyncio",
+            ),
+        }
+
+    async def _list_operations(
+        self,
+        request: operations_pb2.ListOperationsRequest,
+        *,
+        # TODO(https://github.com/googleapis/python-api-core/issues/722): Leverage `retry`
+        # to allow configuring retryable error codes.
+        retry=gapic_v1.method_async.DEFAULT,
+        timeout: Optional[float] = None,
+        metadata: Sequence[Tuple[str, str]] = (),
+    ) -> operations_pb2.ListOperationsResponse:
+        r"""Asynchronously call the list operations method over HTTP.
+
+        Args:
+            request (~.operations_pb2.ListOperationsRequest):
+                The request object. The request message for
+                [Operations.ListOperations][google.api_core.operations_v1.Operations.ListOperations].
+            timeout (float): The timeout for this request.
+            metadata (Sequence[Tuple[str, str]]): Strings which should be
+                sent along with the request as metadata.
+
+        Returns:
+            ~.operations_pb2.ListOperationsResponse:
+                The response message for
+                [Operations.ListOperations][google.api_core.operations_v1.Operations.ListOperations].
+
+        """
+
+        http_options = [
+            {
+                "method": "get",
+                "uri": "/{}/{{name=**}}/operations".format(self._path_prefix),
+            },
+        ]
+        if "google.longrunning.Operations.ListOperations" in self._http_options:
+            http_options = self._http_options[
+                "google.longrunning.Operations.ListOperations"
+            ]
+
+        request_kwargs = self._convert_protobuf_message_to_dict(request)
+        transcoded_request = path_template.transcode(http_options, **request_kwargs)
+
+        uri = transcoded_request["uri"]
+        method = transcoded_request["method"]
+
+        # Jsonify the query params
+        query_params_request = operations_pb2.ListOperationsRequest()
+        json_format.ParseDict(transcoded_request["query_params"], query_params_request)
+        query_params = json_format.MessageToDict(
+            query_params_request,
+            preserving_proto_field_name=False,
+            use_integers_for_enums=False,
+        )
+
+        # Send the request
+        headers = dict(metadata)
+        headers["Content-Type"] = "application/json"
+        # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
+        response = await getattr(self._session, method)(
+            "{host}{uri}".format(host=self._host, uri=uri),
+            timeout=timeout,
+            headers=headers,
+            params=rest_helpers.flatten_query_params(query_params),
+        )
+        content = await response.read()
+
+        # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
+        # subclass.
+        if response.status_code >= 400:
+            payload = json.loads(content.decode("utf-8"))
+            request_url = "{host}{uri}".format(host=self._host, uri=uri)
+            raise core_exceptions.format_http_response_error(response, method, request_url, payload)  # type: ignore
+
+        # Return the response
+        api_response = operations_pb2.ListOperationsResponse()
+        json_format.Parse(content, api_response, ignore_unknown_fields=False)
+        return api_response
+
+    async def _get_operation(
+        self,
+        request: operations_pb2.GetOperationRequest,
+        *,
+        # TODO(https://github.com/googleapis/python-api-core/issues/722): Leverage `retry`
+        # to allow configuring retryable error codes.
+        retry=gapic_v1.method_async.DEFAULT,
+        timeout: Optional[float] = None,
+        metadata: Sequence[Tuple[str, str]] = (),
+    ) -> operations_pb2.Operation:
+        r"""Asynchronously call the get operation method over HTTP.
+
+        Args:
+            request (~.operations_pb2.GetOperationRequest):
+                The request object. The request message for
+                [Operations.GetOperation][google.api_core.operations_v1.Operations.GetOperation].
+            timeout (float): The timeout for this request.
+            metadata (Sequence[Tuple[str, str]]): Strings which should be
+                sent along with the request as metadata.
+
+        Returns:
+            ~.operations_pb2.Operation:
+                This resource represents a long-
+                running operation that is the result of a
+                network API call.
+
+        """
+
+        http_options = [
+            {
+                "method": "get",
+                "uri": "/{}/{{name=**/operations/*}}".format(self._path_prefix),
+            },
+        ]
+        if "google.longrunning.Operations.GetOperation" in self._http_options:
+            http_options = self._http_options[
+                "google.longrunning.Operations.GetOperation"
+            ]
+
+        request_kwargs = self._convert_protobuf_message_to_dict(request)
+        transcoded_request = path_template.transcode(http_options, **request_kwargs)
+
+        uri = transcoded_request["uri"]
+        method = transcoded_request["method"]
+
+        # Jsonify the query params
+        query_params_request = operations_pb2.GetOperationRequest()
+        json_format.ParseDict(transcoded_request["query_params"], query_params_request)
+        query_params = json_format.MessageToDict(
+            query_params_request,
+            preserving_proto_field_name=False,
+            use_integers_for_enums=False,
+        )
+
+        # Send the request
+        headers = dict(metadata)
+        headers["Content-Type"] = "application/json"
+        # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
+        response = await getattr(self._session, method)(
+            "{host}{uri}".format(host=self._host, uri=uri),
+            timeout=timeout,
+            headers=headers,
+            params=rest_helpers.flatten_query_params(query_params),
+        )
+        content = await response.read()
+
+        # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
+        # subclass.
+        if response.status_code >= 400:
+            payload = json.loads(content.decode("utf-8"))
+            request_url = "{host}{uri}".format(host=self._host, uri=uri)
+            raise core_exceptions.format_http_response_error(response, method, request_url, payload)  # type: ignore
+
+        # Return the response
+        api_response = operations_pb2.Operation()
+        json_format.Parse(content, api_response, ignore_unknown_fields=False)
+        return api_response
+
+    async def _delete_operation(
+        self,
+        request: operations_pb2.DeleteOperationRequest,
+        *,
+        # TODO(https://github.com/googleapis/python-api-core/issues/722): Leverage `retry`
+        # to allow configuring retryable error codes.
+        retry=gapic_v1.method_async.DEFAULT,
+        timeout: Optional[float] = None,
+        metadata: Sequence[Tuple[str, str]] = (),
+    ) -> empty_pb2.Empty:
+        r"""Asynchronously call the delete operation method over HTTP.
+
+        Args:
+            request (~.operations_pb2.DeleteOperationRequest):
+                The request object. The request message for
+                [Operations.DeleteOperation][google.api_core.operations_v1.Operations.DeleteOperation].
+
+            retry (google.api_core.retry.Retry): Designation of what errors, if any,
+                should be retried.
+            timeout (float): The timeout for this request.
+            metadata (Sequence[Tuple[str, str]]): Strings which should be
+                sent along with the request as metadata.
+        """
+
+        http_options = [
+            {
+                "method": "delete",
+                "uri": "/{}/{{name=**/operations/*}}".format(self._path_prefix),
+            },
+        ]
+        if "google.longrunning.Operations.DeleteOperation" in self._http_options:
+            http_options = self._http_options[
+                "google.longrunning.Operations.DeleteOperation"
+            ]
+
+        request_kwargs = self._convert_protobuf_message_to_dict(request)
+        transcoded_request = path_template.transcode(http_options, **request_kwargs)
+
+        uri = transcoded_request["uri"]
+        method = transcoded_request["method"]
+
+        # Jsonify the query params
+        query_params_request = operations_pb2.DeleteOperationRequest()
+        json_format.ParseDict(transcoded_request["query_params"], query_params_request)
+        query_params = json_format.MessageToDict(
+            query_params_request,
+            preserving_proto_field_name=False,
+            use_integers_for_enums=False,
+        )
+
+        # Send the request
+        headers = dict(metadata)
+        headers["Content-Type"] = "application/json"
+        # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
+        response = await getattr(self._session, method)(
+            "{host}{uri}".format(host=self._host, uri=uri),
+            timeout=timeout,
+            headers=headers,
+            params=rest_helpers.flatten_query_params(query_params),
+        )
+
+        # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
+        # subclass.
+        if response.status_code >= 400:
+            content = await response.read()
+            payload = json.loads(content.decode("utf-8"))
+            request_url = "{host}{uri}".format(host=self._host, uri=uri)
+            raise core_exceptions.format_http_response_error(response, method, request_url, payload)  # type: ignore
+
+        return empty_pb2.Empty()
+
+    async def _cancel_operation(
+        self,
+        request: operations_pb2.CancelOperationRequest,
+        *,
+        # TODO(https://github.com/googleapis/python-api-core/issues/722): Leverage `retry`
+        # to allow configuring retryable error codes.
+        retry=gapic_v1.method_async.DEFAULT,
+        timeout: Optional[float] = None,
+        metadata: Sequence[Tuple[str, str]] = (),
+        # TODO(https://github.com/googleapis/python-api-core/issues/722): Add `retry` parameter
+        # to allow configuring retryable error codes.
+    ) -> empty_pb2.Empty:
+        r"""Asynchronously call the cancel operation method over HTTP.
+
+        Args:
+            request (~.operations_pb2.CancelOperationRequest):
+                The request object. The request message for
+                [Operations.CancelOperation][google.api_core.operations_v1.Operations.CancelOperation].
+            timeout (float): The timeout for this request.
+            metadata (Sequence[Tuple[str, str]]): Strings which should be
+                sent along with the request as metadata.
+        """
+
+        http_options = [
+            {
+                "method": "post",
+                "uri": "/{}/{{name=**/operations/*}}:cancel".format(self._path_prefix),
+                "body": "*",
+            },
+        ]
+        if "google.longrunning.Operations.CancelOperation" in self._http_options:
+            http_options = self._http_options[
+                "google.longrunning.Operations.CancelOperation"
+            ]
+
+        request_kwargs = self._convert_protobuf_message_to_dict(request)
+        transcoded_request = path_template.transcode(http_options, **request_kwargs)
+
+        # Jsonify the request body
+        body_request = operations_pb2.CancelOperationRequest()
+        json_format.ParseDict(transcoded_request["body"], body_request)
+        body = json_format.MessageToDict(
+            body_request,
+            preserving_proto_field_name=False,
+            use_integers_for_enums=False,
+        )
+        uri = transcoded_request["uri"]
+        method = transcoded_request["method"]
+
+        # Jsonify the query params
+        query_params_request = operations_pb2.CancelOperationRequest()
+        json_format.ParseDict(transcoded_request["query_params"], query_params_request)
+        query_params = json_format.MessageToDict(
+            query_params_request,
+            preserving_proto_field_name=False,
+            use_integers_for_enums=False,
+        )
+
+        # Send the request
+        headers = dict(metadata)
+        headers["Content-Type"] = "application/json"
+        # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
+        response = await getattr(self._session, method)(
+            "{host}{uri}".format(host=self._host, uri=uri),
+            timeout=timeout,
+            headers=headers,
+            params=rest_helpers.flatten_query_params(query_params),
+            data=body,
+        )
+
+        # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
+        # subclass.
+        if response.status_code >= 400:
+            content = await response.read()
+            payload = json.loads(content.decode("utf-8"))
+            request_url = "{host}{uri}".format(host=self._host, uri=uri)
+            raise core_exceptions.format_http_response_error(response, method, request_url, payload)  # type: ignore
+
+        return empty_pb2.Empty()
+
+    @property
+    def list_operations(
+        self,
+    ) -> Callable[
+        [operations_pb2.ListOperationsRequest],
+        Coroutine[Any, Any, operations_pb2.ListOperationsResponse],
+    ]:
+        return self._list_operations
+
+    @property
+    def get_operation(
+        self,
+    ) -> Callable[
+        [operations_pb2.GetOperationRequest],
+        Coroutine[Any, Any, operations_pb2.Operation],
+    ]:
+        return self._get_operation
+
+    @property
+    def delete_operation(
+        self,
+    ) -> Callable[
+        [operations_pb2.DeleteOperationRequest], Coroutine[Any, Any, empty_pb2.Empty]
+    ]:
+        return self._delete_operation
+
+    @property
+    def cancel_operation(
+        self,
+    ) -> Callable[
+        [operations_pb2.CancelOperationRequest], Coroutine[Any, Any, empty_pb2.Empty]
+    ]:
+        return self._cancel_operation
+
+
+__all__ = ("AsyncOperationsRestTransport",)
diff --git a/tests/unit/operations_v1/test_operations_rest_client.py b/tests/unit/operations_v1/test_operations_rest_client.py
index 4ab4f1f..644cf26 100644
--- a/tests/unit/operations_v1/test_operations_rest_client.py
+++ b/tests/unit/operations_v1/test_operations_rest_client.py
@@ -17,21 +17,24 @@
 
 import mock
 import pytest
+from typing import Any, List
 
 try:
     import grpc  # noqa: F401
 except ImportError:  # pragma: NO COVER
     pytest.skip("No GRPC", allow_module_level=True)
 from requests import Response  # noqa I201
-from requests.sessions import Session
+from google.auth.transport.requests import AuthorizedSession
 
 from google.api_core import client_options
 from google.api_core import exceptions as core_exceptions
 from google.api_core import gapic_v1
 from google.api_core.operations_v1 import AbstractOperationsClient
-from google.api_core.operations_v1 import pagers
-from google.api_core.operations_v1 import transports
+
 import google.auth
+from google.api_core.operations_v1 import pagers
+from google.api_core.operations_v1 import pagers_async
+from google.api_core.operations_v1 import transports
 from google.auth import credentials as ga_credentials
 from google.auth.exceptions import MutualTLSChannelError
 from google.longrunning import operations_pb2
@@ -39,6 +42,16 @@
 from google.protobuf import json_format  # type: ignore
 from google.rpc import status_pb2  # type: ignore
 
+try:
+    import aiohttp  # noqa: F401
+    import google.auth.aio.transport
+    from google.auth.aio.transport.sessions import AsyncAuthorizedSession
+    from google.api_core.operations_v1 import AsyncOperationsRestClient
+    from google.auth.aio import credentials as ga_credentials_async
+
+    GOOGLE_AUTH_AIO_INSTALLED = True
+except ImportError:
+    GOOGLE_AUTH_AIO_INSTALLED = False
 
 HTTP_OPTIONS = {
     "google.longrunning.Operations.CancelOperation": [
@@ -55,17 +68,62 @@
     ],
 }
 
+PYPARAM_CLIENT: List[Any] = [
+    AbstractOperationsClient,
+]
+PYPARAM_CLIENT_TRANSPORT_NAME = [
+    [AbstractOperationsClient, transports.OperationsRestTransport, "rest"],
+]
+PYPARAM_CLIENT_TRANSPORT_CREDENTIALS = [
+    [
+        AbstractOperationsClient,
+        transports.OperationsRestTransport,
+        ga_credentials.AnonymousCredentials(),
+    ],
+]
+
+if GOOGLE_AUTH_AIO_INSTALLED:
+    PYPARAM_CLIENT.append(AsyncOperationsRestClient)
+    PYPARAM_CLIENT_TRANSPORT_NAME.append(
+        [
+            AsyncOperationsRestClient,
+            transports.AsyncOperationsRestTransport,
+            "rest_asyncio",
+        ]
+    )
+    PYPARAM_CLIENT_TRANSPORT_CREDENTIALS.append(
+        [
+            AsyncOperationsRestClient,
+            transports.AsyncOperationsRestTransport,
+            ga_credentials_async.AnonymousCredentials(),
+        ]
+    )
+
 
 def client_cert_source_callback():
     return b"cert bytes", b"key bytes"
 
 
-def _get_operations_client(http_options=HTTP_OPTIONS):
-    transport = transports.rest.OperationsRestTransport(
-        credentials=ga_credentials.AnonymousCredentials(), http_options=http_options
+def _get_session_type(is_async: bool):
+    return (
+        AsyncAuthorizedSession
+        if is_async and GOOGLE_AUTH_AIO_INSTALLED
+        else AuthorizedSession
     )
 
-    return AbstractOperationsClient(transport=transport)
+
+def _get_operations_client(is_async: bool, http_options=HTTP_OPTIONS):
+    if is_async and GOOGLE_AUTH_AIO_INSTALLED:
+        async_transport = transports.rest_asyncio.AsyncOperationsRestTransport(
+            credentials=ga_credentials_async.AnonymousCredentials(),
+            http_options=http_options,
+        )
+        return AsyncOperationsRestClient(transport=async_transport)
+    else:
+        sync_transport = transports.rest.OperationsRestTransport(
+            credentials=ga_credentials.AnonymousCredentials(), http_options=http_options
+        )
+        return AbstractOperationsClient(transport=sync_transport)
 
 
 # If default endpoint is localhost, then default mtls endpoint will be the same.
@@ -79,57 +137,69 @@
     )
 
 
-def test__get_default_mtls_endpoint():
+# TODO: Add support for mtls in async rest
+@pytest.mark.parametrize(
+    "client_class",
+    [
+        AbstractOperationsClient,
+    ],
+)
+def test__get_default_mtls_endpoint(client_class):
     api_endpoint = "example.googleapis.com"
     api_mtls_endpoint = "example.mtls.googleapis.com"
     sandbox_endpoint = "example.sandbox.googleapis.com"
     sandbox_mtls_endpoint = "example.mtls.sandbox.googleapis.com"
     non_googleapi = "api.example.com"
 
-    assert AbstractOperationsClient._get_default_mtls_endpoint(None) is None
+    assert client_class._get_default_mtls_endpoint(None) is None
+    assert client_class._get_default_mtls_endpoint(api_endpoint) == api_mtls_endpoint
     assert (
-        AbstractOperationsClient._get_default_mtls_endpoint(api_endpoint)
-        == api_mtls_endpoint
+        client_class._get_default_mtls_endpoint(api_mtls_endpoint) == api_mtls_endpoint
     )
     assert (
-        AbstractOperationsClient._get_default_mtls_endpoint(api_mtls_endpoint)
-        == api_mtls_endpoint
-    )
-    assert (
-        AbstractOperationsClient._get_default_mtls_endpoint(sandbox_endpoint)
+        client_class._get_default_mtls_endpoint(sandbox_endpoint)
         == sandbox_mtls_endpoint
     )
     assert (
-        AbstractOperationsClient._get_default_mtls_endpoint(sandbox_mtls_endpoint)
+        client_class._get_default_mtls_endpoint(sandbox_mtls_endpoint)
         == sandbox_mtls_endpoint
     )
-    assert (
-        AbstractOperationsClient._get_default_mtls_endpoint(non_googleapi)
-        == non_googleapi
-    )
-
-
-@pytest.mark.parametrize("client_class", [AbstractOperationsClient])
-def test_operations_client_from_service_account_info(client_class):
-    creds = ga_credentials.AnonymousCredentials()
-    with mock.patch.object(
-        service_account.Credentials, "from_service_account_info"
-    ) as factory:
-        factory.return_value = creds
-        info = {"valid": True}
-        client = client_class.from_service_account_info(info)
-        assert client.transport._credentials == creds
-        assert isinstance(client, client_class)
-
-        assert client.transport._host == "https://longrunning.googleapis.com"
+    assert client_class._get_default_mtls_endpoint(non_googleapi) == non_googleapi
 
 
 @pytest.mark.parametrize(
-    "transport_class,transport_name", [(transports.OperationsRestTransport, "rest")]
+    "client_class",
+    PYPARAM_CLIENT,
 )
-def test_operations_client_service_account_always_use_jwt(
-    transport_class, transport_name
-):
+def test_operations_client_from_service_account_info(client_class):
+    creds = ga_credentials.AnonymousCredentials()
+    if "async" in str(client_class):
+        # TODO(): Add support for service account info to async REST transport.
+        with pytest.raises(NotImplementedError):
+            info = {"valid": True}
+            client_class.from_service_account_info(info)
+    else:
+        with mock.patch.object(
+            service_account.Credentials, "from_service_account_info"
+        ) as factory:
+            factory.return_value = creds
+            info = {"valid": True}
+            client = client_class.from_service_account_info(info)
+            assert client.transport._credentials == creds
+            assert isinstance(client, client_class)
+
+            assert client.transport._host == "https://longrunning.googleapis.com"
+
+
+@pytest.mark.parametrize(
+    "transport_class",
+    [
+        transports.OperationsRestTransport,
+        # TODO(https://github.com/googleapis/python-api-core/issues/706): Add support for
+        # service account credentials in transports.AsyncOperationsRestTransport
+    ],
+)
+def test_operations_client_service_account_always_use_jwt(transport_class):
     with mock.patch.object(
         service_account.Credentials, "with_always_use_jwt_access", create=True
     ) as use_jwt:
@@ -145,35 +215,53 @@
         use_jwt.assert_not_called()
 
 
-@pytest.mark.parametrize("client_class", [AbstractOperationsClient])
+@pytest.mark.parametrize(
+    "client_class",
+    PYPARAM_CLIENT,
+)
 def test_operations_client_from_service_account_file(client_class):
-    creds = ga_credentials.AnonymousCredentials()
-    with mock.patch.object(
-        service_account.Credentials, "from_service_account_file"
-    ) as factory:
-        factory.return_value = creds
-        client = client_class.from_service_account_file("dummy/file/path.json")
-        assert client.transport._credentials == creds
-        assert isinstance(client, client_class)
 
-        client = client_class.from_service_account_json("dummy/file/path.json")
-        assert client.transport._credentials == creds
-        assert isinstance(client, client_class)
+    if "async" in str(client_class):
+        # TODO(): Add support for service account creds to async REST transport.
+        with pytest.raises(NotImplementedError):
+            client_class.from_service_account_file("dummy/file/path.json")
+    else:
+        creds = ga_credentials.AnonymousCredentials()
+        with mock.patch.object(
+            service_account.Credentials, "from_service_account_file"
+        ) as factory:
+            factory.return_value = creds
+            client = client_class.from_service_account_file("dummy/file/path.json")
+            assert client.transport._credentials == creds
+            assert isinstance(client, client_class)
 
-        assert client.transport._host == "https://longrunning.googleapis.com"
+            client = client_class.from_service_account_json("dummy/file/path.json")
+            assert client.transport._credentials == creds
+            assert isinstance(client, client_class)
+
+            assert client.transport._host == "https://longrunning.googleapis.com"
 
 
-def test_operations_client_get_transport_class():
-    transport = AbstractOperationsClient.get_transport_class()
+@pytest.mark.parametrize(
+    "client_class,transport_class,transport_name",
+    PYPARAM_CLIENT_TRANSPORT_NAME,
+)
+def test_operations_client_get_transport_class(
+    client_class, transport_class, transport_name
+):
+    transport = client_class.get_transport_class()
     available_transports = [
         transports.OperationsRestTransport,
     ]
+    if GOOGLE_AUTH_AIO_INSTALLED:
+        available_transports.append(transports.AsyncOperationsRestTransport)
     assert transport in available_transports
 
-    transport = AbstractOperationsClient.get_transport_class("rest")
-    assert transport == transports.OperationsRestTransport
+    transport = client_class.get_transport_class(transport_name)
+    assert transport == transport_class
 
 
+# TODO(): Update this test case to include async REST once we have support for MTLS.
 @pytest.mark.parametrize(
     "client_class,transport_class,transport_name",
     [(AbstractOperationsClient, transports.OperationsRestTransport, "rest")],
@@ -186,22 +274,21 @@
 def test_operations_client_client_options(
     client_class, transport_class, transport_name
 ):
-    # Check that if channel is provided we won't create a new one.
-    with mock.patch.object(AbstractOperationsClient, "get_transport_class") as gtc:
-        transport = transport_class(credentials=ga_credentials.AnonymousCredentials())
-        client = client_class(transport=transport)
-        gtc.assert_not_called()
+    # # Check that if channel is provided we won't create a new one.
+    # with mock.patch.object(AbstractOperationsBaseClient, "get_transport_class") as gtc:
+    #     client = client_class(transport=transport_class())
+    #     gtc.assert_not_called()
 
-    # Check that if channel is provided via str we will create a new one.
-    with mock.patch.object(AbstractOperationsClient, "get_transport_class") as gtc:
-        client = client_class(transport=transport_name)
-        gtc.assert_called()
+    # # Check that if channel is provided via str we will create a new one.
+    # with mock.patch.object(AbstractOperationsBaseClient, "get_transport_class") as gtc:
+    #     client = client_class(transport=transport_name)
+    #     gtc.assert_called()
 
     # Check the case api_endpoint is provided.
     options = client_options.ClientOptions(api_endpoint="squid.clam.whelk")
     with mock.patch.object(transport_class, "__init__") as patched:
         patched.return_value = None
-        client = client_class(client_options=options)
+        client = client_class(client_options=options, transport=transport_name)
         patched.assert_called_once_with(
             credentials=None,
             credentials_file=None,
@@ -218,7 +305,7 @@
     with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}):
         with mock.patch.object(transport_class, "__init__") as patched:
             patched.return_value = None
-            client = client_class()
+            client = client_class(transport=transport_name)
             patched.assert_called_once_with(
                 credentials=None,
                 credentials_file=None,
@@ -235,7 +322,7 @@
     with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}):
         with mock.patch.object(transport_class, "__init__") as patched:
             patched.return_value = None
-            client = client_class()
+            client = client_class(transport=transport_name)
             patched.assert_called_once_with(
                 credentials=None,
                 credentials_file=None,
@@ -264,7 +351,7 @@
     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)
+        client = client_class(client_options=options, transport=transport_name)
         patched.assert_called_once_with(
             credentials=None,
             credentials_file=None,
@@ -277,6 +364,7 @@
         )
 
 
+# TODO: Add support for mtls in async REST
 @pytest.mark.parametrize(
     "client_class,transport_class,transport_name,use_client_cert_env",
     [
@@ -393,7 +481,7 @@
 
 @pytest.mark.parametrize(
     "client_class,transport_class,transport_name",
-    [(AbstractOperationsClient, transports.OperationsRestTransport, "rest")],
+    PYPARAM_CLIENT_TRANSPORT_NAME,
 )
 def test_operations_client_client_options_scopes(
     client_class, transport_class, transport_name
@@ -402,52 +490,59 @@
     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"],
-            client_cert_source_for_mtls=None,
-            quota_project_id=None,
-            client_info=transports.base.DEFAULT_CLIENT_INFO,
-            always_use_jwt_access=True,
-        )
+    if "async" in str(client_class):
+        # TODO(): Add support for scopes to async REST transport.
+        with pytest.raises(core_exceptions.AsyncRestUnsupportedParameterError):
+            client_class(client_options=options, transport=transport_name)
+    else:
+        with mock.patch.object(transport_class, "__init__") as patched:
+            patched.return_value = None
+            client = client_class(client_options=options, transport=transport_name)
+            patched.assert_called_once_with(
+                credentials=None,
+                credentials_file=None,
+                host=client.DEFAULT_ENDPOINT,
+                scopes=["1", "2"],
+                client_cert_source_for_mtls=None,
+                quota_project_id=None,
+                client_info=transports.base.DEFAULT_CLIENT_INFO,
+                always_use_jwt_access=True,
+            )
 
 
 @pytest.mark.parametrize(
     "client_class,transport_class,transport_name",
-    [(AbstractOperationsClient, transports.OperationsRestTransport, "rest")],
+    PYPARAM_CLIENT_TRANSPORT_NAME,
 )
 def test_operations_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,
-            client_cert_source_for_mtls=None,
-            quota_project_id=None,
-            client_info=transports.base.DEFAULT_CLIENT_INFO,
-            always_use_jwt_access=True,
-        )
+    if "async" in str(client_class):
+        # TODO(): Add support for credentials file to async REST transport.
+        with pytest.raises(core_exceptions.AsyncRestUnsupportedParameterError):
+            client_class(client_options=options, transport=transport_name)
+    else:
+        with mock.patch.object(transport_class, "__init__") as patched:
+            patched.return_value = None
+            client = client_class(client_options=options, transport=transport_name)
+            patched.assert_called_once_with(
+                credentials=None,
+                credentials_file="credentials.json",
+                host=client.DEFAULT_ENDPOINT,
+                scopes=None,
+                client_cert_source_for_mtls=None,
+                quota_project_id=None,
+                client_info=transports.base.DEFAULT_CLIENT_INFO,
+                always_use_jwt_access=True,
+            )
 
 
-def test_list_operations_rest(
-    transport: str = "rest", request_type=operations_pb2.ListOperationsRequest
-):
-    client = _get_operations_client()
-
+def test_list_operations_rest():
+    client = _get_operations_client(is_async=False)
     # Mock the http request call within the method and fake a response.
-    with mock.patch.object(Session, "request") as req:
+    with mock.patch.object(_get_session_type(is_async=False), "request") as req:
         # Designate an appropriate value for the returned response.
         return_value = operations_pb2.ListOperationsResponse(
             next_page_token="next_page_token_value",
@@ -477,10 +572,49 @@
     assert response.next_page_token == "next_page_token_value"
 
 
-def test_list_operations_rest_failure():
-    client = _get_operations_client(http_options=None)
+@pytest.mark.asyncio
+async def test_list_operations_rest_async():
+    if not GOOGLE_AUTH_AIO_INSTALLED:
+        pytest.skip("Skipped because google-api-core[async_rest] is not installed")
 
-    with mock.patch.object(Session, "request") as req:
+    client = _get_operations_client(is_async=True)
+    # Mock the http request call within the method and fake a response.
+    with mock.patch.object(_get_session_type(is_async=True), "request") as req:
+        # Designate an appropriate value for the returned response.
+        return_value = operations_pb2.ListOperationsResponse(
+            next_page_token="next_page_token_value",
+        )
+
+        # Wrap the value into a proper Response obj
+        response_value = mock.Mock()
+        response_value.status_code = 200
+        json_return_value = json_format.MessageToJson(return_value)
+        response_value.read = mock.AsyncMock(
+            return_value=json_return_value.encode("UTF-8")
+        )
+        req.return_value = response_value
+        response = await client.list_operations(
+            name="operations", filter_="my_filter", page_size=10, page_token="abc"
+        )
+
+        actual_args = req.call_args
+        assert actual_args.args[0] == "GET"
+        assert actual_args.args[1] == "https://longrunning.googleapis.com/v3/operations"
+        assert actual_args.kwargs["params"] == [
+            ("filter", "my_filter"),
+            ("pageSize", 10),
+            ("pageToken", "abc"),
+        ]
+
+    # Establish that the response is the type that we expect.
+    assert isinstance(response, pagers_async.ListOperationsAsyncPager)
+    assert response.next_page_token == "next_page_token_value"
+
+
+def test_list_operations_rest_failure():
+    client = _get_operations_client(is_async=False, http_options=None)
+
+    with mock.patch.object(_get_session_type(is_async=False), "request") as req:
         response_value = Response()
         response_value.status_code = 400
         mock_request = mock.MagicMock()
@@ -492,13 +626,31 @@
             client.list_operations(name="operations")
 
 
+@pytest.mark.asyncio
+async def test_list_operations_rest_failure_async():
+    if not GOOGLE_AUTH_AIO_INSTALLED:
+        pytest.skip("Skipped because google-api-core[async_rest] is not installed")
+
+    client = _get_operations_client(is_async=True, http_options=None)
+
+    with mock.patch.object(_get_session_type(is_async=True), "request") as req:
+        response_value = mock.Mock()
+        response_value.status_code = 400
+        response_value.read = mock.AsyncMock(return_value=b"{}")
+        mock_request = mock.MagicMock()
+        mock_request.method = "GET"
+        mock_request.url = "https://longrunning.googleapis.com:443/v1/operations"
+        response_value.request = mock_request
+        req.return_value = response_value
+        with pytest.raises(core_exceptions.GoogleAPIError):
+            await client.list_operations(name="operations")
+
+
 def test_list_operations_rest_pager():
-    client = AbstractOperationsClient(
-        credentials=ga_credentials.AnonymousCredentials(),
-    )
+    client = _get_operations_client(is_async=False, http_options=None)
 
     # Mock the http request call within the method and fake a response.
-    with mock.patch.object(Session, "request") as req:
+    with mock.patch.object(_get_session_type(is_async=False), "request") as req:
         # TODO(kbandes): remove this mock unless there's a good reason for it.
         # with mock.patch.object(path_template, 'transcode') as transcode:
         # Set the response as a series of pages
@@ -545,13 +697,80 @@
             assert page_.next_page_token == token
 
 
-def test_get_operation_rest(
-    transport: str = "rest", request_type=operations_pb2.GetOperationRequest
-):
-    client = _get_operations_client()
+@pytest.mark.asyncio
+async def test_list_operations_rest_pager_async():
+    if not GOOGLE_AUTH_AIO_INSTALLED:
+        pytest.skip("Skipped because google-api-core[async_rest] is not installed")
+    client = _get_operations_client(is_async=True, http_options=None)
 
     # Mock the http request call within the method and fake a response.
-    with mock.patch.object(Session, "request") as req:
+    with mock.patch.object(_get_session_type(is_async=True), "request") as req:
+        # TODO(kbandes): remove this mock unless there's a good reason for it.
+        # with mock.patch.object(path_template, 'transcode') as transcode:
+        # Set the response as a series of pages
+        response = (
+            operations_pb2.ListOperationsResponse(
+                operations=[
+                    operations_pb2.Operation(),
+                    operations_pb2.Operation(),
+                    operations_pb2.Operation(),
+                ],
+                next_page_token="abc",
+            ),
+            operations_pb2.ListOperationsResponse(
+                operations=[],
+                next_page_token="def",
+            ),
+            operations_pb2.ListOperationsResponse(
+                operations=[operations_pb2.Operation()],
+                next_page_token="ghi",
+            ),
+            operations_pb2.ListOperationsResponse(
+                operations=[operations_pb2.Operation(), operations_pb2.Operation()],
+            ),
+        )
+        # Two responses for two calls
+        response = response + response
+
+        # Wrap the values into proper Response objs
+        response = tuple(json_format.MessageToJson(x) for x in response)
+        return_values = tuple(mock.Mock() for i in response)
+        for return_val, response_val in zip(return_values, response):
+            return_val.read = mock.AsyncMock(return_value=response_val.encode("UTF-8"))
+            return_val.status_code = 200
+        req.side_effect = return_values
+
+        pager = await client.list_operations(name="operations")
+
+        responses = []
+        async for response in pager:
+            responses.append(response)
+
+        results = list(responses)
+        assert len(results) == 6
+        assert all(isinstance(i, operations_pb2.Operation) for i in results)
+        pager = await client.list_operations(name="operations")
+
+        responses = []
+        async for response in pager:
+            responses.append(response)
+
+        assert len(responses) == 6
+        assert all(isinstance(i, operations_pb2.Operation) for i in results)
+
+        pages = []
+
+        async for page in pager.pages:
+            pages.append(page)
+        for page_, token in zip(pages, ["", "", "", "abc", "def", "ghi", ""]):
+            assert page_.next_page_token == token
+
+
+def test_get_operation_rest():
+    client = _get_operations_client(is_async=False)
+
+    # Mock the http request call within the method and fake a response.
+    with mock.patch.object(_get_session_type(is_async=False), "request") as req:
         # Designate an appropriate value for the returned response.
         return_value = operations_pb2.Operation(
             name="operations/sample1",
@@ -580,10 +799,46 @@
     assert response.done is True
 
 
-def test_get_operation_rest_failure():
-    client = _get_operations_client(http_options=None)
+@pytest.mark.asyncio
+async def test_get_operation_rest_async():
+    if not GOOGLE_AUTH_AIO_INSTALLED:
+        pytest.skip("Skipped because google-api-core[async_rest] is not installed")
+    client = _get_operations_client(is_async=True)
 
-    with mock.patch.object(Session, "request") as req:
+    # Mock the http request call within the method and fake a response.
+    with mock.patch.object(_get_session_type(is_async=True), "request") as req:
+        # Designate an appropriate value for the returned response.
+        return_value = operations_pb2.Operation(
+            name="operations/sample1",
+            done=True,
+            error=status_pb2.Status(code=411),
+        )
+
+        # Wrap the value into a proper Response obj
+        response_value = mock.Mock()
+        response_value.status_code = 200
+        json_return_value = json_format.MessageToJson(return_value)
+        response_value.read = mock.AsyncMock(return_value=json_return_value)
+        req.return_value = response_value
+        response = await client.get_operation("operations/sample1")
+
+    actual_args = req.call_args
+    assert actual_args.args[0] == "GET"
+    assert (
+        actual_args.args[1]
+        == "https://longrunning.googleapis.com/v3/operations/sample1"
+    )
+
+    # Establish that the response is the type that we expect.
+    assert isinstance(response, operations_pb2.Operation)
+    assert response.name == "operations/sample1"
+    assert response.done is True
+
+
+def test_get_operation_rest_failure():
+    client = _get_operations_client(is_async=False, http_options=None)
+
+    with mock.patch.object(_get_session_type(is_async=False), "request") as req:
         response_value = Response()
         response_value.status_code = 400
         mock_request = mock.MagicMock()
@@ -595,13 +850,30 @@
             client.get_operation("sample0/operations/sample1")
 
 
-def test_delete_operation_rest(
-    transport: str = "rest", request_type=operations_pb2.DeleteOperationRequest
-):
-    client = _get_operations_client()
+@pytest.mark.asyncio
+async def test_get_operation_rest_failure_async():
+    if not GOOGLE_AUTH_AIO_INSTALLED:
+        pytest.skip("Skipped because google-api-core[async_rest] is not installed")
+    client = _get_operations_client(is_async=True, http_options=None)
+
+    with mock.patch.object(_get_session_type(is_async=True), "request") as req:
+        response_value = mock.Mock()
+        response_value.status_code = 400
+        response_value.read = mock.AsyncMock(return_value=b"{}")
+        mock_request = mock.MagicMock()
+        mock_request.method = "GET"
+        mock_request.url = "https://longrunning.googleapis.com/v1/operations/sample1"
+        response_value.request = mock_request
+        req.return_value = response_value
+        with pytest.raises(core_exceptions.GoogleAPIError):
+            await client.get_operation("sample0/operations/sample1")
+
+
+def test_delete_operation_rest():
+    client = _get_operations_client(is_async=False)
 
     # Mock the http request call within the method and fake a response.
-    with mock.patch.object(Session, "request") as req:
+    with mock.patch.object(_get_session_type(is_async=False), "request") as req:
         # Wrap the value into a proper Response obj
         response_value = Response()
         response_value.status_code = 200
@@ -618,10 +890,36 @@
         )
 
 
-def test_delete_operation_rest_failure():
-    client = _get_operations_client(http_options=None)
+@pytest.mark.asyncio
+async def test_delete_operation_rest_async():
+    if not GOOGLE_AUTH_AIO_INSTALLED:
+        pytest.skip("Skipped because google-api-core[async_rest] is not installed")
+    client = _get_operations_client(is_async=True)
 
-    with mock.patch.object(Session, "request") as req:
+    # Mock the http request call within the method and fake a response.
+    with mock.patch.object(_get_session_type(is_async=True), "request") as req:
+        # Wrap the value into a proper Response obj
+        response_value = mock.Mock()
+        response_value.status_code = 200
+        json_return_value = ""
+        response_value.read = mock.AsyncMock(
+            return_value=json_return_value.encode("UTF-8")
+        )
+        req.return_value = response_value
+        await client.delete_operation(name="operations/sample1")
+        assert req.call_count == 1
+        actual_args = req.call_args
+        assert actual_args.args[0] == "DELETE"
+        assert (
+            actual_args.args[1]
+            == "https://longrunning.googleapis.com/v3/operations/sample1"
+        )
+
+
+def test_delete_operation_rest_failure():
+    client = _get_operations_client(is_async=False, http_options=None)
+
+    with mock.patch.object(_get_session_type(is_async=False), "request") as req:
         response_value = Response()
         response_value.status_code = 400
         mock_request = mock.MagicMock()
@@ -633,11 +931,30 @@
             client.delete_operation(name="sample0/operations/sample1")
 
 
-def test_cancel_operation_rest(transport: str = "rest"):
-    client = _get_operations_client()
+@pytest.mark.asyncio
+async def test_delete_operation_rest_failure_async():
+    if not GOOGLE_AUTH_AIO_INSTALLED:
+        pytest.skip("Skipped because google-api-core[async_rest] is not installed")
+    client = _get_operations_client(is_async=True, http_options=None)
+
+    with mock.patch.object(_get_session_type(is_async=True), "request") as req:
+        response_value = mock.Mock()
+        response_value.status_code = 400
+        response_value.read = mock.AsyncMock(return_value=b"{}")
+        mock_request = mock.MagicMock()
+        mock_request.method = "DELETE"
+        mock_request.url = "https://longrunning.googleapis.com/v1/operations/sample1"
+        response_value.request = mock_request
+        req.return_value = response_value
+        with pytest.raises(core_exceptions.GoogleAPIError):
+            await client.delete_operation(name="sample0/operations/sample1")
+
+
+def test_cancel_operation_rest():
+    client = _get_operations_client(is_async=False)
 
     # Mock the http request call within the method and fake a response.
-    with mock.patch.object(Session, "request") as req:
+    with mock.patch.object(_get_session_type(is_async=False), "request") as req:
         # Wrap the value into a proper Response obj
         response_value = Response()
         response_value.status_code = 200
@@ -654,10 +971,36 @@
         )
 
 
-def test_cancel_operation_rest_failure():
-    client = _get_operations_client(http_options=None)
+@pytest.mark.asyncio
+async def test_cancel_operation_rest_async():
+    if not GOOGLE_AUTH_AIO_INSTALLED:
+        pytest.skip("Skipped because google-api-core[async_rest] is not installed")
+    client = _get_operations_client(is_async=True)
 
-    with mock.patch.object(Session, "request") as req:
+    # Mock the http request call within the method and fake a response.
+    with mock.patch.object(_get_session_type(is_async=True), "request") as req:
+        # Wrap the value into a proper Response obj
+        response_value = mock.Mock()
+        response_value.status_code = 200
+        json_return_value = ""
+        response_value.read = mock.AsyncMock(
+            return_value=json_return_value.encode("UTF-8")
+        )
+        req.return_value = response_value
+        await client.cancel_operation(name="operations/sample1")
+        assert req.call_count == 1
+        actual_args = req.call_args
+        assert actual_args.args[0] == "POST"
+        assert (
+            actual_args.args[1]
+            == "https://longrunning.googleapis.com/v3/operations/sample1:cancel"
+        )
+
+
+def test_cancel_operation_rest_failure():
+    client = _get_operations_client(is_async=False, http_options=None)
+
+    with mock.patch.object(_get_session_type(is_async=False), "request") as req:
         response_value = Response()
         response_value.status_code = 400
         mock_request = mock.MagicMock()
@@ -671,52 +1014,79 @@
             client.cancel_operation(name="sample0/operations/sample1")
 
 
-def test_credentials_transport_error():
+@pytest.mark.asyncio
+async def test_cancel_operation_rest_failure_async():
+    if not GOOGLE_AUTH_AIO_INSTALLED:
+        pytest.skip("Skipped because google-api-core[async_rest] is not installed")
+    client = _get_operations_client(is_async=True, http_options=None)
+
+    with mock.patch.object(_get_session_type(is_async=True), "request") as req:
+        response_value = mock.Mock()
+        response_value.status_code = 400
+        response_value.read = mock.AsyncMock(return_value=b"{}")
+        mock_request = mock.MagicMock()
+        mock_request.method = "POST"
+        mock_request.url = (
+            "https://longrunning.googleapis.com/v1/operations/sample1:cancel"
+        )
+        response_value.request = mock_request
+        req.return_value = response_value
+        with pytest.raises(core_exceptions.GoogleAPIError):
+            await client.cancel_operation(name="sample0/operations/sample1")
+
+
+@pytest.mark.parametrize(
+    "client_class,transport_class,credentials",
+    PYPARAM_CLIENT_TRANSPORT_CREDENTIALS,
+)
+def test_credentials_transport_error(client_class, transport_class, credentials):
+
     # It is an error to provide credentials and a transport instance.
-    transport = transports.OperationsRestTransport(
-        credentials=ga_credentials.AnonymousCredentials(),
-    )
+    transport = transport_class(credentials=credentials)
     with pytest.raises(ValueError):
-        AbstractOperationsClient(
+        client_class(
             credentials=ga_credentials.AnonymousCredentials(),
             transport=transport,
         )
 
     # It is an error to provide a credentials file and a transport instance.
-    transport = transports.OperationsRestTransport(
-        credentials=ga_credentials.AnonymousCredentials(),
-    )
+    transport = transport_class(credentials=credentials)
     with pytest.raises(ValueError):
-        AbstractOperationsClient(
+        client_class(
             client_options={"credentials_file": "credentials.json"},
             transport=transport,
         )
 
     # It is an error to provide scopes and a transport instance.
-    transport = transports.OperationsRestTransport(
-        credentials=ga_credentials.AnonymousCredentials(),
-    )
+    transport = transport_class(credentials=credentials)
     with pytest.raises(ValueError):
-        AbstractOperationsClient(
+        client_class(
             client_options={"scopes": ["1", "2"]},
             transport=transport,
         )
 
 
-def test_transport_instance():
+@pytest.mark.parametrize(
+    "client_class,transport_class,credentials",
+    PYPARAM_CLIENT_TRANSPORT_CREDENTIALS,
+)
+def test_transport_instance(client_class, transport_class, credentials):
     # A client may be instantiated with a custom transport instance.
-    transport = transports.OperationsRestTransport(
-        credentials=ga_credentials.AnonymousCredentials(),
+    transport = transport_class(
+        credentials=credentials,
     )
-    client = AbstractOperationsClient(transport=transport)
+    client = client_class(transport=transport)
     assert client.transport is transport
 
 
-@pytest.mark.parametrize("transport_class", [transports.OperationsRestTransport])
-def test_transport_adc(transport_class):
+@pytest.mark.parametrize(
+    "client_class,transport_class,credentials",
+    PYPARAM_CLIENT_TRANSPORT_CREDENTIALS,
+)
+def test_transport_adc(client_class, transport_class, credentials):
     # Test default credentials are used if not provided.
     with mock.patch.object(google.auth, "default") as adc:
-        adc.return_value = (ga_credentials.AnonymousCredentials(), None)
+        adc.return_value = (credentials, None)
         transport_class()
         adc.assert_called_once()
 
@@ -788,32 +1158,59 @@
         adc.assert_called_once()
 
 
-def test_operations_auth_adc():
+@pytest.mark.parametrize(
+    "client_class",
+    PYPARAM_CLIENT,
+)
+def test_operations_auth_adc(client_class):
     # If no credentials are provided, we should use ADC credentials.
     with mock.patch.object(google.auth, "default", autospec=True) as adc:
         adc.return_value = (ga_credentials.AnonymousCredentials(), None)
-        AbstractOperationsClient()
-        adc.assert_called_once_with(
-            scopes=None,
-            default_scopes=(),
-            quota_project_id=None,
-        )
+
+        if "async" in str(client_class).lower():
+            # TODO(): Add support for adc to async REST transport.
+            # NOTE: Ideally, the logic for adc shouldn't be called if transport
+            # is set to async REST. If the user does not configure credentials
+            # of type `google.auth.aio.credentials.Credentials`,
+            # we should raise an exception to avoid the adc workflow.
+            with pytest.raises(google.auth.exceptions.InvalidType):
+                client_class()
+        else:
+            client_class()
+            adc.assert_called_once_with(
+                scopes=None,
+                default_scopes=(),
+                quota_project_id=None,
+            )
 
 
-def test_operations_http_transport_client_cert_source_for_mtls():
+# TODO(https://github.com/googleapis/python-api-core/issues/705): Add
+# testing for `transports.AsyncOperationsRestTransport` once MTLS is supported
+# in `google.auth.aio.transport`.
+@pytest.mark.parametrize(
+    "transport_class",
+    [
+        transports.OperationsRestTransport,
+    ],
+)
+def test_operations_http_transport_client_cert_source_for_mtls(transport_class):
     cred = ga_credentials.AnonymousCredentials()
     with mock.patch(
         "google.auth.transport.requests.AuthorizedSession.configure_mtls_channel"
     ) as mock_configure_mtls_channel:
-        transports.OperationsRestTransport(
+        transport_class(
             credentials=cred, client_cert_source_for_mtls=client_cert_source_callback
         )
         mock_configure_mtls_channel.assert_called_once_with(client_cert_source_callback)
 
 
-def test_operations_host_no_port():
-    client = AbstractOperationsClient(
-        credentials=ga_credentials.AnonymousCredentials(),
+@pytest.mark.parametrize(
+    "client_class,transport_class,credentials",
+    PYPARAM_CLIENT_TRANSPORT_CREDENTIALS,
+)
+def test_operations_host_no_port(client_class, transport_class, credentials):
+    client = client_class(
+        credentials=credentials,
         client_options=client_options.ClientOptions(
             api_endpoint="longrunning.googleapis.com"
         ),
@@ -821,9 +1218,13 @@
     assert client.transport._host == "https://longrunning.googleapis.com"
 
 
-def test_operations_host_with_port():
-    client = AbstractOperationsClient(
-        credentials=ga_credentials.AnonymousCredentials(),
+@pytest.mark.parametrize(
+    "client_class,transport_class,credentials",
+    PYPARAM_CLIENT_TRANSPORT_CREDENTIALS,
+)
+def test_operations_host_with_port(client_class, transport_class, credentials):
+    client = client_class(
+        credentials=credentials,
         client_options=client_options.ClientOptions(
             api_endpoint="longrunning.googleapis.com:8000"
         ),
@@ -831,127 +1232,165 @@
     assert client.transport._host == "https://longrunning.googleapis.com:8000"
 
 
-def test_common_billing_account_path():
+@pytest.mark.parametrize(
+    "client_class",
+    PYPARAM_CLIENT,
+)
+def test_common_billing_account_path(client_class):
     billing_account = "squid"
     expected = "billingAccounts/{billing_account}".format(
         billing_account=billing_account,
     )
-    actual = AbstractOperationsClient.common_billing_account_path(billing_account)
+    actual = client_class.common_billing_account_path(billing_account)
     assert expected == actual
 
 
-def test_parse_common_billing_account_path():
+@pytest.mark.parametrize(
+    "client_class",
+    PYPARAM_CLIENT,
+)
+def test_parse_common_billing_account_path(client_class):
     expected = {
         "billing_account": "clam",
     }
-    path = AbstractOperationsClient.common_billing_account_path(**expected)
+    path = client_class.common_billing_account_path(**expected)
 
     # Check that the path construction is reversible.
-    actual = AbstractOperationsClient.parse_common_billing_account_path(path)
+    actual = client_class.parse_common_billing_account_path(path)
     assert expected == actual
 
 
-def test_common_folder_path():
+@pytest.mark.parametrize(
+    "client_class",
+    PYPARAM_CLIENT,
+)
+def test_common_folder_path(client_class):
     folder = "whelk"
     expected = "folders/{folder}".format(
         folder=folder,
     )
-    actual = AbstractOperationsClient.common_folder_path(folder)
+    actual = client_class.common_folder_path(folder)
     assert expected == actual
 
 
-def test_parse_common_folder_path():
+@pytest.mark.parametrize(
+    "client_class",
+    PYPARAM_CLIENT,
+)
+def test_parse_common_folder_path(client_class):
     expected = {
         "folder": "octopus",
     }
-    path = AbstractOperationsClient.common_folder_path(**expected)
+    path = client_class.common_folder_path(**expected)
 
     # Check that the path construction is reversible.
-    actual = AbstractOperationsClient.parse_common_folder_path(path)
+    actual = client_class.parse_common_folder_path(path)
     assert expected == actual
 
 
-def test_common_organization_path():
+@pytest.mark.parametrize(
+    "client_class",
+    PYPARAM_CLIENT,
+)
+def test_common_organization_path(client_class):
     organization = "oyster"
     expected = "organizations/{organization}".format(
         organization=organization,
     )
-    actual = AbstractOperationsClient.common_organization_path(organization)
+    actual = client_class.common_organization_path(organization)
     assert expected == actual
 
 
-def test_parse_common_organization_path():
+@pytest.mark.parametrize(
+    "client_class",
+    PYPARAM_CLIENT,
+)
+def test_parse_common_organization_path(client_class):
     expected = {
         "organization": "nudibranch",
     }
-    path = AbstractOperationsClient.common_organization_path(**expected)
+    path = client_class.common_organization_path(**expected)
 
     # Check that the path construction is reversible.
-    actual = AbstractOperationsClient.parse_common_organization_path(path)
+    actual = client_class.parse_common_organization_path(path)
     assert expected == actual
 
 
-def test_common_project_path():
+@pytest.mark.parametrize(
+    "client_class",
+    PYPARAM_CLIENT,
+)
+def test_common_project_path(client_class):
     project = "cuttlefish"
     expected = "projects/{project}".format(
         project=project,
     )
-    actual = AbstractOperationsClient.common_project_path(project)
+    actual = client_class.common_project_path(project)
     assert expected == actual
 
 
-def test_parse_common_project_path():
+@pytest.mark.parametrize(
+    "client_class",
+    PYPARAM_CLIENT,
+)
+def test_parse_common_project_path(client_class):
     expected = {
         "project": "mussel",
     }
-    path = AbstractOperationsClient.common_project_path(**expected)
+    path = client_class.common_project_path(**expected)
 
     # Check that the path construction is reversible.
-    actual = AbstractOperationsClient.parse_common_project_path(path)
+    actual = client_class.parse_common_project_path(path)
     assert expected == actual
 
 
-def test_common_location_path():
+@pytest.mark.parametrize(
+    "client_class",
+    PYPARAM_CLIENT,
+)
+def test_common_location_path(client_class):
     project = "winkle"
     location = "nautilus"
     expected = "projects/{project}/locations/{location}".format(
         project=project,
         location=location,
     )
-    actual = AbstractOperationsClient.common_location_path(project, location)
+    actual = client_class.common_location_path(project, location)
     assert expected == actual
 
 
-def test_parse_common_location_path():
+@pytest.mark.parametrize(
+    "client_class",
+    PYPARAM_CLIENT,
+)
+def test_parse_common_location_path(client_class):
     expected = {
         "project": "scallop",
         "location": "abalone",
     }
-    path = AbstractOperationsClient.common_location_path(**expected)
+    path = client_class.common_location_path(**expected)
 
     # Check that the path construction is reversible.
-    actual = AbstractOperationsClient.parse_common_location_path(path)
+    actual = client_class.parse_common_location_path(path)
     assert expected == actual
 
 
-def test_client_withDEFAULT_CLIENT_INFO():
+@pytest.mark.parametrize(
+    "client_class,transport_class,credentials",
+    PYPARAM_CLIENT_TRANSPORT_CREDENTIALS,
+)
+def test_client_withDEFAULT_CLIENT_INFO(client_class, transport_class, credentials):
     client_info = gapic_v1.client_info.ClientInfo()
-
-    with mock.patch.object(
-        transports.OperationsTransport, "_prep_wrapped_messages"
-    ) as prep:
-        AbstractOperationsClient(
-            credentials=ga_credentials.AnonymousCredentials(),
+    with mock.patch.object(transport_class, "_prep_wrapped_messages") as prep:
+        client_class(
+            credentials=credentials,
             client_info=client_info,
         )
         prep.assert_called_once_with(client_info)
 
-    with mock.patch.object(
-        transports.OperationsTransport, "_prep_wrapped_messages"
-    ) as prep:
-        transport_class = AbstractOperationsClient.get_transport_class()
+    with mock.patch.object(transport_class, "_prep_wrapped_messages") as prep:
         transport_class(
-            credentials=ga_credentials.AnonymousCredentials(),
+            credentials=credentials,
             client_info=client_info,
         )
         prep.assert_called_once_with(client_info)