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 common
34from google.cloud.errorreporting_v1beta1.types import error_group_service
35from .base import ErrorGroupServiceTransport, DEFAULT_CLIENT_INFO
36
37try:
38 from google.api_core import client_logging # type: ignore
39
40 CLIENT_LOGGING_SUPPORTED = True # pragma: NO COVER
41except ImportError: # pragma: NO COVER
42 CLIENT_LOGGING_SUPPORTED = False
43
44_LOGGER = std_logging.getLogger(__name__)
45
46
47class _LoggingClientInterceptor(grpc.UnaryUnaryClientInterceptor): # pragma: NO COVER
48 def intercept_unary_unary(self, continuation, client_call_details, request):
49 logging_enabled = CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
50 std_logging.DEBUG
51 )
52 if logging_enabled: # pragma: NO COVER
53 request_metadata = client_call_details.metadata
54 if isinstance(request, proto.Message):
55 request_payload = type(request).to_json(request)
56 elif isinstance(request, google.protobuf.message.Message):
57 request_payload = MessageToJson(request)
58 else:
59 request_payload = f"{type(request).__name__}: {pickle.dumps(request)}"
60
61 request_metadata = {
62 key: value.decode("utf-8") if isinstance(value, bytes) else value
63 for key, value in request_metadata
64 }
65 grpc_request = {
66 "payload": request_payload,
67 "requestMethod": "grpc",
68 "metadata": dict(request_metadata),
69 }
70 _LOGGER.debug(
71 f"Sending request for {client_call_details.method}",
72 extra={
73 "serviceName": "google.devtools.clouderrorreporting.v1beta1.ErrorGroupService",
74 "rpcName": str(client_call_details.method),
75 "request": grpc_request,
76 "metadata": grpc_request["metadata"],
77 },
78 )
79 response = continuation(client_call_details, request)
80 if logging_enabled: # pragma: NO COVER
81 response_metadata = response.trailing_metadata()
82 # Convert gRPC metadata `<class 'grpc.aio._metadata.Metadata'>` to list of tuples
83 metadata = (
84 dict([(k, str(v)) for k, v in response_metadata])
85 if response_metadata
86 else None
87 )
88 result = response.result()
89 if isinstance(result, proto.Message):
90 response_payload = type(result).to_json(result)
91 elif isinstance(result, google.protobuf.message.Message):
92 response_payload = MessageToJson(result)
93 else:
94 response_payload = f"{type(result).__name__}: {pickle.dumps(result)}"
95 grpc_response = {
96 "payload": response_payload,
97 "metadata": metadata,
98 "status": "OK",
99 }
100 _LOGGER.debug(
101 f"Received response for {client_call_details.method}.",
102 extra={
103 "serviceName": "google.devtools.clouderrorreporting.v1beta1.ErrorGroupService",
104 "rpcName": client_call_details.method,
105 "response": grpc_response,
106 "metadata": grpc_response["metadata"],
107 },
108 )
109 return response
110
111
112class ErrorGroupServiceGrpcTransport(ErrorGroupServiceTransport):
113 """gRPC backend transport for ErrorGroupService.
114
115 Service for retrieving and updating individual error groups.
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 get_group(
328 self,
329 ) -> Callable[[error_group_service.GetGroupRequest], common.ErrorGroup]:
330 r"""Return a callable for the get group method over gRPC.
331
332 Get the specified group.
333
334 Returns:
335 Callable[[~.GetGroupRequest],
336 ~.ErrorGroup]:
337 A function that, when called, will call the underlying RPC
338 on the server.
339 """
340 # Generate a "stub function" on-the-fly which will actually make
341 # the request.
342 # gRPC handles serialization and deserialization, so we just need
343 # to pass in the functions for each.
344 if "get_group" not in self._stubs:
345 self._stubs["get_group"] = self._logged_channel.unary_unary(
346 "/google.devtools.clouderrorreporting.v1beta1.ErrorGroupService/GetGroup",
347 request_serializer=error_group_service.GetGroupRequest.serialize,
348 response_deserializer=common.ErrorGroup.deserialize,
349 )
350 return self._stubs["get_group"]
351
352 @property
353 def update_group(
354 self,
355 ) -> Callable[[error_group_service.UpdateGroupRequest], common.ErrorGroup]:
356 r"""Return a callable for the update group method over gRPC.
357
358 Replace the data for the specified group.
359 Fails if the group does not exist.
360
361 Returns:
362 Callable[[~.UpdateGroupRequest],
363 ~.ErrorGroup]:
364 A function that, when called, will call the underlying RPC
365 on the server.
366 """
367 # Generate a "stub function" on-the-fly which will actually make
368 # the request.
369 # gRPC handles serialization and deserialization, so we just need
370 # to pass in the functions for each.
371 if "update_group" not in self._stubs:
372 self._stubs["update_group"] = self._logged_channel.unary_unary(
373 "/google.devtools.clouderrorreporting.v1beta1.ErrorGroupService/UpdateGroup",
374 request_serializer=error_group_service.UpdateGroupRequest.serialize,
375 response_deserializer=common.ErrorGroup.deserialize,
376 )
377 return self._stubs["update_group"]
378
379 def close(self):
380 self._logged_channel.close()
381
382 @property
383 def kind(self) -> str:
384 return "grpc"
385
386
387__all__ = ("ErrorGroupServiceGrpcTransport",)