1# -*- coding: utf-8 -*-
2# Copyright 2020 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 typing import Callable, Dict, Optional, Sequence, Tuple, Union
18
19from requests import __version__ as requests_version
20
21from google.api_core import exceptions as core_exceptions # type: ignore
22from google.api_core import gapic_v1 # type: ignore
23from google.api_core import path_template # type: ignore
24from google.api_core import rest_helpers # type: ignore
25from google.api_core import retry as retries # type: ignore
26from google.auth import credentials as ga_credentials # type: ignore
27from google.auth.transport.requests import AuthorizedSession # type: ignore
28from google.longrunning import operations_pb2 # type: ignore
29from google.protobuf import empty_pb2 # type: ignore
30from google.protobuf import json_format # type: ignore
31import google.protobuf
32
33import grpc
34from .base import DEFAULT_CLIENT_INFO as BASE_DEFAULT_CLIENT_INFO, OperationsTransport
35
36PROTOBUF_VERSION = google.protobuf.__version__
37
38OptionalRetry = Union[retries.Retry, object]
39
40DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
41 gapic_version=BASE_DEFAULT_CLIENT_INFO.gapic_version,
42 grpc_version=None,
43 rest_version=f"requests@{requests_version}",
44)
45
46
47class OperationsRestTransport(OperationsTransport):
48 """REST backend transport for Operations.
49
50 Manages long-running operations with an API service.
51
52 When an API method normally takes long time to complete, it can be
53 designed to return [Operation][google.api_core.operations_v1.Operation] to the
54 client, and the client can use this interface to receive the real
55 response asynchronously by polling the operation resource, or pass
56 the operation resource to another API (such as Google Cloud Pub/Sub
57 API) to receive the response. Any API service that returns
58 long-running operations should implement the ``Operations``
59 interface so developers can have a consistent client experience.
60
61 This class defines the same methods as the primary client, so the
62 primary client can load the underlying transport implementation
63 and call it.
64
65 It sends JSON representations of protocol buffers over HTTP/1.1
66 """
67
68 def __init__(
69 self,
70 *,
71 host: str = "longrunning.googleapis.com",
72 credentials: Optional[ga_credentials.Credentials] = None,
73 credentials_file: Optional[str] = None,
74 scopes: Optional[Sequence[str]] = None,
75 client_cert_source_for_mtls: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
76 quota_project_id: Optional[str] = None,
77 client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
78 always_use_jwt_access: Optional[bool] = False,
79 url_scheme: str = "https",
80 http_options: Optional[Dict] = None,
81 path_prefix: str = "v1",
82 ) -> None:
83 """Instantiate the transport.
84
85 Args:
86 host (Optional[str]):
87 The hostname to connect to.
88 credentials (Optional[google.auth.credentials.Credentials]): The
89 authorization credentials to attach to requests. These
90 credentials identify the application to the service; if none
91 are specified, the client will attempt to ascertain the
92 credentials from the environment.
93
94 credentials_file (Optional[str]): A file with credentials that can
95 be loaded with :func:`google.auth.load_credentials_from_file`.
96 This argument is ignored if ``channel`` is provided.
97
98 .. warning::
99 Important: If you accept a credential configuration (credential JSON/File/Stream)
100 from an external source for authentication to Google Cloud Platform, you must
101 validate it before providing it to any Google API or client library. Providing an
102 unvalidated credential configuration to Google APIs or libraries can compromise
103 the security of your systems and data. For more information, refer to
104 `Validate credential configurations from external sources`_.
105
106 .. _Validate credential configurations from external sources:
107
108 https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
109 scopes (Optional(Sequence[str])): A list of scopes. This argument is
110 ignored if ``channel`` is provided.
111 client_cert_source_for_mtls (Callable[[], Tuple[bytes, bytes]]): Client
112 certificate to configure mutual TLS HTTP channel. It is ignored
113 if ``channel`` is provided.
114 quota_project_id (Optional[str]): An optional project to use for billing
115 and quota.
116 client_info (google.api_core.gapic_v1.client_info.ClientInfo):
117 The client info used to send a user-agent string along with
118 API requests. If ``None``, then default info will be used.
119 Generally, you only need to set this if you're developing
120 your own client library.
121 always_use_jwt_access (Optional[bool]): Whether self signed JWT should
122 be used for service account credentials.
123 url_scheme: the protocol scheme for the API endpoint. Normally
124 "https", but for testing or local servers,
125 "http" can be specified.
126 http_options: a dictionary of http_options for transcoding, to override
127 the defaults from operations.proto. Each method has an entry
128 with the corresponding http rules as value.
129 path_prefix: path prefix (usually represents API version). Set to
130 "v1" by default.
131
132 """
133 # Run the base constructor
134 # TODO(yon-mg): resolve other ctor params i.e. scopes, quota, etc.
135 # TODO: When custom host (api_endpoint) is set, `scopes` must *also* be set on the
136 # credentials object
137 super().__init__(
138 host=host,
139 credentials=credentials,
140 client_info=client_info,
141 always_use_jwt_access=always_use_jwt_access,
142 )
143 self._session = AuthorizedSession(
144 self._credentials, default_host=self.DEFAULT_HOST
145 )
146 if client_cert_source_for_mtls:
147 self._session.configure_mtls_channel(client_cert_source_for_mtls)
148 # TODO(https://github.com/googleapis/python-api-core/issues/720): Add wrap logic directly to the property methods for callables.
149 self._prep_wrapped_messages(client_info)
150 self._http_options = http_options or {}
151 self._path_prefix = path_prefix
152
153 def _list_operations(
154 self,
155 request: operations_pb2.ListOperationsRequest,
156 *,
157 # TODO(https://github.com/googleapis/python-api-core/issues/723): Leverage `retry`
158 # to allow configuring retryable error codes.
159 retry: OptionalRetry = gapic_v1.method.DEFAULT,
160 timeout: Optional[float] = None,
161 compression: Optional[grpc.Compression] = gapic_v1.method.DEFAULT,
162 metadata: Sequence[Tuple[str, str]] = (),
163 ) -> operations_pb2.ListOperationsResponse:
164 r"""Call the list operations method over HTTP.
165
166 Args:
167 request (~.operations_pb2.ListOperationsRequest):
168 The request object. The request message for
169 [Operations.ListOperations][google.api_core.operations_v1.Operations.ListOperations].
170
171 retry (google.api_core.retry.Retry): Designation of what errors, if any,
172 should be retried.
173 timeout (float): The timeout for this request.
174 metadata (Sequence[Tuple[str, str]]): Strings which should be
175 sent along with the request as metadata.
176
177 Returns:
178 ~.operations_pb2.ListOperationsResponse:
179 The response message for
180 [Operations.ListOperations][google.api_core.operations_v1.Operations.ListOperations].
181
182 """
183
184 http_options = [
185 {
186 "method": "get",
187 "uri": "/{}/{{name=**}}/operations".format(self._path_prefix),
188 },
189 ]
190 if "google.longrunning.Operations.ListOperations" in self._http_options:
191 http_options = self._http_options[
192 "google.longrunning.Operations.ListOperations"
193 ]
194
195 request_kwargs = self._convert_protobuf_message_to_dict(request)
196 transcoded_request = path_template.transcode(http_options, **request_kwargs)
197
198 uri = transcoded_request["uri"]
199 method = transcoded_request["method"]
200
201 # Jsonify the query params
202 query_params_request = operations_pb2.ListOperationsRequest()
203 json_format.ParseDict(transcoded_request["query_params"], query_params_request)
204 query_params = json_format.MessageToDict(
205 query_params_request,
206 preserving_proto_field_name=False,
207 use_integers_for_enums=False,
208 )
209
210 # Send the request
211 headers = dict(metadata)
212 headers["Content-Type"] = "application/json"
213 # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
214 response = getattr(self._session, method)(
215 "{host}{uri}".format(host=self._host, uri=uri),
216 timeout=timeout,
217 headers=headers,
218 params=rest_helpers.flatten_query_params(query_params),
219 )
220
221 # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
222 # subclass.
223 if response.status_code >= 400:
224 raise core_exceptions.from_http_response(response)
225
226 # Return the response
227 api_response = operations_pb2.ListOperationsResponse()
228 json_format.Parse(response.content, api_response, ignore_unknown_fields=False)
229 return api_response
230
231 def _get_operation(
232 self,
233 request: operations_pb2.GetOperationRequest,
234 *,
235 # TODO(https://github.com/googleapis/python-api-core/issues/723): Leverage `retry`
236 # to allow configuring retryable error codes.
237 retry: OptionalRetry = gapic_v1.method.DEFAULT,
238 timeout: Optional[float] = None,
239 compression: Optional[grpc.Compression] = gapic_v1.method.DEFAULT,
240 metadata: Sequence[Tuple[str, str]] = (),
241 ) -> operations_pb2.Operation:
242 r"""Call the get operation method over HTTP.
243
244 Args:
245 request (~.operations_pb2.GetOperationRequest):
246 The request object. The request message for
247 [Operations.GetOperation][google.api_core.operations_v1.Operations.GetOperation].
248
249 retry (google.api_core.retry.Retry): Designation of what errors, if any,
250 should be retried.
251 timeout (float): The timeout for this request.
252 metadata (Sequence[Tuple[str, str]]): Strings which should be
253 sent along with the request as metadata.
254
255 Returns:
256 ~.operations_pb2.Operation:
257 This resource represents a long-
258 running operation that is the result of a
259 network API call.
260
261 """
262
263 http_options = [
264 {
265 "method": "get",
266 "uri": "/{}/{{name=**/operations/*}}".format(self._path_prefix),
267 },
268 ]
269 if "google.longrunning.Operations.GetOperation" in self._http_options:
270 http_options = self._http_options[
271 "google.longrunning.Operations.GetOperation"
272 ]
273
274 request_kwargs = self._convert_protobuf_message_to_dict(request)
275 transcoded_request = path_template.transcode(http_options, **request_kwargs)
276
277 uri = transcoded_request["uri"]
278 method = transcoded_request["method"]
279
280 # Jsonify the query params
281 query_params_request = operations_pb2.GetOperationRequest()
282 json_format.ParseDict(transcoded_request["query_params"], query_params_request)
283 query_params = json_format.MessageToDict(
284 query_params_request,
285 preserving_proto_field_name=False,
286 use_integers_for_enums=False,
287 )
288
289 # Send the request
290 headers = dict(metadata)
291 headers["Content-Type"] = "application/json"
292 # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
293 response = getattr(self._session, method)(
294 "{host}{uri}".format(host=self._host, uri=uri),
295 timeout=timeout,
296 headers=headers,
297 params=rest_helpers.flatten_query_params(query_params),
298 )
299
300 # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
301 # subclass.
302 if response.status_code >= 400:
303 raise core_exceptions.from_http_response(response)
304
305 # Return the response
306 api_response = operations_pb2.Operation()
307 json_format.Parse(response.content, api_response, ignore_unknown_fields=False)
308 return api_response
309
310 def _delete_operation(
311 self,
312 request: operations_pb2.DeleteOperationRequest,
313 *,
314 # TODO(https://github.com/googleapis/python-api-core/issues/723): Leverage `retry`
315 # to allow configuring retryable error codes.
316 retry: OptionalRetry = gapic_v1.method.DEFAULT,
317 timeout: Optional[float] = None,
318 compression: Optional[grpc.Compression] = gapic_v1.method.DEFAULT,
319 metadata: Sequence[Tuple[str, str]] = (),
320 ) -> empty_pb2.Empty:
321 r"""Call the delete operation method over HTTP.
322
323 Args:
324 request (~.operations_pb2.DeleteOperationRequest):
325 The request object. The request message for
326 [Operations.DeleteOperation][google.api_core.operations_v1.Operations.DeleteOperation].
327
328 retry (google.api_core.retry.Retry): Designation of what errors, if any,
329 should be retried.
330 timeout (float): The timeout for this request.
331 metadata (Sequence[Tuple[str, str]]): Strings which should be
332 sent along with the request as metadata.
333 """
334
335 http_options = [
336 {
337 "method": "delete",
338 "uri": "/{}/{{name=**/operations/*}}".format(self._path_prefix),
339 },
340 ]
341 if "google.longrunning.Operations.DeleteOperation" in self._http_options:
342 http_options = self._http_options[
343 "google.longrunning.Operations.DeleteOperation"
344 ]
345
346 request_kwargs = self._convert_protobuf_message_to_dict(request)
347 transcoded_request = path_template.transcode(http_options, **request_kwargs)
348
349 uri = transcoded_request["uri"]
350 method = transcoded_request["method"]
351
352 # Jsonify the query params
353 query_params_request = operations_pb2.DeleteOperationRequest()
354 json_format.ParseDict(transcoded_request["query_params"], query_params_request)
355 query_params = json_format.MessageToDict(
356 query_params_request,
357 preserving_proto_field_name=False,
358 use_integers_for_enums=False,
359 )
360
361 # Send the request
362 headers = dict(metadata)
363 headers["Content-Type"] = "application/json"
364 # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
365 response = getattr(self._session, method)(
366 "{host}{uri}".format(host=self._host, uri=uri),
367 timeout=timeout,
368 headers=headers,
369 params=rest_helpers.flatten_query_params(query_params),
370 )
371
372 # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
373 # subclass.
374 if response.status_code >= 400:
375 raise core_exceptions.from_http_response(response)
376
377 return empty_pb2.Empty()
378
379 def _cancel_operation(
380 self,
381 request: operations_pb2.CancelOperationRequest,
382 *,
383 # TODO(https://github.com/googleapis/python-api-core/issues/723): Leverage `retry`
384 # to allow configuring retryable error codes.
385 retry: OptionalRetry = gapic_v1.method.DEFAULT,
386 timeout: Optional[float] = None,
387 compression: Optional[grpc.Compression] = gapic_v1.method.DEFAULT,
388 metadata: Sequence[Tuple[str, str]] = (),
389 ) -> empty_pb2.Empty:
390 r"""Call the cancel operation method over HTTP.
391
392 Args:
393 request (~.operations_pb2.CancelOperationRequest):
394 The request object. The request message for
395 [Operations.CancelOperation][google.api_core.operations_v1.Operations.CancelOperation].
396
397 retry (google.api_core.retry.Retry): Designation of what errors, if any,
398 should be retried.
399 timeout (float): The timeout for this request.
400 metadata (Sequence[Tuple[str, str]]): Strings which should be
401 sent along with the request as metadata.
402 """
403
404 http_options = [
405 {
406 "method": "post",
407 "uri": "/{}/{{name=**/operations/*}}:cancel".format(self._path_prefix),
408 "body": "*",
409 },
410 ]
411 if "google.longrunning.Operations.CancelOperation" in self._http_options:
412 http_options = self._http_options[
413 "google.longrunning.Operations.CancelOperation"
414 ]
415
416 request_kwargs = self._convert_protobuf_message_to_dict(request)
417 transcoded_request = path_template.transcode(http_options, **request_kwargs)
418
419 # Jsonify the request body
420 body_request = operations_pb2.CancelOperationRequest()
421 json_format.ParseDict(transcoded_request["body"], body_request)
422 body = json_format.MessageToDict(
423 body_request,
424 preserving_proto_field_name=False,
425 use_integers_for_enums=False,
426 )
427 uri = transcoded_request["uri"]
428 method = transcoded_request["method"]
429
430 # Jsonify the query params
431 query_params_request = operations_pb2.CancelOperationRequest()
432 json_format.ParseDict(transcoded_request["query_params"], query_params_request)
433 query_params = json_format.MessageToDict(
434 query_params_request,
435 preserving_proto_field_name=False,
436 use_integers_for_enums=False,
437 )
438
439 # Send the request
440 headers = dict(metadata)
441 headers["Content-Type"] = "application/json"
442 # TODO(https://github.com/googleapis/python-api-core/issues/721): Update incorrect use of `uri`` variable name.
443 response = getattr(self._session, method)(
444 "{host}{uri}".format(host=self._host, uri=uri),
445 timeout=timeout,
446 headers=headers,
447 params=rest_helpers.flatten_query_params(query_params),
448 data=body,
449 )
450
451 # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception
452 # subclass.
453 if response.status_code >= 400:
454 raise core_exceptions.from_http_response(response)
455
456 return empty_pb2.Empty()
457
458 @property
459 def list_operations(
460 self,
461 ) -> Callable[
462 [operations_pb2.ListOperationsRequest], operations_pb2.ListOperationsResponse
463 ]:
464 return self._list_operations
465
466 @property
467 def get_operation(
468 self,
469 ) -> Callable[[operations_pb2.GetOperationRequest], operations_pb2.Operation]:
470 return self._get_operation
471
472 @property
473 def delete_operation(
474 self,
475 ) -> Callable[[operations_pb2.DeleteOperationRequest], empty_pb2.Empty]:
476 return self._delete_operation
477
478 @property
479 def cancel_operation(
480 self,
481 ) -> Callable[[operations_pb2.CancelOperationRequest], empty_pb2.Empty]:
482 return self._cancel_operation
483
484
485__all__ = ("OperationsRestTransport",)