1# -*- coding: utf-8 -*-
2# Copyright 2023 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#
16
17from google.auth.transport.requests import AuthorizedSession # type: ignore
18import json # type: ignore
19import grpc # type: ignore
20from google.auth.transport.grpc import SslCredentials # type: ignore
21from google.auth import credentials as ga_credentials # type: ignore
22from google.api_core import exceptions as core_exceptions
23from google.api_core import retry as retries
24from google.api_core import rest_helpers
25from google.api_core import rest_streaming
26from google.api_core import path_template
27from google.api_core import gapic_v1
28
29from google.protobuf import json_format
30from requests import __version__ as requests_version
31import dataclasses
32import re
33from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union
34import warnings
35
36try:
37 OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault]
38except AttributeError: # pragma: NO COVER
39 OptionalRetry = Union[retries.Retry, object] # type: ignore
40
41
42from google.cloud.errorreporting_v1beta1.types import report_errors_service
43
44from .base import (
45 ReportErrorsServiceTransport,
46 DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO,
47)
48
49
50DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
51 gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
52 grpc_version=None,
53 rest_version=requests_version,
54)
55
56
57class ReportErrorsServiceRestInterceptor:
58 """Interceptor for ReportErrorsService.
59
60 Interceptors are used to manipulate requests, request metadata, and responses
61 in arbitrary ways.
62 Example use cases include:
63 * Logging
64 * Verifying requests according to service or custom semantics
65 * Stripping extraneous information from responses
66
67 These use cases and more can be enabled by injecting an
68 instance of a custom subclass when constructing the ReportErrorsServiceRestTransport.
69
70 .. code-block:: python
71 class MyCustomReportErrorsServiceInterceptor(ReportErrorsServiceRestInterceptor):
72 def pre_report_error_event(self, request, metadata):
73 logging.log(f"Received request: {request}")
74 return request, metadata
75
76 def post_report_error_event(self, response):
77 logging.log(f"Received response: {response}")
78 return response
79
80 transport = ReportErrorsServiceRestTransport(interceptor=MyCustomReportErrorsServiceInterceptor())
81 client = ReportErrorsServiceClient(transport=transport)
82
83
84 """
85
86 def pre_report_error_event(
87 self,
88 request: report_errors_service.ReportErrorEventRequest,
89 metadata: Sequence[Tuple[str, str]],
90 ) -> Tuple[
91 report_errors_service.ReportErrorEventRequest, Sequence[Tuple[str, str]]
92 ]:
93 """Pre-rpc interceptor for report_error_event
94
95 Override in a subclass to manipulate the request or metadata
96 before they are sent to the ReportErrorsService server.
97 """
98 return request, metadata
99
100 def post_report_error_event(
101 self, response: report_errors_service.ReportErrorEventResponse
102 ) -> report_errors_service.ReportErrorEventResponse:
103 """Post-rpc interceptor for report_error_event
104
105 Override in a subclass to manipulate the response
106 after it is returned by the ReportErrorsService server but before
107 it is returned to user code.
108 """
109 return response
110
111
112@dataclasses.dataclass
113class ReportErrorsServiceRestStub:
114 _session: AuthorizedSession
115 _host: str
116 _interceptor: ReportErrorsServiceRestInterceptor
117
118
119class ReportErrorsServiceRestTransport(ReportErrorsServiceTransport):
120 """REST backend transport for ReportErrorsService.
121
122 An API for reporting error events.
123
124 This class defines the same methods as the primary client, so the
125 primary client can load the underlying transport implementation
126 and call it.
127
128 It sends JSON representations of protocol buffers over HTTP/1.1
129
130 """
131
132 def __init__(
133 self,
134 *,
135 host: str = "clouderrorreporting.googleapis.com",
136 credentials: Optional[ga_credentials.Credentials] = None,
137 credentials_file: Optional[str] = None,
138 scopes: Optional[Sequence[str]] = None,
139 client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
140 quota_project_id: Optional[str] = None,
141 client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
142 always_use_jwt_access: Optional[bool] = False,
143 url_scheme: str = "https",
144 interceptor: Optional[ReportErrorsServiceRestInterceptor] = None,
145 api_audience: Optional[str] = None,
146 ) -> None:
147 """Instantiate the transport.
148
149 Args:
150 host (Optional[str]):
151 The hostname to connect to.
152 credentials (Optional[google.auth.credentials.Credentials]): The
153 authorization credentials to attach to requests. These
154 credentials identify the application to the service; if none
155 are specified, the client will attempt to ascertain the
156 credentials from the environment.
157
158 credentials_file (Optional[str]): A file with credentials that can
159 be loaded with :func:`google.auth.load_credentials_from_file`.
160 This argument is ignored if ``channel`` is provided.
161 scopes (Optional(Sequence[str])): A list of scopes. This argument is
162 ignored if ``channel`` is provided.
163 client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
164 certificate to configure mutual TLS HTTP channel. It is ignored
165 if ``channel`` is provided.
166 quota_project_id (Optional[str]): An optional project to use for billing
167 and quota.
168 client_info (google.api_core.gapic_v1.client_info.ClientInfo):
169 The client info used to send a user-agent string along with
170 API requests. If ``None``, then default info will be used.
171 Generally, you only need to set this if you are developing
172 your own client library.
173 always_use_jwt_access (Optional[bool]): Whether self signed JWT should
174 be used for service account credentials.
175 url_scheme: the protocol scheme for the API endpoint. Normally
176 "https", but for testing or local servers,
177 "http" can be specified.
178 """
179 # Run the base constructor
180 # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
181 # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
182 # credentials object
183 maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host)
184 if maybe_url_match is None:
185 raise ValueError(
186 f"Unexpected hostname structure: {host}"
187 ) # pragma: NO COVER
188
189 url_match_items = maybe_url_match.groupdict()
190
191 host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host
192
193 super().__init__(
194 host=host,
195 credentials=credentials,
196 client_info=client_info,
197 always_use_jwt_access=always_use_jwt_access,
198 api_audience=api_audience,
199 )
200 self._session = AuthorizedSession(
201 self._credentials, default_host=self.DEFAULT_HOST
202 )
203 if client_cert_source_for_mtls:
204 self._session.configure_mtls_channel(client_cert_source_for_mtls)
205 self._interceptor = interceptor or ReportErrorsServiceRestInterceptor()
206 self._prep_wrapped_messages(client_info)
207
208 class _ReportErrorEvent(ReportErrorsServiceRestStub):
209 def __hash__(self):
210 return hash("ReportErrorEvent")
211
212 __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {}
213
214 @classmethod
215 def _get_unset_required_fields(cls, message_dict):
216 return {
217 k: v
218 for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items()
219 if k not in message_dict
220 }
221
222 def __call__(
223 self,
224 request: report_errors_service.ReportErrorEventRequest,
225 *,
226 retry: OptionalRetry = gapic_v1.method.DEFAULT,
227 timeout: Optional[float] = None,
228 metadata: Sequence[Tuple[str, str]] = (),
229 ) -> report_errors_service.ReportErrorEventResponse:
230 r"""Call the report error event method over HTTP.
231
232 Args:
233 request (~.report_errors_service.ReportErrorEventRequest):
234 The request object. A request for reporting an individual
235 error event.
236 retry (google.api_core.retry.Retry): Designation of what errors, if any,
237 should be retried.
238 timeout (float): The timeout for this request.
239 metadata (Sequence[Tuple[str, str]]): Strings which should be
240 sent along with the request as metadata.
241
242 Returns:
243 ~.report_errors_service.ReportErrorEventResponse:
244 Response for reporting an individual
245 error event. Data may be added to this
246 message in the future.
247
248 """
249
250 http_options: List[Dict[str, str]] = [
251 {
252 "method": "post",
253 "uri": "/v1beta1/{project_name=projects/*}/events:report",
254 "body": "event",
255 },
256 ]
257 request, metadata = self._interceptor.pre_report_error_event(
258 request, metadata
259 )
260 pb_request = report_errors_service.ReportErrorEventRequest.pb(request)
261 transcoded_request = path_template.transcode(http_options, pb_request)
262
263 # Jsonify the request body
264
265 body = json_format.MessageToJson(
266 transcoded_request["body"],
267 including_default_value_fields=False,
268 use_integers_for_enums=True,
269 )
270 uri = transcoded_request["uri"]
271 method = transcoded_request["method"]
272
273 # Jsonify the query params
274 query_params = json.loads(
275 json_format.MessageToJson(
276 transcoded_request["query_params"],
277 including_default_value_fields=False,
278 use_integers_for_enums=True,
279 )
280 )
281 query_params.update(self._get_unset_required_fields(query_params))
282
283 query_params["$alt"] = "json;enum-encoding=int"
284
285 # Send the request
286 headers = dict(metadata)
287 headers["Content-Type"] = "application/json"
288 response = getattr(self._session, method)(
289 "{host}{uri}".format(host=self._host, uri=uri),
290 timeout=timeout,
291 headers=headers,
292 params=rest_helpers.flatten_query_params(query_params, strict=True),
293 data=body,
294 )
295
296 # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
297 # subclass.
298 if response.status_code >= 400:
299 raise core_exceptions.from_http_response(response)
300
301 # Return the response
302 resp = report_errors_service.ReportErrorEventResponse()
303 pb_resp = report_errors_service.ReportErrorEventResponse.pb(resp)
304
305 json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True)
306 resp = self._interceptor.post_report_error_event(resp)
307 return resp
308
309 @property
310 def report_error_event(
311 self,
312 ) -> Callable[
313 [report_errors_service.ReportErrorEventRequest],
314 report_errors_service.ReportErrorEventResponse,
315 ]:
316 # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here.
317 # In C++ this would require a dynamic_cast
318 return self._ReportErrorEvent(self._session, self._host, self._interceptor) # type: ignore
319
320 @property
321 def kind(self) -> str:
322 return "rest"
323
324 def close(self):
325 self._session.close()
326
327
328__all__ = ("ReportErrorsServiceRestTransport",)