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# 
    16import abc 
    17import re 
    18from typing import Awaitable, Callable, Optional, Sequence, Union 
    19import warnings 
    20 
    21import google.auth  # type: ignore 
    22from google.auth import credentials as ga_credentials  # type: ignore 
    23from google.longrunning import operations_pb2 
    24from google.oauth2 import service_account  # type: ignore 
    25import google.protobuf 
    26from google.protobuf import empty_pb2, json_format  # type: ignore 
    27from grpc import Compression 
    28 
    29import google.api_core  # type: ignore 
    30from google.api_core import exceptions as core_exceptions  # type: ignore 
    31from google.api_core import gapic_v1  # type: ignore 
    32from google.api_core import general_helpers 
    33from google.api_core import retry as retries  # type: ignore 
    34from google.api_core import version 
    35 
    36PROTOBUF_VERSION = google.protobuf.__version__ 
    37 
    38DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( 
    39    gapic_version=version.__version__, 
    40) 
    41 
    42 
    43class OperationsTransport(abc.ABC): 
    44    """Abstract transport class for Operations.""" 
    45 
    46    AUTH_SCOPES = () 
    47 
    48    DEFAULT_HOST: str = "longrunning.googleapis.com" 
    49 
    50    def __init__( 
    51        self, 
    52        *, 
    53        host: str = DEFAULT_HOST, 
    54        # TODO(https://github.com/googleapis/python-api-core/issues/709): update type hint for credentials to include `google.auth.aio.Credentials`. 
    55        credentials: Optional[ga_credentials.Credentials] = None, 
    56        credentials_file: Optional[str] = None, 
    57        scopes: Optional[Sequence[str]] = None, 
    58        quota_project_id: Optional[str] = None, 
    59        client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, 
    60        always_use_jwt_access: Optional[bool] = False, 
    61        url_scheme="https", 
    62        **kwargs, 
    63    ) -> None: 
    64        """Instantiate the transport. 
    65 
    66        Args: 
    67            host (Optional[str]): 
    68                 The hostname to connect to. 
    69            credentials (Optional[google.auth.credentials.Credentials]): The 
    70                authorization credentials to attach to requests. These 
    71                credentials identify the application to the service; if none 
    72                are specified, the client will attempt to ascertain the 
    73                credentials from the environment. 
    74            credentials_file (Optional[str]): Deprecated. A file with credentials that can 
    75                be loaded with :func:`google.auth.load_credentials_from_file`. 
    76                This argument is mutually exclusive with credentials. This argument will be 
    77                removed in the next major version of `google-api-core`. 
    78 
    79                .. warning:: 
    80                    Important: If you accept a credential configuration (credential JSON/File/Stream) 
    81                    from an external source for authentication to Google Cloud Platform, you must 
    82                    validate it before providing it to any Google API or client library. Providing an 
    83                    unvalidated credential configuration to Google APIs or libraries can compromise 
    84                    the security of your systems and data. For more information, refer to 
    85                    `Validate credential configurations from external sources`_. 
    86 
    87                .. _Validate credential configurations from external sources: 
    88 
    89                https://cloud.google.com/docs/authentication/external/externally-sourced-credentials 
    90            scopes (Optional[Sequence[str]]): A list of scopes. 
    91            quota_project_id (Optional[str]): An optional project to use for billing 
    92                and quota. 
    93            client_info (google.api_core.gapic_v1.client_info.ClientInfo): 
    94                The client info used to send a user-agent string along with 
    95                API requests. If ``None``, then default info will be used. 
    96                Generally, you only need to set this if you're developing 
    97                your own client library. 
    98            always_use_jwt_access (Optional[bool]): Whether self signed JWT should 
    99                be used for service account credentials. 
    100            url_scheme: the protocol scheme for the API endpoint.  Normally 
    101                "https", but for testing or local servers, 
    102                "http" can be specified. 
    103        """ 
    104        if credentials_file is not None: 
    105            warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning) 
    106 
    107        maybe_url_match = re.match("^(?P<scheme>http(?:s)?://)?(?P<host>.*)$", host) 
    108        if maybe_url_match is None: 
    109            raise ValueError( 
    110                f"Unexpected hostname structure: {host}" 
    111            )  # pragma: NO COVER 
    112 
    113        url_match_items = maybe_url_match.groupdict() 
    114 
    115        host = f"{url_scheme}://{host}" if not url_match_items["scheme"] else host 
    116 
    117        # Save the hostname. Default to port 443 (HTTPS) if none is specified. 
    118        if ":" not in host: 
    119            host += ":443"  # pragma: NO COVER 
    120        self._host = host 
    121 
    122        scopes_kwargs = {"scopes": scopes, "default_scopes": self.AUTH_SCOPES} 
    123 
    124        # Save the scopes. 
    125        self._scopes = scopes 
    126 
    127        # If no credentials are provided, then determine the appropriate 
    128        # defaults. 
    129        if credentials and credentials_file: 
    130            raise core_exceptions.DuplicateCredentialArgs( 
    131                "'credentials_file' and 'credentials' are mutually exclusive" 
    132            ) 
    133 
    134        if credentials_file is not None: 
    135            credentials, _ = google.auth.load_credentials_from_file( 
    136                credentials_file, **scopes_kwargs, quota_project_id=quota_project_id 
    137            ) 
    138 
    139        elif credentials is None: 
    140            credentials, _ = google.auth.default( 
    141                **scopes_kwargs, quota_project_id=quota_project_id 
    142            ) 
    143 
    144        # If the credentials are service account credentials, then always try to use self signed JWT. 
    145        if ( 
    146            always_use_jwt_access 
    147            and isinstance(credentials, service_account.Credentials) 
    148            and hasattr(service_account.Credentials, "with_always_use_jwt_access") 
    149        ): 
    150            credentials = credentials.with_always_use_jwt_access(True) 
    151 
    152        # Save the credentials. 
    153        self._credentials = credentials 
    154 
    155    def _prep_wrapped_messages(self, client_info): 
    156        # Precompute the wrapped methods. 
    157        self._wrapped_methods = { 
    158            self.list_operations: gapic_v1.method.wrap_method( 
    159                self.list_operations, 
    160                default_retry=retries.Retry( 
    161                    initial=0.5, 
    162                    maximum=10.0, 
    163                    multiplier=2.0, 
    164                    predicate=retries.if_exception_type( 
    165                        core_exceptions.ServiceUnavailable, 
    166                    ), 
    167                    deadline=10.0, 
    168                ), 
    169                default_timeout=10.0, 
    170                default_compression=Compression.NoCompression, 
    171                client_info=client_info, 
    172            ), 
    173            self.get_operation: gapic_v1.method.wrap_method( 
    174                self.get_operation, 
    175                default_retry=retries.Retry( 
    176                    initial=0.5, 
    177                    maximum=10.0, 
    178                    multiplier=2.0, 
    179                    predicate=retries.if_exception_type( 
    180                        core_exceptions.ServiceUnavailable, 
    181                    ), 
    182                    deadline=10.0, 
    183                ), 
    184                default_timeout=10.0, 
    185                default_compression=Compression.NoCompression, 
    186                client_info=client_info, 
    187            ), 
    188            self.delete_operation: gapic_v1.method.wrap_method( 
    189                self.delete_operation, 
    190                default_retry=retries.Retry( 
    191                    initial=0.5, 
    192                    maximum=10.0, 
    193                    multiplier=2.0, 
    194                    predicate=retries.if_exception_type( 
    195                        core_exceptions.ServiceUnavailable, 
    196                    ), 
    197                    deadline=10.0, 
    198                ), 
    199                default_timeout=10.0, 
    200                default_compression=Compression.NoCompression, 
    201                client_info=client_info, 
    202            ), 
    203            self.cancel_operation: gapic_v1.method.wrap_method( 
    204                self.cancel_operation, 
    205                default_retry=retries.Retry( 
    206                    initial=0.5, 
    207                    maximum=10.0, 
    208                    multiplier=2.0, 
    209                    predicate=retries.if_exception_type( 
    210                        core_exceptions.ServiceUnavailable, 
    211                    ), 
    212                    deadline=10.0, 
    213                ), 
    214                default_timeout=10.0, 
    215                default_compression=Compression.NoCompression, 
    216                client_info=client_info, 
    217            ), 
    218        } 
    219 
    220    def close(self): 
    221        """Closes resources associated with the transport. 
    222 
    223        .. warning:: 
    224             Only call this method if the transport is NOT shared 
    225             with other clients - this may cause errors in other clients! 
    226        """ 
    227        raise NotImplementedError() 
    228 
    229    def _convert_protobuf_message_to_dict( 
    230        self, message: google.protobuf.message.Message 
    231    ): 
    232        r"""Converts protobuf message to a dictionary. 
    233 
    234        When the dictionary is encoded to JSON, it conforms to proto3 JSON spec. 
    235 
    236        Args: 
    237            message(google.protobuf.message.Message): The protocol buffers message 
    238                instance to serialize. 
    239 
    240        Returns: 
    241            A dict representation of the protocol buffer message. 
    242        """ 
    243        # TODO(https://github.com/googleapis/python-api-core/issues/643): For backwards compatibility 
    244        # with protobuf 3.x 4.x, Remove once support for protobuf 3.x and 4.x is dropped. 
    245        if PROTOBUF_VERSION[0:2] in ["3.", "4."]: 
    246            result = json_format.MessageToDict( 
    247                message, 
    248                preserving_proto_field_name=True, 
    249                including_default_value_fields=True,  # type: ignore # backward compatibility 
    250            ) 
    251        else: 
    252            result = json_format.MessageToDict( 
    253                message, 
    254                preserving_proto_field_name=True, 
    255                always_print_fields_with_no_presence=True, 
    256            ) 
    257 
    258        return result 
    259 
    260    @property 
    261    def list_operations( 
    262        self, 
    263    ) -> Callable[ 
    264        [operations_pb2.ListOperationsRequest], 
    265        Union[ 
    266            operations_pb2.ListOperationsResponse, 
    267            Awaitable[operations_pb2.ListOperationsResponse], 
    268        ], 
    269    ]: 
    270        raise NotImplementedError() 
    271 
    272    @property 
    273    def get_operation( 
    274        self, 
    275    ) -> Callable[ 
    276        [operations_pb2.GetOperationRequest], 
    277        Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]], 
    278    ]: 
    279        raise NotImplementedError() 
    280 
    281    @property 
    282    def delete_operation( 
    283        self, 
    284    ) -> Callable[ 
    285        [operations_pb2.DeleteOperationRequest], 
    286        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]], 
    287    ]: 
    288        raise NotImplementedError() 
    289 
    290    @property 
    291    def cancel_operation( 
    292        self, 
    293    ) -> Callable[ 
    294        [operations_pb2.CancelOperationRequest], 
    295        Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]], 
    296    ]: 
    297        raise NotImplementedError() 
    298 
    299 
    300__all__ = ("OperationsTransport",)