Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/google/api_core/operations_v1/abstract_operations_base_client.py: 48%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

125 statements  

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 )