Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/google/cloud/client/__init__.py: 42%
89 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:45 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:45 +0000
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.
15"""Base classes for client used to interact with Google Cloud APIs."""
17import io
18import json
19import os
20from pickle import PicklingError
21from typing import Tuple
22from typing import Union
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
34_GOOGLE_AUTH_CREDENTIALS_HELP = (
35 "This library only supports credentials from google-auth-library-python. "
36 "See https://google-auth.readthedocs.io/en/latest/ "
37 "for help on authentication with this library."
38)
40# Default timeout for auth requests.
41_CREDENTIALS_REFRESH_TIMEOUT = 300
44class _ClientFactoryMixin(object):
45 """Mixin to allow factories that create credentials.
47 .. note::
49 This class is virtual.
50 """
52 _SET_PROJECT = False
54 @classmethod
55 def from_service_account_info(cls, info, *args, **kwargs):
56 """Factory to retrieve JSON credentials while creating client.
58 :type info: dict
59 :param info:
60 The JSON object with a private key and other credentials
61 information (downloaded from the Google APIs console).
63 :type args: tuple
64 :param args: Remaining positional arguments to pass to constructor.
66 :param kwargs: Remaining keyword arguments to pass to constructor.
68 :rtype: :class:`_ClientFactoryMixin`
69 :returns: The client created with the retrieved JSON credentials.
70 :raises TypeError: if there is a conflict with the kwargs
71 and the credentials created by the factory.
72 """
73 if "credentials" in kwargs:
74 raise TypeError("credentials must not be in keyword arguments")
76 credentials = service_account.Credentials.from_service_account_info(info)
77 if cls._SET_PROJECT:
78 if "project" not in kwargs:
79 kwargs["project"] = info.get("project_id")
81 kwargs["credentials"] = credentials
82 return cls(*args, **kwargs)
84 @classmethod
85 def from_service_account_json(cls, json_credentials_path, *args, **kwargs):
86 """Factory to retrieve JSON credentials while creating client.
88 :type json_credentials_path: str
89 :param json_credentials_path: The path to a private key file (this file
90 was given to you when you created the
91 service account). This file must contain
92 a JSON object with a private key and
93 other credentials information (downloaded
94 from the Google APIs console).
96 :type args: tuple
97 :param args: Remaining positional arguments to pass to constructor.
99 :param kwargs: Remaining keyword arguments to pass to constructor.
101 :rtype: :class:`_ClientFactoryMixin`
102 :returns: The client created with the retrieved JSON credentials.
103 :raises TypeError: if there is a conflict with the kwargs
104 and the credentials created by the factory.
105 """
106 with io.open(json_credentials_path, "r", encoding="utf-8") as json_fi:
107 credentials_info = json.load(json_fi)
109 return cls.from_service_account_info(credentials_info, *args, **kwargs)
112class Client(_ClientFactoryMixin):
113 """Client to bundle configuration needed for API requests.
115 Stores ``credentials`` and an HTTP object so that subclasses
116 can pass them along to a connection class.
118 If no value is passed in for ``_http``, a :class:`requests.Session` object
119 will be created and authorized with the ``credentials``. If not, the
120 ``credentials`` and ``_http`` need not be related.
122 Callers and subclasses may seek to use the private key from
123 ``credentials`` to sign data.
125 Args:
126 credentials (google.auth.credentials.Credentials):
127 (Optional) The OAuth2 Credentials to use for this client. If not
128 passed (and if no ``_http`` object is passed), falls back to the
129 default inferred from the environment.
130 client_options (google.api_core.client_options.ClientOptions):
131 (Optional) Custom options for the client.
132 _http (requests.Session):
133 (Optional) HTTP object to make requests. Can be any object that
134 defines ``request()`` with the same interface as
135 :meth:`requests.Session.request`. If not passed, an ``_http``
136 object is created that is bound to the ``credentials`` for the
137 current object.
138 This parameter should be considered private, and could change in
139 the future.
141 Raises:
142 google.auth.exceptions.DefaultCredentialsError:
143 Raised if ``credentials`` is not specified and the library fails
144 to acquire default credentials.
145 """
147 SCOPE: Union[Tuple[str, ...], None] = None
148 """The scopes required for authenticating with a service.
150 Needs to be set by subclasses.
151 """
153 def __init__(self, credentials=None, _http=None, client_options=None):
154 if isinstance(client_options, dict):
155 client_options = google.api_core.client_options.from_dict(client_options)
156 if client_options is None:
157 client_options = google.api_core.client_options.ClientOptions()
159 if credentials and client_options.credentials_file:
160 raise google.api_core.exceptions.DuplicateCredentialArgs(
161 "'credentials' and 'client_options.credentials_file' are mutually exclusive."
162 )
164 if credentials and not isinstance(
165 credentials, google.auth.credentials.Credentials
166 ):
167 raise ValueError(_GOOGLE_AUTH_CREDENTIALS_HELP)
169 scopes = client_options.scopes or self.SCOPE
171 # if no http is provided, credentials must exist
172 if not _http and credentials is None:
173 if client_options.credentials_file:
174 credentials, _ = google.auth.load_credentials_from_file(
175 client_options.credentials_file, scopes=scopes
176 )
177 else:
178 credentials, _ = google.auth.default(scopes=scopes)
180 self._credentials = google.auth.credentials.with_scopes_if_required(
181 credentials, scopes=scopes
182 )
184 if client_options.quota_project_id:
185 self._credentials = self._credentials.with_quota_project(
186 client_options.quota_project_id
187 )
189 self._http_internal = _http
190 self._client_cert_source = client_options.client_cert_source
192 def __getstate__(self):
193 """Explicitly state that clients are not pickleable."""
194 raise PicklingError(
195 "\n".join(
196 [
197 "Pickling client objects is explicitly not supported.",
198 "Clients have non-trivial state that is local and unpickleable.",
199 ]
200 )
201 )
203 @property
204 def _http(self):
205 """Getter for object used for HTTP transport.
207 :rtype: :class:`~requests.Session`
208 :returns: An HTTP object.
209 """
210 if self._http_internal is None:
211 self._http_internal = google.auth.transport.requests.AuthorizedSession(
212 self._credentials,
213 refresh_timeout=_CREDENTIALS_REFRESH_TIMEOUT,
214 )
215 self._http_internal.configure_mtls_channel(self._client_cert_source)
216 return self._http_internal
218 def close(self):
219 """Clean up transport, if set.
221 Suggested use:
223 .. code-block:: python
225 import contextlib
227 with contextlib.closing(client): # closes on exit
228 do_something_with(client)
229 """
230 if self._http_internal is not None:
231 self._http_internal.close()
234class _ClientProjectMixin(object):
235 """Mixin to allow setting the project on the client.
237 :type project: str
238 :param project:
239 (Optional) the project which the client acts on behalf of. If not
240 passed, falls back to the default inferred from the environment.
242 :type credentials: :class:`google.auth.credentials.Credentials`
243 :param credentials:
244 (Optional) credentials used to discover a project, if not passed.
246 :raises: :class:`EnvironmentError` if the project is neither passed in nor
247 set on the credentials or in the environment. :class:`ValueError`
248 if the project value is invalid.
249 """
251 def __init__(self, project=None, credentials=None):
252 # This test duplicates the one from `google.auth.default`, but earlier,
253 # for backward compatibility: we want the environment variable to
254 # override any project set on the credentials. See:
255 # https://github.com/googleapis/python-cloud-core/issues/27
256 if project is None:
257 project = os.getenv(
258 environment_vars.PROJECT,
259 os.getenv(environment_vars.LEGACY_PROJECT),
260 )
262 # Project set on explicit credentials overrides discovery from
263 # SDK / GAE / GCE.
264 if project is None and credentials is not None:
265 project = getattr(credentials, "project_id", None)
267 if project is None:
268 project = self._determine_default(project)
270 if project is None:
271 raise EnvironmentError(
272 "Project was not passed and could not be "
273 "determined from the environment."
274 )
276 if isinstance(project, bytes):
277 project = project.decode("utf-8")
279 if not isinstance(project, str):
280 raise ValueError("Project must be a string.")
282 self.project = project
284 @staticmethod
285 def _determine_default(project):
286 """Helper: use default project detection."""
287 return _determine_default_project(project)
290class ClientWithProject(Client, _ClientProjectMixin):
291 """Client that also stores a project.
293 :type project: str
294 :param project: the project which the client acts on behalf of. If not
295 passed falls back to the default inferred from the
296 environment.
298 :type credentials: :class:`~google.auth.credentials.Credentials`
299 :param credentials: (Optional) The OAuth2 Credentials to use for this
300 client. If not passed (and if no ``_http`` object is
301 passed), falls back to the default inferred from the
302 environment.
304 :type _http: :class:`~requests.Session`
305 :param _http: (Optional) HTTP object to make requests. Can be any object
306 that defines ``request()`` with the same interface as
307 :meth:`~requests.Session.request`. If not passed, an
308 ``_http`` object is created that is bound to the
309 ``credentials`` for the current object.
310 This parameter should be considered private, and could
311 change in the future.
313 :raises: :class:`ValueError` if the project is neither passed in nor
314 set in the environment.
315 """
317 _SET_PROJECT = True # Used by from_service_account_json()
319 def __init__(self, project=None, credentials=None, client_options=None, _http=None):
320 _ClientProjectMixin.__init__(self, project=project, credentials=credentials)
321 Client.__init__(
322 self, credentials=credentials, client_options=client_options, _http=_http
323 )