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# 16from collections import OrderedDict 17from distutils import util 18import os 19import re 20from typing import Dict, Optional, Sequence, Tuple, Type, Union 21 22from google.api_core import client_options as client_options_lib # type: ignore 23from google.api_core import gapic_v1 # type: ignore 24from google.api_core import retry as retries # type: ignore 25from google.api_core.operations_v1 import pagers 26from google.api_core.operations_v1.transports.base import ( 27 DEFAULT_CLIENT_INFO, 28 OperationsTransport, 29) 30from google.api_core.operations_v1.transports.rest import OperationsRestTransport 31from google.auth import credentials as ga_credentials # type: ignore 32from google.auth.exceptions import MutualTLSChannelError # type: ignore 33from google.auth.transport import mtls # type: ignore 34from google.longrunning import operations_pb2 35from google.oauth2 import service_account # type: ignore 36 37OptionalRetry = Union[retries.Retry, object] 38 39 40class AbstractOperationsClientMeta(type): 41 """Metaclass for the Operations client. 42 43 This provides class-level methods for building and retrieving 44 support objects (e.g. transport) without polluting the client instance 45 objects. 46 """ 47 48 _transport_registry = OrderedDict() # type: Dict[str, Type[OperationsTransport]] 49 _transport_registry["rest"] = OperationsRestTransport 50 51 def get_transport_class( 52 cls, label: Optional[str] = None, 53 ) -> Type[OperationsTransport]: 54 """Returns an appropriate transport class. 55 56 Args: 57 label: The name of the desired transport. If none is 58 provided, then the first transport in the registry is used. 59 60 Returns: 61 The transport class to use. 62 """ 63 # If a specific transport is requested, return that one. 64 if label: 65 return cls._transport_registry[label] 66 67 # No transport is requested; return the default (that is, the first one 68 # in the dictionary). 69 return next(iter(cls._transport_registry.values())) 70 71 72class AbstractOperationsClient(metaclass=AbstractOperationsClientMeta): 73 """Manages long-running operations with an API service. 74 75 When an API method normally takes long time to complete, it can be 76 designed to return [Operation][google.api_core.operations_v1.Operation] to the 77 client, and the client can use this interface to receive the real 78 response asynchronously by polling the operation resource, or pass 79 the operation resource to another API (such as Google Cloud Pub/Sub 80 API) to receive the response. Any API service that returns 81 long-running operations should implement the ``Operations`` 82 interface so developers can have a consistent client experience. 83 """ 84 85 @staticmethod 86 def _get_default_mtls_endpoint(api_endpoint): 87 """Converts api endpoint to mTLS endpoint. 88 89 Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to 90 "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively. 91 Args: 92 api_endpoint (Optional[str]): the api endpoint to convert. 93 Returns: 94 str: converted mTLS api endpoint. 95 """ 96 if not api_endpoint: 97 return api_endpoint 98 99 mtls_endpoint_re = re.compile( 100 r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?" 101 ) 102 103 m = mtls_endpoint_re.match(api_endpoint) 104 name, mtls, sandbox, googledomain = m.groups() 105 if mtls or not googledomain: 106 return api_endpoint 107 108 if sandbox: 109 return api_endpoint.replace( 110 "sandbox.googleapis.com", "mtls.sandbox.googleapis.com" 111 ) 112 113 return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com") 114 115 DEFAULT_ENDPOINT = "longrunning.googleapis.com" 116 DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__( # type: ignore 117 DEFAULT_ENDPOINT 118 ) 119 120 @classmethod 121 def from_service_account_info(cls, info: dict, *args, **kwargs): 122 """Creates an instance of this client using the provided credentials 123 info. 124 125 Args: 126 info (dict): The service account private key info. 127 args: Additional arguments to pass to the constructor. 128 kwargs: Additional arguments to pass to the constructor. 129 130 Returns: 131 AbstractOperationsClient: The constructed client. 132 """ 133 credentials = service_account.Credentials.from_service_account_info(info) 134 kwargs["credentials"] = credentials 135 return cls(*args, **kwargs) 136 137 @classmethod 138 def from_service_account_file(cls, filename: str, *args, **kwargs): 139 """Creates an instance of this client using the provided credentials 140 file. 141 142 Args: 143 filename (str): The path to the service account private key json 144 file. 145 args: Additional arguments to pass to the constructor. 146 kwargs: Additional arguments to pass to the constructor. 147 148 Returns: 149 AbstractOperationsClient: The constructed client. 150 """ 151 credentials = service_account.Credentials.from_service_account_file(filename) 152 kwargs["credentials"] = credentials 153 return cls(*args, **kwargs) 154 155 from_service_account_json = from_service_account_file 156 157 @property 158 def transport(self) -> OperationsTransport: 159 """Returns the transport used by the client instance. 160 161 Returns: 162 OperationsTransport: The transport used by the client 163 instance. 164 """ 165 return self._transport 166 167 @staticmethod 168 def common_billing_account_path(billing_account: str,) -> str: 169 """Returns a fully-qualified billing_account string.""" 170 return "billingAccounts/{billing_account}".format( 171 billing_account=billing_account, 172 ) 173 174 @staticmethod 175 def parse_common_billing_account_path(path: str) -> Dict[str, str]: 176 """Parse a billing_account path into its component segments.""" 177 m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path) 178 return m.groupdict() if m else {} 179 180 @staticmethod 181 def common_folder_path(folder: str,) -> str: 182 """Returns a fully-qualified folder string.""" 183 return "folders/{folder}".format(folder=folder,) 184 185 @staticmethod 186 def parse_common_folder_path(path: str) -> Dict[str, str]: 187 """Parse a folder path into its component segments.""" 188 m = re.match(r"^folders/(?P<folder>.+?)$", path) 189 return m.groupdict() if m else {} 190 191 @staticmethod 192 def common_organization_path(organization: str,) -> str: 193 """Returns a fully-qualified organization string.""" 194 return "organizations/{organization}".format(organization=organization,) 195 196 @staticmethod 197 def parse_common_organization_path(path: str) -> Dict[str, str]: 198 """Parse a organization path into its component segments.""" 199 m = re.match(r"^organizations/(?P<organization>.+?)$", path) 200 return m.groupdict() if m else {} 201 202 @staticmethod 203 def common_project_path(project: str,) -> str: 204 """Returns a fully-qualified project string.""" 205 return "projects/{project}".format(project=project,) 206 207 @staticmethod 208 def parse_common_project_path(path: str) -> Dict[str, str]: 209 """Parse a project path into its component segments.""" 210 m = re.match(r"^projects/(?P<project>.+?)$", path) 211 return m.groupdict() if m else {} 212 213 @staticmethod 214 def common_location_path(project: str, location: str,) -> str: 215 """Returns a fully-qualified location string.""" 216 return "projects/{project}/locations/{location}".format( 217 project=project, location=location, 218 ) 219 220 @staticmethod 221 def parse_common_location_path(path: str) -> Dict[str, str]: 222 """Parse a location path into its component segments.""" 223 m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path) 224 return m.groupdict() if m else {} 225 226 def __init__( 227 self, 228 *, 229 credentials: Optional[ga_credentials.Credentials] = None, 230 transport: Union[str, OperationsTransport, None] = None, 231 client_options: Optional[client_options_lib.ClientOptions] = None, 232 client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, 233 ) -> None: 234 """Instantiates the operations client. 235 236 Args: 237 credentials (Optional[google.auth.credentials.Credentials]): The 238 authorization credentials to attach to requests. These 239 credentials identify the application to the service; if none 240 are specified, the client will attempt to ascertain the 241 credentials from the environment. 242 transport (Union[str, OperationsTransport]): The 243 transport to use. If set to None, a transport is chosen 244 automatically. 245 client_options (google.api_core.client_options.ClientOptions): Custom options for the 246 client. It won't take effect if a ``transport`` instance is provided. 247 (1) The ``api_endpoint`` property can be used to override the 248 default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT 249 environment variable can also be used to override the endpoint: 250 "always" (always use the default mTLS endpoint), "never" (always 251 use the default regular endpoint) and "auto" (auto switch to the 252 default mTLS endpoint if client certificate is present, this is 253 the default value). However, the ``api_endpoint`` property takes 254 precedence if provided. 255 (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable 256 is "true", then the ``client_cert_source`` property can be used 257 to provide client certificate for mutual TLS transport. If 258 not provided, the default SSL client certificate will be used if 259 present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not 260 set, no client certificate will be used. 261 client_info (google.api_core.gapic_v1.client_info.ClientInfo): 262 The client info used to send a user-agent string along with 263 API requests. If ``None``, then default info will be used. 264 Generally, you only need to set this if you're developing 265 your own client library. 266 267 Raises: 268 google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport 269 creation failed for any reason. 270 """ 271 if isinstance(client_options, dict): 272 client_options = client_options_lib.from_dict(client_options) 273 if client_options is None: 274 client_options = client_options_lib.ClientOptions() 275 276 # Create SSL credentials for mutual TLS if needed. 277 use_client_cert = bool( 278 util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) 279 ) 280 281 client_cert_source_func = None 282 is_mtls = False 283 if use_client_cert: 284 if client_options.client_cert_source: 285 is_mtls = True 286 client_cert_source_func = client_options.client_cert_source 287 else: 288 is_mtls = mtls.has_default_client_cert_source() 289 if is_mtls: 290 client_cert_source_func = mtls.default_client_cert_source() 291 else: 292 client_cert_source_func = None 293 294 # Figure out which api endpoint to use. 295 if client_options.api_endpoint is not None: 296 api_endpoint = client_options.api_endpoint 297 else: 298 use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") 299 if use_mtls_env == "never": 300 api_endpoint = self.DEFAULT_ENDPOINT 301 elif use_mtls_env == "always": 302 api_endpoint = self.DEFAULT_MTLS_ENDPOINT 303 elif use_mtls_env == "auto": 304 if is_mtls: 305 api_endpoint = self.DEFAULT_MTLS_ENDPOINT 306 else: 307 api_endpoint = self.DEFAULT_ENDPOINT 308 else: 309 raise MutualTLSChannelError( 310 "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted " 311 "values: never, auto, always" 312 ) 313 314 # Save or instantiate the transport. 315 # Ordinarily, we provide the transport, but allowing a custom transport 316 # instance provides an extensibility point for unusual situations. 317 if isinstance(transport, OperationsTransport): 318 # transport is a OperationsTransport instance. 319 if credentials or client_options.credentials_file: 320 raise ValueError( 321 "When providing a transport instance, " 322 "provide its credentials directly." 323 ) 324 if client_options.scopes: 325 raise ValueError( 326 "When providing a transport instance, provide its scopes " 327 "directly." 328 ) 329 self._transport = transport 330 else: 331 Transport = type(self).get_transport_class(transport) 332 self._transport = Transport( 333 credentials=credentials, 334 credentials_file=client_options.credentials_file, 335 host=api_endpoint, 336 scopes=client_options.scopes, 337 client_cert_source_for_mtls=client_cert_source_func, 338 quota_project_id=client_options.quota_project_id, 339 client_info=client_info, 340 always_use_jwt_access=True, 341 ) 342 343 def list_operations( 344 self, 345 name: str, 346 filter_: Optional[str] = None, 347 *, 348 page_size: Optional[int] = None, 349 page_token: Optional[str] = None, 350 retry: OptionalRetry = gapic_v1.method.DEFAULT, 351 timeout: Optional[float] = None, 352 metadata: Sequence[Tuple[str, str]] = (), 353 ) -> pagers.ListOperationsPager: 354 r"""Lists operations that match the specified filter in the request. 355 If the server doesn't support this method, it returns 356 ``UNIMPLEMENTED``. 357 358 NOTE: the ``name`` binding allows API services to override the 359 binding to use different resource name schemes, such as 360 ``users/*/operations``. To override the binding, API services 361 can add a binding such as ``"/v1/{name=users/*}/operations"`` to 362 their service configuration. For backwards compatibility, the 363 default name includes the operations collection id, however 364 overriding users must ensure the name binding is the parent 365 resource, without the operations collection id. 366 367 Args: 368 name (str): 369 The name of the operation's parent 370 resource. 371 filter_ (str): 372 The standard list filter. 373 This corresponds to the ``filter`` field 374 on the ``request`` instance; if ``request`` is provided, this 375 should not be set. 376 retry (google.api_core.retry.Retry): Designation of what errors, if any, 377 should be retried. 378 timeout (float): The timeout for this request. 379 metadata (Sequence[Tuple[str, str]]): Strings which should be 380 sent along with the request as metadata. 381 382 Returns: 383 google.api_core.operations_v1.pagers.ListOperationsPager: 384 The response message for 385 [Operations.ListOperations][google.api_core.operations_v1.Operations.ListOperations]. 386 387 Iterating over this object will yield results and 388 resolve additional pages automatically. 389 390 """ 391 # Create a protobuf request object. 392 request = operations_pb2.ListOperationsRequest(name=name, filter=filter_) 393 if page_size is not None: 394 request.page_size = page_size 395 if page_token is not None: 396 request.page_token = page_token 397 398 # Wrap the RPC method; this adds retry and timeout information, 399 # and friendly error handling. 400 rpc = self._transport._wrapped_methods[self._transport.list_operations] 401 402 # Certain fields should be provided within the metadata header; 403 # add these here. 404 metadata = tuple(metadata or ()) + ( 405 gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), 406 ) 407 408 # Send the request. 409 response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) 410 411 # This method is paged; wrap the response in a pager, which provides 412 # an `__iter__` convenience method. 413 response = pagers.ListOperationsPager( 414 method=rpc, request=request, response=response, metadata=metadata, 415 ) 416 417 # Done; return the response. 418 return response 419 420 def get_operation( 421 self, 422 name: str, 423 *, 424 retry: OptionalRetry = gapic_v1.method.DEFAULT, 425 timeout: Optional[float] = None, 426 metadata: Sequence[Tuple[str, str]] = (), 427 ) -> operations_pb2.Operation: 428 r"""Gets the latest state of a long-running operation. 429 Clients can use this method to poll the operation result 430 at intervals as recommended by the API service. 431 432 Args: 433 name (str): 434 The name of the operation resource. 435 retry (google.api_core.retry.Retry): Designation of what errors, if any, 436 should be retried. 437 timeout (float): The timeout for this request. 438 metadata (Sequence[Tuple[str, str]]): Strings which should be 439 sent along with the request as metadata. 440 441 Returns: 442 google.longrunning.operations_pb2.Operation: 443 This resource represents a long- 444 unning operation that is the result of a 445 network API call. 446 447 """ 448 449 request = operations_pb2.GetOperationRequest(name=name) 450 451 # Wrap the RPC method; this adds retry and timeout information, 452 # and friendly error handling. 453 rpc = self._transport._wrapped_methods[self._transport.get_operation] 454 455 # Certain fields should be provided within the metadata header; 456 # add these here. 457 metadata = tuple(metadata or ()) + ( 458 gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), 459 ) 460 461 # Send the request. 462 response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) 463 464 # Done; return the response. 465 return response 466 467 def delete_operation( 468 self, 469 name: str, 470 *, 471 retry: OptionalRetry = gapic_v1.method.DEFAULT, 472 timeout: Optional[float] = None, 473 metadata: Sequence[Tuple[str, str]] = (), 474 ) -> None: 475 r"""Deletes a long-running operation. This method indicates that the 476 client is no longer interested in the operation result. It does 477 not cancel the operation. If the server doesn't support this 478 method, it returns ``google.rpc.Code.UNIMPLEMENTED``. 479 480 Args: 481 name (str): 482 The name of the operation resource to 483 be deleted. 484 485 This corresponds to the ``name`` field 486 on the ``request`` instance; if ``request`` is provided, this 487 should not be set. 488 retry (google.api_core.retry.Retry): Designation of what errors, if any, 489 should be retried. 490 timeout (float): The timeout for this request. 491 metadata (Sequence[Tuple[str, str]]): Strings which should be 492 sent along with the request as metadata. 493 """ 494 # Create the request object. 495 request = operations_pb2.DeleteOperationRequest(name=name) 496 497 # Wrap the RPC method; this adds retry and timeout information, 498 # and friendly error handling. 499 rpc = self._transport._wrapped_methods[self._transport.delete_operation] 500 501 # Certain fields should be provided within the metadata header; 502 # add these here. 503 metadata = tuple(metadata or ()) + ( 504 gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), 505 ) 506 507 # Send the request. 508 rpc( 509 request, retry=retry, timeout=timeout, metadata=metadata, 510 ) 511 512 def cancel_operation( 513 self, 514 name: Optional[str] = None, 515 *, 516 retry: OptionalRetry = gapic_v1.method.DEFAULT, 517 timeout: Optional[float] = None, 518 metadata: Sequence[Tuple[str, str]] = (), 519 ) -> None: 520 r"""Starts asynchronous cancellation on a long-running operation. 521 The server makes a best effort to cancel the operation, but 522 success is not guaranteed. If the server doesn't support this 523 method, it returns ``google.rpc.Code.UNIMPLEMENTED``. Clients 524 can use 525 [Operations.GetOperation][google.api_core.operations_v1.Operations.GetOperation] 526 or other methods to check whether the cancellation succeeded or 527 whether the operation completed despite cancellation. On 528 successful cancellation, the operation is not deleted; instead, 529 it becomes an operation with an 530 [Operation.error][google.api_core.operations_v1.Operation.error] value with 531 a [google.rpc.Status.code][google.rpc.Status.code] of 1, 532 corresponding to ``Code.CANCELLED``. 533 534 Args: 535 name (str): 536 The name of the operation resource to 537 be cancelled. 538 539 This corresponds to the ``name`` field 540 on the ``request`` instance; if ``request`` is provided, this 541 should not be set. 542 retry (google.api_core.retry.Retry): Designation of what errors, if any, 543 should be retried. 544 timeout (float): The timeout for this request. 545 metadata (Sequence[Tuple[str, str]]): Strings which should be 546 sent along with the request as metadata. 547 """ 548 # Create the request object. 549 request = operations_pb2.CancelOperationRequest(name=name) 550 551 # Wrap the RPC method; this adds retry and timeout information, 552 # and friendly error handling. 553 rpc = self._transport._wrapped_methods[self._transport.cancel_operation] 554 555 # Certain fields should be provided within the metadata header; 556 # add these here. 557 metadata = tuple(metadata or ()) + ( 558 gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), 559 ) 560 561 # Send the request. 562 rpc( 563 request, retry=retry, timeout=timeout, metadata=metadata, 564 ) 565