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",)