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 report_errors_service 
    37from .base import ReportErrorsServiceTransport, DEFAULT_CLIENT_INFO 
    38from .grpc import ReportErrorsServiceGrpcTransport 
    39 
    40try: 
    41    from google.api_core import client_logging  # type: ignore 
    42 
    43    CLIENT_LOGGING_SUPPORTED = True  # pragma: NO COVER 
    44except ImportError:  # pragma: NO COVER 
    45    CLIENT_LOGGING_SUPPORTED = False 
    46 
    47_LOGGER = std_logging.getLogger(__name__) 
    48 
    49 
    50class _LoggingClientAIOInterceptor( 
    51    grpc.aio.UnaryUnaryClientInterceptor 
    52):  # pragma: NO COVER 
    53    async def intercept_unary_unary(self, continuation, client_call_details, request): 
    54        logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor( 
    55            std_logging.DEBUG 
    56        ) 
    57        if logging_enabled:  # pragma: NO COVER 
    58            request_metadata = client_call_details.metadata 
    59            if isinstance(request, proto.Message): 
    60                request_payload = type(request).to_json(request) 
    61            elif isinstance(request, google.protobuf.message.Message): 
    62                request_payload = MessageToJson(request) 
    63            else: 
    64                request_payload = f"{type(request).__name__}: {pickle.dumps(request)}" 
    65 
    66            request_metadata = { 
    67                key: value.decode("utf-8") if isinstance(value, bytes) else value 
    68                for key, value in request_metadata 
    69            } 
    70            grpc_request = { 
    71                "payload": request_payload, 
    72                "requestMethod": "grpc", 
    73                "metadata": dict(request_metadata), 
    74            } 
    75            _LOGGER.debug( 
    76                f"Sending request for {client_call_details.method}", 
    77                extra={ 
    78                    "serviceName": "google.devtools.clouderrorreporting.v1beta1.ReportErrorsService", 
    79                    "rpcName": str(client_call_details.method), 
    80                    "request": grpc_request, 
    81                    "metadata": grpc_request["metadata"], 
    82                }, 
    83            ) 
    84        response = await continuation(client_call_details, request) 
    85        if logging_enabled:  # pragma: NO COVER 
    86            response_metadata = await response.trailing_metadata() 
    87            # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples 
    88            metadata = ( 
    89                dict([(k, str(v)) for k, v in response_metadata]) 
    90                if response_metadata 
    91                else None 
    92            ) 
    93            result = await response 
    94            if isinstance(result, proto.Message): 
    95                response_payload = type(result).to_json(result) 
    96            elif isinstance(result, google.protobuf.message.Message): 
    97                response_payload = MessageToJson(result) 
    98            else: 
    99                response_payload = f"{type(result).__name__}: {pickle.dumps(result)}" 
    100            grpc_response = { 
    101                "payload": response_payload, 
    102                "metadata": metadata, 
    103                "status": "OK", 
    104            } 
    105            _LOGGER.debug( 
    106                f"Received response to rpc {client_call_details.method}.", 
    107                extra={ 
    108                    "serviceName": "google.devtools.clouderrorreporting.v1beta1.ReportErrorsService", 
    109                    "rpcName": str(client_call_details.method), 
    110                    "response": grpc_response, 
    111                    "metadata": grpc_response["metadata"], 
    112                }, 
    113            ) 
    114        return response 
    115 
    116 
    117class ReportErrorsServiceGrpcAsyncIOTransport(ReportErrorsServiceTransport): 
    118    """gRPC AsyncIO backend transport for ReportErrorsService. 
    119 
    120    An API for reporting error events. 
    121 
    122    This class defines the same methods as the primary client, so the 
    123    primary client can load the underlying transport implementation 
    124    and call it. 
    125 
    126    It sends protocol buffers over the wire using gRPC (which is built on 
    127    top of HTTP/2); the ``grpcio`` package must be installed. 
    128    """ 
    129 
    130    _grpc_channel: aio.Channel 
    131    _stubs: Dict[str, Callable] = {} 
    132 
    133    @classmethod 
    134    def create_channel( 
    135        cls, 
    136        host: str = "clouderrorreporting.googleapis.com", 
    137        credentials: Optional[ga_credentials.Credentials] = None, 
    138        credentials_file: Optional[str] = None, 
    139        scopes: Optional[Sequence[str]] = None, 
    140        quota_project_id: Optional[str] = None, 
    141        **kwargs, 
    142    ) -> aio.Channel: 
    143        """Create and return a gRPC AsyncIO channel object. 
    144        Args: 
    145            host (Optional[str]): The host for the channel to use. 
    146            credentials (Optional[~.Credentials]): The 
    147                authorization credentials to attach to requests. These 
    148                credentials identify this application to the service. If 
    149                none are specified, the client will attempt to ascertain 
    150                the credentials from the environment. 
    151            credentials_file (Optional[str]): A file with credentials that can 
    152                be loaded with :func:`google.auth.load_credentials_from_file`. 
    153            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this 
    154                service. These are only used when credentials are not specified and 
    155                are passed to :func:`google.auth.default`. 
    156            quota_project_id (Optional[str]): An optional project to use for billing 
    157                and quota. 
    158            kwargs (Optional[dict]): Keyword arguments, which are passed to the 
    159                channel creation. 
    160        Returns: 
    161            aio.Channel: A gRPC AsyncIO channel object. 
    162        """ 
    163 
    164        return grpc_helpers_async.create_channel( 
    165            host, 
    166            credentials=credentials, 
    167            credentials_file=credentials_file, 
    168            quota_project_id=quota_project_id, 
    169            default_scopes=cls.AUTH_SCOPES, 
    170            scopes=scopes, 
    171            default_host=cls.DEFAULT_HOST, 
    172            **kwargs, 
    173        ) 
    174 
    175    def __init__( 
    176        self, 
    177        *, 
    178        host: str = "clouderrorreporting.googleapis.com", 
    179        credentials: Optional[ga_credentials.Credentials] = None, 
    180        credentials_file: Optional[str] = None, 
    181        scopes: Optional[Sequence[str]] = None, 
    182        channel: Optional[Union[aio.Channel, Callable[..., aio.Channel]]] = None, 
    183        api_mtls_endpoint: Optional[str] = None, 
    184        client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None, 
    185        ssl_channel_credentials: Optional[grpc.ChannelCredentials] = None, 
    186        client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None, 
    187        quota_project_id: Optional[str] = None, 
    188        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, 
    189        always_use_jwt_access: Optional[bool] = False, 
    190        api_audience: Optional[str] = None, 
    191    ) -> None: 
    192        """Instantiate the transport. 
    193 
    194        Args: 
    195            host (Optional[str]): 
    196                 The hostname to connect to (default: 'clouderrorreporting.googleapis.com'). 
    197            credentials (Optional[google.auth.credentials.Credentials]): The 
    198                authorization credentials to attach to requests. These 
    199                credentials identify the application to the service; if none 
    200                are specified, the client will attempt to ascertain the 
    201                credentials from the environment. 
    202                This argument is ignored if a ``channel`` instance is provided. 
    203            credentials_file (Optional[str]): A file with credentials that can 
    204                be loaded with :func:`google.auth.load_credentials_from_file`. 
    205                This argument is ignored if a ``channel`` instance is provided. 
    206            scopes (Optional[Sequence[str]]): A optional list of scopes needed for this 
    207                service. These are only used when credentials are not specified and 
    208                are passed to :func:`google.auth.default`. 
    209            channel (Optional[Union[aio.Channel, Callable[..., aio.Channel]]]): 
    210                A ``Channel`` instance through which to make calls, or a Callable 
    211                that constructs and returns one. If set to None, ``self.create_channel`` 
    212                is used to create the channel. If a Callable is given, it will be called 
    213                with the same arguments as used in ``self.create_channel``. 
    214            api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. 
    215                If provided, it overrides the ``host`` argument and tries to create 
    216                a mutual TLS channel with client SSL credentials from 
    217                ``client_cert_source`` or application default SSL credentials. 
    218            client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): 
    219                Deprecated. A callback to provide client SSL certificate bytes and 
    220                private key bytes, both in PEM format. It is ignored if 
    221                ``api_mtls_endpoint`` is None. 
    222            ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials 
    223                for the grpc channel. It is ignored if a ``channel`` instance is provided. 
    224            client_cert_source_for_mtls (Optional[Callable[[], Tuple[bytes, bytes]]]): 
    225                A callback to provide client certificate bytes and private key bytes, 
    226                both in PEM format. It is used to configure a mutual TLS channel. It is 
    227                ignored if a ``channel`` instance or ``ssl_channel_credentials`` is provided. 
    228            quota_project_id (Optional[str]): An optional project to use for billing 
    229                and quota. 
    230            client_info (google.api_core.gapic_v1.client_info.ClientInfo): 
    231                The client info used to send a user-agent string along with 
    232                API requests. If ``None``, then default info will be used. 
    233                Generally, you only need to set this if you're developing 
    234                your own client library. 
    235            always_use_jwt_access (Optional[bool]): Whether self signed JWT should 
    236                be used for service account credentials. 
    237 
    238        Raises: 
    239            google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport 
    240              creation failed for any reason. 
    241          google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` 
    242              and ``credentials_file`` are passed. 
    243        """ 
    244        self._grpc_channel = None 
    245        self._ssl_channel_credentials = ssl_channel_credentials 
    246        self._stubs: Dict[str, Callable] = {} 
    247 
    248        if api_mtls_endpoint: 
    249            warnings.warn("api_mtls_endpoint is deprecated", DeprecationWarning) 
    250        if client_cert_source: 
    251            warnings.warn("client_cert_source is deprecated", DeprecationWarning) 
    252 
    253        if isinstance(channel, aio.Channel): 
    254            # Ignore credentials if a channel was passed. 
    255            credentials = None 
    256            self._ignore_credentials = True 
    257            # If a channel was explicitly provided, set it. 
    258            self._grpc_channel = channel 
    259            self._ssl_channel_credentials = None 
    260        else: 
    261            if api_mtls_endpoint: 
    262                host = api_mtls_endpoint 
    263 
    264                # Create SSL credentials with client_cert_source or application 
    265                # default SSL credentials. 
    266                if client_cert_source: 
    267                    cert, key = client_cert_source() 
    268                    self._ssl_channel_credentials = grpc.ssl_channel_credentials( 
    269                        certificate_chain=cert, private_key=key 
    270                    ) 
    271                else: 
    272                    self._ssl_channel_credentials = SslCredentials().ssl_credentials 
    273 
    274            else: 
    275                if client_cert_source_for_mtls and not ssl_channel_credentials: 
    276                    cert, key = client_cert_source_for_mtls() 
    277                    self._ssl_channel_credentials = grpc.ssl_channel_credentials( 
    278                        certificate_chain=cert, private_key=key 
    279                    ) 
    280 
    281        # The base transport sets the host, credentials and scopes 
    282        super().__init__( 
    283            host=host, 
    284            credentials=credentials, 
    285            credentials_file=credentials_file, 
    286            scopes=scopes, 
    287            quota_project_id=quota_project_id, 
    288            client_info=client_info, 
    289            always_use_jwt_access=always_use_jwt_access, 
    290            api_audience=api_audience, 
    291        ) 
    292 
    293        if not self._grpc_channel: 
    294            # initialize with the provided callable or the default channel 
    295            channel_init = channel or type(self).create_channel 
    296            self._grpc_channel = channel_init( 
    297                self._host, 
    298                # use the credentials which are saved 
    299                credentials=self._credentials, 
    300                # Set ``credentials_file`` to ``None`` here as 
    301                # the credentials that we saved earlier should be used. 
    302                credentials_file=None, 
    303                scopes=self._scopes, 
    304                ssl_credentials=self._ssl_channel_credentials, 
    305                quota_project_id=quota_project_id, 
    306                options=[ 
    307                    ("grpc.max_send_message_length", -1), 
    308                    ("grpc.max_receive_message_length", -1), 
    309                ], 
    310            ) 
    311 
    312        self._interceptor = _LoggingClientAIOInterceptor() 
    313        self._grpc_channel._unary_unary_interceptors.append(self._interceptor) 
    314        self._logged_channel = self._grpc_channel 
    315        self._wrap_with_kind = ( 
    316            "kind" in inspect.signature(gapic_v1.method_async.wrap_method).parameters 
    317        ) 
    318        # Wrap messages. This must be done after self._logged_channel exists 
    319        self._prep_wrapped_messages(client_info) 
    320 
    321    @property 
    322    def grpc_channel(self) -> aio.Channel: 
    323        """Create the channel designed to connect to this service. 
    324 
    325        This property caches on the instance; repeated calls return 
    326        the same channel. 
    327        """ 
    328        # Return the channel from cache. 
    329        return self._grpc_channel 
    330 
    331    @property 
    332    def report_error_event( 
    333        self, 
    334    ) -> Callable[ 
    335        [report_errors_service.ReportErrorEventRequest], 
    336        Awaitable[report_errors_service.ReportErrorEventResponse], 
    337    ]: 
    338        r"""Return a callable for the report error event method over gRPC. 
    339 
    340        Report an individual error event and record the event to a log. 
    341 
    342        This endpoint accepts **either** an OAuth token, **or** an `API 
    343        key <https://support.google.com/cloud/answer/6158862>`__ for 
    344        authentication. To use an API key, append it to the URL as the 
    345        value of a ``key`` parameter. For example: 
    346 
    347        ``POST https://clouderrorreporting.googleapis.com/v1beta1/{projectName}/events:report?key=123ABC456`` 
    348 
    349        **Note:** [Error Reporting] 
    350        (https://cloud.google.com/error-reporting) is a service built on 
    351        Cloud Logging and can analyze log entries when all of the 
    352        following are true: 
    353 
    354        -  Customer-managed encryption keys (CMEK) are disabled on the 
    355           log bucket. 
    356        -  The log bucket satisfies one of the following: 
    357 
    358           -  The log bucket is stored in the same project where the 
    359              logs originated. 
    360           -  The logs were routed to a project, and then that project 
    361              stored those logs in a log bucket that it owns. 
    362 
    363        Returns: 
    364            Callable[[~.ReportErrorEventRequest], 
    365                    Awaitable[~.ReportErrorEventResponse]]: 
    366                A function that, when called, will call the underlying RPC 
    367                on the server. 
    368        """ 
    369        # Generate a "stub function" on-the-fly which will actually make 
    370        # the request. 
    371        # gRPC handles serialization and deserialization, so we just need 
    372        # to pass in the functions for each. 
    373        if "report_error_event" not in self._stubs: 
    374            self._stubs["report_error_event"] = self._logged_channel.unary_unary( 
    375                "/google.devtools.clouderrorreporting.v1beta1.ReportErrorsService/ReportErrorEvent", 
    376                request_serializer=report_errors_service.ReportErrorEventRequest.serialize, 
    377                response_deserializer=report_errors_service.ReportErrorEventResponse.deserialize, 
    378            ) 
    379        return self._stubs["report_error_event"] 
    380 
    381    def _prep_wrapped_messages(self, client_info): 
    382        """Precompute the wrapped methods, overriding the base class method to use async wrappers.""" 
    383        self._wrapped_methods = { 
    384            self.report_error_event: self._wrap_method( 
    385                self.report_error_event, 
    386                default_timeout=None, 
    387                client_info=client_info, 
    388            ), 
    389        } 
    390 
    391    def _wrap_method(self, func, *args, **kwargs): 
    392        if self._wrap_with_kind:  # pragma: NO COVER 
    393            kwargs["kind"] = self.kind 
    394        return gapic_v1.method_async.wrap_method(func, *args, **kwargs) 
    395 
    396    def close(self): 
    397        return self._logged_channel.close() 
    398 
    399    @property 
    400    def kind(self) -> str: 
    401        return "grpc_asyncio" 
    402 
    403 
    404__all__ = ("ReportErrorsServiceGrpcAsyncIOTransport",)