Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/google/cloud/client/__init__.py: 62%

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

100 statements  

1# Copyright 2015 Google LLC 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15"""Base classes for client used to interact with Google Cloud APIs.""" 

16 

17import io 

18import json 

19import os 

20from pickle import PicklingError 

21from typing import Tuple 

22from typing import Union 

23 

24import google.api_core.client_options 

25import google.api_core.exceptions 

26import google.auth 

27from google.auth import environment_vars 

28import google.auth.credentials 

29import google.auth.transport.requests 

30from google.cloud._helpers import _determine_default_project 

31from google.oauth2 import service_account 

32 

33try: 

34 import google.auth.api_key 

35 

36 HAS_GOOGLE_AUTH_API_KEY = True 

37except ImportError: 

38 HAS_GOOGLE_AUTH_API_KEY = False 

39 

40 

41_GOOGLE_AUTH_CREDENTIALS_HELP = ( 

42 "This library only supports credentials from google-auth-library-python. " 

43 "See https://google-auth.readthedocs.io/en/latest/ " 

44 "for help on authentication with this library." 

45) 

46 

47# Default timeout for auth requests. 

48_CREDENTIALS_REFRESH_TIMEOUT = 300 

49 

50 

51class _ClientFactoryMixin(object): 

52 """Mixin to allow factories that create credentials. 

53 

54 .. note:: 

55 

56 This class is virtual. 

57 """ 

58 

59 _SET_PROJECT = False 

60 

61 @classmethod 

62 def from_service_account_info(cls, info, *args, **kwargs): 

63 """Factory to retrieve JSON credentials while creating client. 

64 

65 :type info: dict 

66 :param info: 

67 The JSON object with a private key and other credentials 

68 information (downloaded from the Google APIs console). 

69 

70 :type args: tuple 

71 :param args: Remaining positional arguments to pass to constructor. 

72 

73 :param kwargs: Remaining keyword arguments to pass to constructor. 

74 

75 :rtype: :class:`_ClientFactoryMixin` 

76 :returns: The client created with the retrieved JSON credentials. 

77 :raises TypeError: if there is a conflict with the kwargs 

78 and the credentials created by the factory. 

79 """ 

80 if "credentials" in kwargs: 

81 raise TypeError("credentials must not be in keyword arguments") 

82 

83 credentials = service_account.Credentials.from_service_account_info(info) 

84 if cls._SET_PROJECT: 

85 if "project" not in kwargs: 

86 kwargs["project"] = info.get("project_id") 

87 

88 kwargs["credentials"] = credentials 

89 return cls(*args, **kwargs) 

90 

91 @classmethod 

92 def from_service_account_json(cls, json_credentials_path, *args, **kwargs): 

93 """Factory to retrieve JSON credentials while creating client. 

94 

95 :type json_credentials_path: str 

96 :param json_credentials_path: The path to a private key file (this file 

97 was given to you when you created the 

98 service account). This file must contain 

99 a JSON object with a private key and 

100 other credentials information (downloaded 

101 from the Google APIs console). 

102 

103 :type args: tuple 

104 :param args: Remaining positional arguments to pass to constructor. 

105 

106 :param kwargs: Remaining keyword arguments to pass to constructor. 

107 

108 :rtype: :class:`_ClientFactoryMixin` 

109 :returns: The client created with the retrieved JSON credentials. 

110 :raises TypeError: if there is a conflict with the kwargs 

111 and the credentials created by the factory. 

112 """ 

113 with io.open(json_credentials_path, "r", encoding="utf-8") as json_fi: 

114 credentials_info = json.load(json_fi) 

115 

116 return cls.from_service_account_info(credentials_info, *args, **kwargs) 

117 

118 

119class Client(_ClientFactoryMixin): 

120 """Client to bundle configuration needed for API requests. 

121 

122 Stores ``credentials`` and an HTTP object so that subclasses 

123 can pass them along to a connection class. 

124 

125 If no value is passed in for ``_http``, a :class:`requests.Session` object 

126 will be created and authorized with the ``credentials``. If not, the 

127 ``credentials`` and ``_http`` need not be related. 

128 

129 Callers and subclasses may seek to use the private key from 

130 ``credentials`` to sign data. 

131 

132 Args: 

133 credentials (google.auth.credentials.Credentials): 

134 (Optional) The OAuth2 Credentials to use for this client. If not 

135 passed (and if no ``_http`` object is passed), falls back to the 

136 default inferred from the environment. 

137 client_options (google.api_core.client_options.ClientOptions): 

138 (Optional) Custom options for the client. 

139 _http (requests.Session): 

140 (Optional) HTTP object to make requests. Can be any object that 

141 defines ``request()`` with the same interface as 

142 :meth:`requests.Session.request`. If not passed, an ``_http`` 

143 object is created that is bound to the ``credentials`` for the 

144 current object. 

145 This parameter should be considered private, and could change in 

146 the future. 

147 

148 Raises: 

149 google.auth.exceptions.DefaultCredentialsError: 

150 Raised if ``credentials`` is not specified and the library fails 

151 to acquire default credentials. 

152 """ 

153 

154 SCOPE: Union[Tuple[str, ...], None] = None 

155 """The scopes required for authenticating with a service. 

156 

157 Needs to be set by subclasses. 

158 """ 

159 

160 def __init__(self, credentials=None, _http=None, client_options=None): 

161 if isinstance(client_options, dict): 

162 client_options = google.api_core.client_options.from_dict(client_options) 

163 if client_options is None: 

164 client_options = google.api_core.client_options.ClientOptions() 

165 

166 if credentials and client_options.credentials_file: 

167 raise google.api_core.exceptions.DuplicateCredentialArgs( 

168 "'credentials' and 'client_options.credentials_file' are mutually exclusive." 

169 ) 

170 

171 if ( 

172 HAS_GOOGLE_AUTH_API_KEY 

173 and client_options.api_key 

174 and (credentials or client_options.credentials_file) 

175 ): 

176 raise google.api_core.exceptions.DuplicateCredentialArgs( 

177 "'client_options.api_key' is mutually exclusive with 'credentials' and 'client_options.credentials_file'." 

178 ) 

179 

180 if credentials and not isinstance( 

181 credentials, google.auth.credentials.Credentials 

182 ): 

183 raise ValueError(_GOOGLE_AUTH_CREDENTIALS_HELP) 

184 

185 scopes = client_options.scopes or self.SCOPE 

186 

187 # if no http is provided, credentials must exist 

188 if not _http and credentials is None: 

189 if client_options.credentials_file: 

190 credentials, _ = google.auth.load_credentials_from_file( 

191 client_options.credentials_file, scopes=scopes 

192 ) 

193 elif HAS_GOOGLE_AUTH_API_KEY and client_options.api_key is not None: 

194 credentials = google.auth.api_key.Credentials(client_options.api_key) 

195 else: 

196 credentials, _ = google.auth.default(scopes=scopes) 

197 

198 self._credentials = google.auth.credentials.with_scopes_if_required( 

199 credentials, scopes=scopes 

200 ) 

201 

202 if client_options.quota_project_id: 

203 self._credentials = self._credentials.with_quota_project( 

204 client_options.quota_project_id 

205 ) 

206 

207 self._http_internal = _http 

208 self._client_cert_source = client_options.client_cert_source 

209 

210 def __getstate__(self): 

211 """Explicitly state that clients are not pickleable.""" 

212 raise PicklingError( 

213 "\n".join( 

214 [ 

215 "Pickling client objects is explicitly not supported.", 

216 "Clients have non-trivial state that is local and unpickleable.", 

217 ] 

218 ) 

219 ) 

220 

221 @property 

222 def _http(self): 

223 """Getter for object used for HTTP transport. 

224 

225 :rtype: :class:`~requests.Session` 

226 :returns: An HTTP object. 

227 """ 

228 if self._http_internal is None: 

229 self._http_internal = google.auth.transport.requests.AuthorizedSession( 

230 self._credentials, 

231 refresh_timeout=_CREDENTIALS_REFRESH_TIMEOUT, 

232 ) 

233 self._http_internal.configure_mtls_channel(self._client_cert_source) 

234 return self._http_internal 

235 

236 def close(self): 

237 """Clean up transport, if set. 

238 

239 Suggested use: 

240 

241 .. code-block:: python 

242 

243 import contextlib 

244 

245 with contextlib.closing(client): # closes on exit 

246 do_something_with(client) 

247 """ 

248 if self._http_internal is not None: 

249 self._http_internal.close() 

250 

251 

252class _ClientProjectMixin(object): 

253 """Mixin to allow setting the project on the client. 

254 

255 :type project: str 

256 :param project: 

257 (Optional) the project which the client acts on behalf of. If not 

258 passed, falls back to the default inferred from the environment. 

259 

260 :type credentials: :class:`google.auth.credentials.Credentials` 

261 :param credentials: 

262 (Optional) credentials used to discover a project, if not passed. 

263 

264 :raises: :class:`EnvironmentError` if the project is neither passed in nor 

265 set on the credentials or in the environment. :class:`ValueError` 

266 if the project value is invalid. 

267 """ 

268 

269 def __init__(self, project=None, credentials=None): 

270 # This test duplicates the one from `google.auth.default`, but earlier, 

271 # for backward compatibility: we want the environment variable to 

272 # override any project set on the credentials. See: 

273 # https://github.com/googleapis/python-cloud-core/issues/27 

274 if project is None: 

275 project = os.getenv( 

276 environment_vars.PROJECT, 

277 os.getenv(environment_vars.LEGACY_PROJECT), 

278 ) 

279 

280 # Project set on explicit credentials overrides discovery from 

281 # SDK / GAE / GCE. 

282 if project is None and credentials is not None: 

283 project = getattr(credentials, "project_id", None) 

284 

285 if project is None: 

286 project = self._determine_default(project) 

287 

288 if project is None: 

289 raise EnvironmentError( 

290 "Project was not passed and could not be " 

291 "determined from the environment." 

292 ) 

293 

294 if isinstance(project, bytes): 

295 project = project.decode("utf-8") 

296 

297 if not isinstance(project, str): 

298 raise ValueError("Project must be a string.") 

299 

300 self.project = project 

301 

302 @staticmethod 

303 def _determine_default(project): 

304 """Helper: use default project detection.""" 

305 return _determine_default_project(project) 

306 

307 

308class ClientWithProject(Client, _ClientProjectMixin): 

309 """Client that also stores a project. 

310 

311 :type project: str 

312 :param project: the project which the client acts on behalf of. If not 

313 passed falls back to the default inferred from the 

314 environment. 

315 

316 :type credentials: :class:`~google.auth.credentials.Credentials` 

317 :param credentials: (Optional) The OAuth2 Credentials to use for this 

318 client. If not passed (and if no ``_http`` object is 

319 passed), falls back to the default inferred from the 

320 environment. 

321 

322 :type _http: :class:`~requests.Session` 

323 :param _http: (Optional) HTTP object to make requests. Can be any object 

324 that defines ``request()`` with the same interface as 

325 :meth:`~requests.Session.request`. If not passed, an 

326 ``_http`` object is created that is bound to the 

327 ``credentials`` for the current object. 

328 This parameter should be considered private, and could 

329 change in the future. 

330 

331 :raises: :class:`ValueError` if the project is neither passed in nor 

332 set in the environment. 

333 """ 

334 

335 _SET_PROJECT = True # Used by from_service_account_json() 

336 

337 def __init__(self, project=None, credentials=None, client_options=None, _http=None): 

338 _ClientProjectMixin.__init__(self, project=project, credentials=credentials) 

339 Client.__init__( 

340 self, credentials=credentials, client_options=client_options, _http=_http 

341 )