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