1# -*- coding: utf-8 -*- 
    2# Copyright 2024 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 
    17import os 
    18import re 
    19from typing import Dict, Optional, Type, Union 
    20 
    21from google.api_core import client_options as client_options_lib  # type: ignore 
    22from google.api_core import gapic_v1  # type: ignore 
    23from google.api_core.operations_v1.transports.base import ( 
    24    DEFAULT_CLIENT_INFO, 
    25    OperationsTransport, 
    26) 
    27from google.api_core.operations_v1.transports.rest import OperationsRestTransport 
    28 
    29try: 
    30    from google.api_core.operations_v1.transports.rest_asyncio import ( 
    31        AsyncOperationsRestTransport, 
    32    ) 
    33 
    34    HAS_ASYNC_REST_DEPENDENCIES = True 
    35except ImportError as e: 
    36    HAS_ASYNC_REST_DEPENDENCIES = False 
    37    ASYNC_REST_EXCEPTION = e 
    38 
    39from google.auth import credentials as ga_credentials  # type: ignore 
    40from google.auth.exceptions import MutualTLSChannelError  # type: ignore 
    41from google.auth.transport import mtls  # type: ignore 
    42 
    43 
    44class AbstractOperationsBaseClientMeta(type): 
    45    """Metaclass for the Operations Base client. 
    46 
    47    This provides base class-level methods for building and retrieving 
    48    support objects (e.g. transport) without polluting the client instance 
    49    objects. 
    50    """ 
    51 
    52    _transport_registry = OrderedDict()  # type: Dict[str, Type[OperationsTransport]] 
    53    _transport_registry["rest"] = OperationsRestTransport 
    54    if HAS_ASYNC_REST_DEPENDENCIES: 
    55        _transport_registry["rest_asyncio"] = AsyncOperationsRestTransport 
    56 
    57    def get_transport_class( 
    58        cls, 
    59        label: Optional[str] = None, 
    60    ) -> Type[OperationsTransport]: 
    61        """Returns an appropriate transport class. 
    62 
    63        Args: 
    64            label: The name of the desired transport. If none is 
    65                provided, then the first transport in the registry is used. 
    66 
    67        Returns: 
    68            The transport class to use. 
    69        """ 
    70        # If a specific transport is requested, return that one. 
    71        if ( 
    72            label == "rest_asyncio" and not HAS_ASYNC_REST_DEPENDENCIES 
    73        ):  # pragma: NO COVER 
    74            raise ASYNC_REST_EXCEPTION 
    75 
    76        if label: 
    77            return cls._transport_registry[label] 
    78 
    79        # No transport is requested; return the default (that is, the first one 
    80        # in the dictionary). 
    81        return next(iter(cls._transport_registry.values())) 
    82 
    83 
    84class AbstractOperationsBaseClient(metaclass=AbstractOperationsBaseClientMeta): 
    85    """Manages long-running operations with an API service. 
    86 
    87    When an API method normally takes long time to complete, it can be 
    88    designed to return [Operation][google.api_core.operations_v1.Operation] to the 
    89    client, and the client can use this interface to receive the real 
    90    response asynchronously by polling the operation resource, or pass 
    91    the operation resource to another API (such as Google Cloud Pub/Sub 
    92    API) to receive the response. Any API service that returns 
    93    long-running operations should implement the ``Operations`` 
    94    interface so developers can have a consistent client experience. 
    95    """ 
    96 
    97    @staticmethod 
    98    def _get_default_mtls_endpoint(api_endpoint): 
    99        """Converts api endpoint to mTLS endpoint. 
    100 
    101        Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to 
    102        "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively. 
    103        Args: 
    104            api_endpoint (Optional[str]): the api endpoint to convert. 
    105        Returns: 
    106            str: converted mTLS api endpoint. 
    107        """ 
    108        if not api_endpoint: 
    109            return api_endpoint 
    110 
    111        mtls_endpoint_re = re.compile( 
    112            r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?" 
    113        ) 
    114 
    115        m = mtls_endpoint_re.match(api_endpoint) 
    116        name, mtls, sandbox, googledomain = m.groups() 
    117        if mtls or not googledomain: 
    118            return api_endpoint 
    119 
    120        if sandbox: 
    121            return api_endpoint.replace( 
    122                "sandbox.googleapis.com", "mtls.sandbox.googleapis.com" 
    123            ) 
    124 
    125        return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com") 
    126 
    127    DEFAULT_ENDPOINT = "longrunning.googleapis.com" 
    128    DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__(  # type: ignore 
    129        DEFAULT_ENDPOINT 
    130    ) 
    131 
    132    @classmethod 
    133    def from_service_account_info(cls, info: dict, *args, **kwargs): 
    134        """ 
    135        This class method should be overridden by the subclasses. 
    136 
    137        Args: 
    138            info (dict): The service account private key info. 
    139            args: Additional arguments to pass to the constructor. 
    140            kwargs: Additional arguments to pass to the constructor. 
    141 
    142        Raises: 
    143            NotImplementedError: If the method is called on the base class. 
    144        """ 
    145        raise NotImplementedError("`from_service_account_info` is not implemented.") 
    146 
    147    @classmethod 
    148    def from_service_account_file(cls, filename: str, *args, **kwargs): 
    149        """ 
    150        This class method should be overridden by the subclasses. 
    151 
    152        Args: 
    153            filename (str): The path to the service account private key json 
    154                file. 
    155            args: Additional arguments to pass to the constructor. 
    156            kwargs: Additional arguments to pass to the constructor. 
    157 
    158        Raises: 
    159            NotImplementedError: If the method is called on the base class. 
    160        """ 
    161        raise NotImplementedError("`from_service_account_file` is not implemented.") 
    162 
    163    from_service_account_json = from_service_account_file 
    164 
    165    @property 
    166    def transport(self) -> OperationsTransport: 
    167        """Returns the transport used by the client instance. 
    168 
    169        Returns: 
    170            OperationsTransport: The transport used by the client 
    171                instance. 
    172        """ 
    173        return self._transport 
    174 
    175    @staticmethod 
    176    def common_billing_account_path( 
    177        billing_account: str, 
    178    ) -> str: 
    179        """Returns a fully-qualified billing_account string.""" 
    180        return "billingAccounts/{billing_account}".format( 
    181            billing_account=billing_account, 
    182        ) 
    183 
    184    @staticmethod 
    185    def parse_common_billing_account_path(path: str) -> Dict[str, str]: 
    186        """Parse a billing_account path into its component segments.""" 
    187        m = re.match(r"^billingAccounts/(?P<billing_account>.+?)$", path) 
    188        return m.groupdict() if m else {} 
    189 
    190    @staticmethod 
    191    def common_folder_path( 
    192        folder: str, 
    193    ) -> str: 
    194        """Returns a fully-qualified folder string.""" 
    195        return "folders/{folder}".format( 
    196            folder=folder, 
    197        ) 
    198 
    199    @staticmethod 
    200    def parse_common_folder_path(path: str) -> Dict[str, str]: 
    201        """Parse a folder path into its component segments.""" 
    202        m = re.match(r"^folders/(?P<folder>.+?)$", path) 
    203        return m.groupdict() if m else {} 
    204 
    205    @staticmethod 
    206    def common_organization_path( 
    207        organization: str, 
    208    ) -> str: 
    209        """Returns a fully-qualified organization string.""" 
    210        return "organizations/{organization}".format( 
    211            organization=organization, 
    212        ) 
    213 
    214    @staticmethod 
    215    def parse_common_organization_path(path: str) -> Dict[str, str]: 
    216        """Parse a organization path into its component segments.""" 
    217        m = re.match(r"^organizations/(?P<organization>.+?)$", path) 
    218        return m.groupdict() if m else {} 
    219 
    220    @staticmethod 
    221    def common_project_path( 
    222        project: str, 
    223    ) -> str: 
    224        """Returns a fully-qualified project string.""" 
    225        return "projects/{project}".format( 
    226            project=project, 
    227        ) 
    228 
    229    @staticmethod 
    230    def parse_common_project_path(path: str) -> Dict[str, str]: 
    231        """Parse a project path into its component segments.""" 
    232        m = re.match(r"^projects/(?P<project>.+?)$", path) 
    233        return m.groupdict() if m else {} 
    234 
    235    @staticmethod 
    236    def common_location_path( 
    237        project: str, 
    238        location: str, 
    239    ) -> str: 
    240        """Returns a fully-qualified location string.""" 
    241        return "projects/{project}/locations/{location}".format( 
    242            project=project, 
    243            location=location, 
    244        ) 
    245 
    246    @staticmethod 
    247    def parse_common_location_path(path: str) -> Dict[str, str]: 
    248        """Parse a location path into its component segments.""" 
    249        m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path) 
    250        return m.groupdict() if m else {} 
    251 
    252    def __init__( 
    253        self, 
    254        *, 
    255        credentials: Optional[ga_credentials.Credentials] = None, 
    256        transport: Union[str, OperationsTransport, None] = None, 
    257        client_options: Optional[client_options_lib.ClientOptions] = None, 
    258        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, 
    259    ) -> None: 
    260        """Instantiates the operations client. 
    261 
    262        Args: 
    263            credentials (Optional[google.auth.credentials.Credentials]): The 
    264                authorization credentials to attach to requests. These 
    265                credentials identify the application to the service; if none 
    266                are specified, the client will attempt to ascertain the 
    267                credentials from the environment. 
    268            transport (Union[str, OperationsTransport]): The 
    269                transport to use. If set to None, a transport is chosen 
    270                automatically. 
    271            client_options (google.api_core.client_options.ClientOptions): Custom options for the 
    272                client. It won't take effect if a ``transport`` instance is provided. 
    273                (1) The ``api_endpoint`` property can be used to override the 
    274                default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT 
    275                environment variable can also be used to override the endpoint: 
    276                "always" (always use the default mTLS endpoint), "never" (always 
    277                use the default regular endpoint) and "auto" (auto switch to the 
    278                default mTLS endpoint if client certificate is present, this is 
    279                the default value). However, the ``api_endpoint`` property takes 
    280                precedence if provided. 
    281                (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable 
    282                is "true", then the ``client_cert_source`` property can be used 
    283                to provide client certificate for mutual TLS transport. If 
    284                not provided, the default SSL client certificate will be used if 
    285                present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not 
    286                set, no client certificate will be used. 
    287            client_info (google.api_core.gapic_v1.client_info.ClientInfo): 
    288                The client info used to send a user-agent string along with 
    289                API requests. If ``None``, then default info will be used. 
    290                Generally, you only need to set this if you're developing 
    291                your own client library. 
    292 
    293        Raises: 
    294            google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport 
    295                creation failed for any reason. 
    296        """ 
    297        if isinstance(client_options, dict): 
    298            client_options = client_options_lib.from_dict(client_options) 
    299        if client_options is None: 
    300            client_options = client_options_lib.ClientOptions() 
    301 
    302        # Create SSL credentials for mutual TLS if needed. 
    303        use_client_cert = os.getenv( 
    304            "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" 
    305        ).lower() 
    306        if use_client_cert not in ("true", "false"): 
    307            raise ValueError( 
    308                "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" 
    309            ) 
    310        client_cert_source_func = None 
    311        is_mtls = False 
    312        if use_client_cert == "true": 
    313            if client_options.client_cert_source: 
    314                is_mtls = True 
    315                client_cert_source_func = client_options.client_cert_source 
    316            else: 
    317                is_mtls = mtls.has_default_client_cert_source() 
    318                if is_mtls: 
    319                    client_cert_source_func = mtls.default_client_cert_source() 
    320                else: 
    321                    client_cert_source_func = None 
    322 
    323        # Figure out which api endpoint to use. 
    324        if client_options.api_endpoint is not None: 
    325            api_endpoint = client_options.api_endpoint 
    326        else: 
    327            use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") 
    328            if use_mtls_env == "never": 
    329                api_endpoint = self.DEFAULT_ENDPOINT 
    330            elif use_mtls_env == "always": 
    331                api_endpoint = self.DEFAULT_MTLS_ENDPOINT 
    332            elif use_mtls_env == "auto": 
    333                if is_mtls: 
    334                    api_endpoint = self.DEFAULT_MTLS_ENDPOINT 
    335                else: 
    336                    api_endpoint = self.DEFAULT_ENDPOINT 
    337            else: 
    338                raise MutualTLSChannelError( 
    339                    "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted " 
    340                    "values: never, auto, always" 
    341                ) 
    342 
    343        # Save or instantiate the transport. 
    344        # Ordinarily, we provide the transport, but allowing a custom transport 
    345        # instance provides an extensibility point for unusual situations. 
    346        if isinstance(transport, OperationsTransport): 
    347            # transport is a OperationsTransport instance. 
    348            if credentials or client_options.credentials_file: 
    349                raise ValueError( 
    350                    "When providing a transport instance, " 
    351                    "provide its credentials directly." 
    352                ) 
    353            if client_options.scopes: 
    354                raise ValueError( 
    355                    "When providing a transport instance, provide its scopes " 
    356                    "directly." 
    357                ) 
    358            self._transport = transport 
    359        else: 
    360            Transport = type(self).get_transport_class(transport) 
    361            self._transport = Transport( 
    362                credentials=credentials, 
    363                credentials_file=client_options.credentials_file, 
    364                host=api_endpoint, 
    365                scopes=client_options.scopes, 
    366                client_cert_source_for_mtls=client_cert_source_func, 
    367                quota_project_id=client_options.quota_project_id, 
    368                client_info=client_info, 
    369                always_use_jwt_access=True, 
    370            )