1# -*- coding: utf-8 -*- 
    2# Copyright 2025 Google LLC 
    3# 
    4# Licensed under the Apache License, Version 2.0 (the "License"); 
    5# you may not use this file except in compliance with the License. 
    6# You may obtain a copy of the License at 
    7# 
    8#     http://www.apache.org/licenses/LICENSE-2.0 
    9# 
    10# Unless required by applicable law or agreed to in writing, software 
    11# distributed under the License is distributed on an "AS IS" BASIS, 
    12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
    13# See the License for the specific language governing permissions and 
    14# limitations under the License. 
    15# 
    16import inspect 
    17import json 
    18import pickle 
    19import logging as std_logging 
    20import warnings 
    21from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union 
    22 
    23from google.api_core import gapic_v1 
    24from google.api_core import grpc_helpers_async 
    25from google.api_core import exceptions as core_exceptions 
    26from google.api_core import retry_async as retries 
    27from google.auth import credentials as ga_credentials  # type: ignore 
    28from google.auth.transport.grpc import SslCredentials  # type: ignore 
    29from google.protobuf.json_format import MessageToJson 
    30import google.protobuf.message 
    31 
    32import grpc  # type: ignore 
    33import proto  # type: ignore 
    34from grpc.experimental import aio  # type: ignore 
    35 
    36from google.cloud.errorreporting_v1beta1.types import common 
    37from google.cloud.errorreporting_v1beta1.types import error_group_service 
    38from .base import ErrorGroupServiceTransport, DEFAULT_CLIENT_INFO 
    39from .grpc import ErrorGroupServiceGrpcTransport 
    40 
    41try: 
    42    from google.api_core import client_logging  # type: ignore 
    43 
    44    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER 
    45except ImportError:  # pragma: NO COVER 
    46    CLIENT_LOGGING_SUPPORTED = False 
    47 
    48_LOGGER = std_logging.getLogger(__name__) 
    49 
    50 
    51class _LoggingClientAIOInterceptor( 
    52    grpc.aio.UnaryUnaryClientInterceptor 
    53):  # pragma: NO COVER 
    54    async def intercept_unary_unary(self, continuation, client_call_details, request): 
    55        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( 
    56            std_logging.DEBUG 
    57        ) 
    58        if logging_enabled:  # pragma: NO COVER 
    59            request_metadata = client_call_details.metadata 
    60            if isinstance(request, proto.Message): 
    61                request_payload = type(request).to_json(request) 
    62            elif isinstance(request, google.protobuf.message.Message): 
    63                request_payload = MessageToJson(request) 
    64            else: 
    65                request_payload = f"{type(request).__name__}: {pickle.dumps(request)}" 
    66 
    67            request_metadata = { 
    68                key: value.decode("utf-8") if isinstance(value, bytes) else value 
    69                for key, value in request_metadata 
    70            } 
    71            grpc_request = { 
    72                "payload": request_payload, 
    73                "requestMethod": "grpc", 
    74                "metadata": dict(request_metadata), 
    75            } 
    76            _LOGGER.debug( 
    77                f"Sending request for {client_call_details.method}", 
    78                extra={ 
    79                    "serviceName": "google.devtools.clouderrorreporting.v1beta1.ErrorGroupService", 
    80                    "rpcName": str(client_call_details.method), 
    81                    "request": grpc_request, 
    82                    "metadata": grpc_request["metadata"], 
    83                }, 
    84            ) 
    85        response = await continuation(client_call_details, request) 
    86        if logging_enabled:  # pragma: NO COVER 
    87            response_metadata = await response.trailing_metadata() 
    88            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples 
    89            metadata = ( 
    90                dict([(k, str(v)) for k, v in response_metadata]) 
    91                if response_metadata 
    92                else None 
    93            ) 
    94            result = await response 
    95            if isinstance(result, proto.Message): 
    96                response_payload = type(result).to_json(result) 
    97            elif isinstance(result, google.protobuf.message.Message): 
    98                response_payload = MessageToJson(result) 
    99            else: 
    100                response_payload = f"{type(result).__name__}: {pickle.dumps(result)}" 
    101            grpc_response = { 
    102                "payload": response_payload, 
    103                "metadata": metadata, 
    104                "status": "OK", 
    105            } 
    106            _LOGGER.debug( 
    107                f"Received response to rpc {client_call_details.method}.", 
    108                extra={ 
    109                    "serviceName": "google.devtools.clouderrorreporting.v1beta1.ErrorGroupService", 
    110                    "rpcName": str(client_call_details.method), 
    111                    "response": grpc_response, 
    112                    "metadata": grpc_response["metadata"], 
    113                }, 
    114            ) 
    115        return response 
    116 
    117 
    118class ErrorGroupServiceGrpcAsyncIOTransport(ErrorGroupServiceTransport): 
    119    """gRPC AsyncIO backend transport for ErrorGroupService. 
    120 
    121    Service for retrieving and updating individual error groups. 
    122 
    123    This class defines the same methods as the primary client, so the 
    124    primary client can load the underlying transport implementation 
    125    and call it. 
    126 
    127    It sends protocol buffers over the wire using gRPC (which is built on 
    128    top of HTTP/2); the ``grpcio`` package must be installed. 
    129    """ 
    130 
    131    _grpc_channel: aio.Channel 
    132    _stubs: Dict[str, Callable] = {} 
    133 
    134    @classmethod 
    135    def create_channel( 
    136        cls, 
    137        host: str = "clouderrorreporting.googleapis.com", 
    138        credentials: Optional[ga_credentials.Credentials] = None, 
    139        credentials_file: Optional[str] = None, 
    140        scopes: Optional[Sequence[str]] = None, 
    141        quota_project_id: Optional[str] = None, 
    142        **kwargs, 
    143    ) -> aio.Channel: 
    144        """Create and return a gRPC AsyncIO channel object. 
    145        Args: 
    146            host (Optional[str]): The host for the channel to use. 
    147            credentials (Optional[~.Credentials]): The 
    148                authorization credentials to attach to requests. These 
    149                credentials identify this application to the service. If 
    150                none are specified, the client will attempt to ascertain 
    151                the credentials from the environment. 
    152            credentials_file (Optional[str]): A file with credentials that can 
    153                be loaded with :func:`google.auth.load_credentials_from_file`. 
    154            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this 
    155                service. These are only used when credentials are not specified and 
    156                are passed to :func:`google.auth.default`. 
    157            quota_project_id (Optional[str]): An optional project to use for billing 
    158                and quota. 
    159            kwargs (Optional[dict]): Keyword arguments, which are passed to the 
    160                channel creation. 
    161        Returns: 
    162            aio.Channel: A gRPC AsyncIO channel object. 
    163        """ 
    164 
    165        return grpc_helpers_async.create_channel( 
    166            host, 
    167            credentials=credentials, 
    168            credentials_file=credentials_file, 
    169            quota_project_id=quota_project_id, 
    170            default_scopes=cls.AUTH_SCOPES, 
    171            scopes=scopes, 
    172            default_host=cls.DEFAULT_HOST, 
    173            **kwargs, 
    174        ) 
    175 
    176    def __init__( 
    177        self, 
    178        *, 
    179        host: str = "clouderrorreporting.googleapis.com", 
    180        credentials: Optional[ga_credentials.Credentials] = None, 
    181        credentials_file: Optional[str] = None, 
    182        scopes: Optional[Sequence[str]] = None, 
    183        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None, 
    184        api_mtls_endpoint: Optional[str] = None, 
    185        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None, 
    186        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None, 
    187        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, 
    188        quota_project_id: Optional[str] = None, 
    189        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, 
    190        always_use_jwt_access: Optional[bool] = False, 
    191        api_audience: Optional[str] = None, 
    192    ) -> None: 
    193        """Instantiate the transport. 
    194 
    195        Args: 
    196            host (Optional[str]): 
    197                 The hostname to connect to (default: 'clouderrorreporting.googleapis.com'). 
    198            credentials (Optional[google.auth.credentials.Credentials]): The 
    199                authorization credentials to attach to requests. These 
    200                credentials identify the application to the service; if none 
    201                are specified, the client will attempt to ascertain the 
    202                credentials from the environment. 
    203                This argument is ignored if a ``channel`` instance is provided. 
    204            credentials_file (Optional[str]): A file with credentials that can 
    205                be loaded with :func:`google.auth.load_credentials_from_file`. 
    206                This argument is ignored if a ``channel`` instance is provided. 
    207            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this 
    208                service. These are only used when credentials are not specified and 
    209                are passed to :func:`google.auth.default`. 
    210            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]): 
    211                A ``Channel`` instance through which to make calls, or a Callable 
    212                that constructs and returns one. If set to None, ``self.create_channel`` 
    213                is used to create the channel. If a Callable is given, it will be called 
    214                with the same arguments as used in ``self.create_channel``. 
    215            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. 
    216                If provided, it overrides the ``host`` argument and tries to create 
    217                a mutual TLS channel with client SSL credentials from 
    218                ``client_cert_source`` or application default SSL credentials. 
    219            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): 
    220                Deprecated. A callback to provide client SSL certificate bytes and 
    221                private key bytes, both in PEM format. It is ignored if 
    222                ``api_mtls_endpoint`` is None. 
    223            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials 
    224                for the grpc channel. It is ignored if a ``channel`` instance is provided. 
    225            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): 
    226                A callback to provide client certificate bytes and private key bytes, 
    227                both in PEM format. It is used to configure a mutual TLS channel. It is 
    228                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided. 
    229            quota_project_id (Optional[str]): An optional project to use for billing 
    230                and quota. 
    231            client_info (google.api_core.gapic_v1.client_info.ClientInfo): 
    232                The client info used to send a user-agent string along with 
    233                API requests. If ``None``, then default info will be used. 
    234                Generally, you only need to set this if you're developing 
    235                your own client library. 
    236            always_use_jwt_access (Optional[bool]): Whether self signed JWT should 
    237                be used for service account credentials. 
    238 
    239        Raises: 
    240            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport 
    241              creation failed for any reason. 
    242          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` 
    243              and ``credentials_file`` are passed. 
    244        """ 
    245        self._grpc_channel = None 
    246        self._ssl_channel_credentials = ssl_channel_credentials 
    247        self._stubs: Dict[str, Callable] = {} 
    248 
    249        if api_mtls_endpoint: 
    250            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) 
    251        if client_cert_source: 
    252            warnings.warn("client_cert_source is deprecated", DeprecationWarning) 
    253 
    254        if isinstance(channel, aio.Channel): 
    255            # Ignore credentials if a channel was passed. 
    256            credentials = None 
    257            self._ignore_credentials = True 
    258            # If a channel was explicitly provided, set it. 
    259            self._grpc_channel = channel 
    260            self._ssl_channel_credentials = None 
    261        else: 
    262            if api_mtls_endpoint: 
    263                host = api_mtls_endpoint 
    264 
    265                # Create SSL credentials with client_cert_source or application 
    266                # default SSL credentials. 
    267                if client_cert_source: 
    268                    cert, key = client_cert_source() 
    269                    self._ssl_channel_credentials = grpc.ssl_channel_credentials( 
    270                        certificate_chain=cert, private_key=key 
    271                    ) 
    272                else: 
    273                    self._ssl_channel_credentials = SslCredentials().ssl_credentials 
    274 
    275            else: 
    276                if client_cert_source_for_mtls and not ssl_channel_credentials: 
    277                    cert, key = client_cert_source_for_mtls() 
    278                    self._ssl_channel_credentials = grpc.ssl_channel_credentials( 
    279                        certificate_chain=cert, private_key=key 
    280                    ) 
    281 
    282        # The base transport sets the host, credentials and scopes 
    283        super().__init__( 
    284            host=host, 
    285            credentials=credentials, 
    286            credentials_file=credentials_file, 
    287            scopes=scopes, 
    288            quota_project_id=quota_project_id, 
    289            client_info=client_info, 
    290            always_use_jwt_access=always_use_jwt_access, 
    291            api_audience=api_audience, 
    292        ) 
    293 
    294        if not self._grpc_channel: 
    295            # initialize with the provided callable or the default channel 
    296            channel_init = channel or type(self).create_channel 
    297            self._grpc_channel = channel_init( 
    298                self._host, 
    299                # use the credentials which are saved 
    300                credentials=self._credentials, 
    301                # Set ``credentials_file`` to ``None`` here as 
    302                # the credentials that we saved earlier should be used. 
    303                credentials_file=None, 
    304                scopes=self._scopes, 
    305                ssl_credentials=self._ssl_channel_credentials, 
    306                quota_project_id=quota_project_id, 
    307                options=[ 
    308                    ("grpc.max_send_message_length", -1), 
    309                    ("grpc.max_receive_message_length", -1), 
    310                ], 
    311            ) 
    312 
    313        self._interceptor = _LoggingClientAIOInterceptor() 
    314        self._grpc_channel._unary_unary_interceptors.append(self._interceptor) 
    315        self._logged_channel = self._grpc_channel 
    316        self._wrap_with_kind = ( 
    317            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters 
    318        ) 
    319        # Wrap messages. This must be done after self._logged_channel exists 
    320        self._prep_wrapped_messages(client_info) 
    321 
    322    @property 
    323    def grpc_channel(self) -> aio.Channel: 
    324        """Create the channel designed to connect to this service. 
    325 
    326        This property caches on the instance; repeated calls return 
    327        the same channel. 
    328        """ 
    329        # Return the channel from cache. 
    330        return self._grpc_channel 
    331 
    332    @property 
    333    def get_group( 
    334        self, 
    335    ) -> Callable[[error_group_service.GetGroupRequest], Awaitable[common.ErrorGroup]]: 
    336        r"""Return a callable for the get group method over gRPC. 
    337 
    338        Get the specified group. 
    339 
    340        Returns: 
    341            Callable[[~.GetGroupRequest], 
    342                    Awaitable[~.ErrorGroup]]: 
    343                A function that, when called, will call the underlying RPC 
    344                on the server. 
    345        """ 
    346        # Generate a "stub function" on-the-fly which will actually make 
    347        # the request. 
    348        # gRPC handles serialization and deserialization, so we just need 
    349        # to pass in the functions for each. 
    350        if "get_group" not in self._stubs: 
    351            self._stubs["get_group"] = self._logged_channel.unary_unary( 
    352                "/google.devtools.clouderrorreporting.v1beta1.ErrorGroupService/GetGroup", 
    353                request_serializer=error_group_service.GetGroupRequest.serialize, 
    354                response_deserializer=common.ErrorGroup.deserialize, 
    355            ) 
    356        return self._stubs["get_group"] 
    357 
    358    @property 
    359    def update_group( 
    360        self, 
    361    ) -> Callable[ 
    362        [error_group_service.UpdateGroupRequest], Awaitable[common.ErrorGroup] 
    363    ]: 
    364        r"""Return a callable for the update group method over gRPC. 
    365 
    366        Replace the data for the specified group. 
    367        Fails if the group does not exist. 
    368 
    369        Returns: 
    370            Callable[[~.UpdateGroupRequest], 
    371                    Awaitable[~.ErrorGroup]]: 
    372                A function that, when called, will call the underlying RPC 
    373                on the server. 
    374        """ 
    375        # Generate a "stub function" on-the-fly which will actually make 
    376        # the request. 
    377        # gRPC handles serialization and deserialization, so we just need 
    378        # to pass in the functions for each. 
    379        if "update_group" not in self._stubs: 
    380            self._stubs["update_group"] = self._logged_channel.unary_unary( 
    381                "/google.devtools.clouderrorreporting.v1beta1.ErrorGroupService/UpdateGroup", 
    382                request_serializer=error_group_service.UpdateGroupRequest.serialize, 
    383                response_deserializer=common.ErrorGroup.deserialize, 
    384            ) 
    385        return self._stubs["update_group"] 
    386 
    387    def _prep_wrapped_messages(self, client_info): 
    388        """Precompute the wrapped methods, overriding the base class method to use async wrappers.""" 
    389        self._wrapped_methods = { 
    390            self.get_group: self._wrap_method( 
    391                self.get_group, 
    392                default_timeout=None, 
    393                client_info=client_info, 
    394            ), 
    395            self.update_group: self._wrap_method( 
    396                self.update_group, 
    397                default_timeout=None, 
    398                client_info=client_info, 
    399            ), 
    400        } 
    401 
    402    def _wrap_method(self, func, *args, **kwargs): 
    403        if self._wrap_with_kind:  # pragma: NO COVER 
    404            kwargs["kind"] = self.kind 
    405        return gapic_v1.method_async.wrap_method(func, *args, **kwargs) 
    406 
    407    def close(self): 
    408        return self._logged_channel.close() 
    409 
    410    @property 
    411    def kind(self) -> str: 
    412        return "grpc_asyncio" 
    413 
    414 
    415__all__ = ("ErrorGroupServiceGrpcAsyncIOTransport",)