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 # Save the scopes.
123 self._scopes = scopes
124
125 # If no credentials are provided, then determine the appropriate
126 # defaults.
127 if credentials and credentials_file:
128 raise core_exceptions.DuplicateCredentialArgs(
129 "'credentials_file' and 'credentials' are mutually exclusive"
130 )
131
132 if credentials_file is not None:
133 credentials, _ = google.auth.load_credentials_from_file(
134 credentials_file,
135 scopes=scopes,
136 quota_project_id=quota_project_id,
137 default_scopes=self.AUTH_SCOPES,
138 )
139
140 elif credentials is None:
141 credentials, _ = google.auth.default(
142 scopes=scopes,
143 quota_project_id=quota_project_id,
144 default_scopes=self.AUTH_SCOPES,
145 )
146
147 # If the credentials are service account credentials, then always try to use self signed JWT.
148 if (
149 always_use_jwt_access
150 and isinstance(credentials, service_account.Credentials)
151 and hasattr(service_account.Credentials, "with_always_use_jwt_access")
152 ):
153 credentials = credentials.with_always_use_jwt_access(True)
154
155 # Save the credentials.
156 self._credentials = credentials
157
158 def _prep_wrapped_messages(self, client_info):
159 # Precompute the wrapped methods.
160 self._wrapped_methods = {
161 self.list_operations: gapic_v1.method.wrap_method(
162 self.list_operations,
163 default_retry=retries.Retry(
164 initial=0.5,
165 maximum=10.0,
166 multiplier=2.0,
167 predicate=retries.if_exception_type(
168 core_exceptions.ServiceUnavailable,
169 ),
170 deadline=10.0,
171 ),
172 default_timeout=10.0,
173 default_compression=Compression.NoCompression,
174 client_info=client_info,
175 ),
176 self.get_operation: gapic_v1.method.wrap_method(
177 self.get_operation,
178 default_retry=retries.Retry(
179 initial=0.5,
180 maximum=10.0,
181 multiplier=2.0,
182 predicate=retries.if_exception_type(
183 core_exceptions.ServiceUnavailable,
184 ),
185 deadline=10.0,
186 ),
187 default_timeout=10.0,
188 default_compression=Compression.NoCompression,
189 client_info=client_info,
190 ),
191 self.delete_operation: gapic_v1.method.wrap_method(
192 self.delete_operation,
193 default_retry=retries.Retry(
194 initial=0.5,
195 maximum=10.0,
196 multiplier=2.0,
197 predicate=retries.if_exception_type(
198 core_exceptions.ServiceUnavailable,
199 ),
200 deadline=10.0,
201 ),
202 default_timeout=10.0,
203 default_compression=Compression.NoCompression,
204 client_info=client_info,
205 ),
206 self.cancel_operation: gapic_v1.method.wrap_method(
207 self.cancel_operation,
208 default_retry=retries.Retry(
209 initial=0.5,
210 maximum=10.0,
211 multiplier=2.0,
212 predicate=retries.if_exception_type(
213 core_exceptions.ServiceUnavailable,
214 ),
215 deadline=10.0,
216 ),
217 default_timeout=10.0,
218 default_compression=Compression.NoCompression,
219 client_info=client_info,
220 ),
221 }
222
223 def close(self):
224 """Closes resources associated with the transport.
225
226 .. warning::
227 Only call this method if the transport is NOT shared
228 with other clients - this may cause errors in other clients!
229 """
230 raise NotImplementedError()
231
232 def _convert_protobuf_message_to_dict(
233 self, message: google.protobuf.message.Message
234 ):
235 r"""Converts protobuf message to a dictionary.
236
237 When the dictionary is encoded to JSON, it conforms to proto3 JSON spec.
238
239 Args:
240 message(google.protobuf.message.Message): The protocol buffers message
241 instance to serialize.
242
243 Returns:
244 A dict representation of the protocol buffer message.
245 """
246 # TODO(https://github.com/googleapis/python-api-core/issues/643): For backwards compatibility
247 # with protobuf 3.x 4.x, Remove once support for protobuf 3.x and 4.x is dropped.
248 if PROTOBUF_VERSION[0:2] in ["3.", "4."]:
249 result = json_format.MessageToDict(
250 message,
251 preserving_proto_field_name=True,
252 including_default_value_fields=True, # type: ignore # backward compatibility
253 )
254 else:
255 result = json_format.MessageToDict(
256 message,
257 preserving_proto_field_name=True,
258 always_print_fields_with_no_presence=True,
259 )
260
261 return result
262
263 @property
264 def list_operations(
265 self,
266 ) -> Callable[
267 [operations_pb2.ListOperationsRequest],
268 Union[
269 operations_pb2.ListOperationsResponse,
270 Awaitable[operations_pb2.ListOperationsResponse],
271 ],
272 ]:
273 raise NotImplementedError()
274
275 @property
276 def get_operation(
277 self,
278 ) -> Callable[
279 [operations_pb2.GetOperationRequest],
280 Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
281 ]:
282 raise NotImplementedError()
283
284 @property
285 def delete_operation(
286 self,
287 ) -> Callable[
288 [operations_pb2.DeleteOperationRequest],
289 Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
290 ]:
291 raise NotImplementedError()
292
293 @property
294 def cancel_operation(
295 self,
296 ) -> Callable[
297 [operations_pb2.CancelOperationRequest],
298 Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
299 ]:
300 raise NotImplementedError()
301
302
303__all__ = ("OperationsTransport",)