Skip to content
This repository was archived by the owner on Feb 13, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 33 additions & 10 deletions google/cloud/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

import six

import google.api_core.client_options
import google.api_core.exceptions
import google.auth
import google.auth.credentials
import google.auth.transport.requests
Expand Down Expand Up @@ -102,6 +104,8 @@ class Client(_ClientFactoryMixin):
(Optional) The OAuth2 Credentials to use for this client. If not
passed (and if no ``_http`` object is passed), falls back to the
default inferred from the environment.
client_options (google.api_core.client_options.ClientOptions):
(Optional) Custom options for the client.
_http (requests.Session):
(Optional) HTTP object to make requests. Can be any object that
defines ``request()`` with the same interface as
Expand All @@ -123,16 +127,35 @@ class Client(_ClientFactoryMixin):
Needs to be set by subclasses.
"""

def __init__(self, credentials=None, _http=None):
if credentials is not None and not isinstance(
credentials, google.auth.credentials.Credentials
):
def __init__(self, credentials=None, _http=None, client_options=None):
if isinstance(client_options, dict):
client_options = google.api_core.client_options.from_dict(client_options)
if client_options is None:
client_options = google.api_core.client_options.ClientOptions()

if credentials and client_options.credentials_file:
raise google.api_core.exceptions.DuplicateCredentialArgs(
"'credentials' and 'client_options.credentials_file' are mutually exclusive.")

if credentials and not isinstance(credentials, google.auth.credentials.Credentials):
raise ValueError(_GOOGLE_AUTH_CREDENTIALS_HELP)
if credentials is None and _http is None:
credentials, _ = google.auth.default()

scopes = client_options.scopes or self.SCOPE

# if no http is provided, credentials must exist
if not _http and credentials is None:
if client_options.credentials_file:
credentials, _ = google.auth.load_credentials_from_file(
client_options.credentials_file, scopes=scopes)
else:
credentials, _ = google.auth.default(scopes=scopes)

self._credentials = google.auth.credentials.with_scopes_if_required(
credentials, self.SCOPE
)
credentials, scopes=scopes)

if client_options.quota_project_id:
self._credentials = self._credentials.with_quota_project(client_options.quota_project_id)

self._http_internal = _http

def __getstate__(self):
Expand Down Expand Up @@ -222,6 +245,6 @@ class ClientWithProject(Client, _ClientProjectMixin):

_SET_PROJECT = True # Used by from_service_account_json()

def __init__(self, project=None, credentials=None, _http=None):
def __init__(self, project=None, credentials=None, client_options=None, _http=None):
_ClientProjectMixin.__init__(self, project=project)
Client.__init__(self, credentials=credentials, _http=_http)
Client.__init__(self, credentials=credentials, client_options=client_options, _http=_http)
99 changes: 66 additions & 33 deletions tests/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ def _make_one(self, *args, **kw):
def test_unpickleable(self):
import pickle

CREDENTIALS = _make_credentials()
credentials = _make_credentials()
HTTP = object()

client_obj = self._make_one(credentials=CREDENTIALS, _http=HTTP)
client_obj = self._make_one(credentials=credentials, _http=HTTP)
with self.assertRaises(pickle.PicklingError):
pickle.dumps(client_obj)

def test_constructor_defaults(self):
def test_ctor_defaults(self):
credentials = _make_credentials()

patch = mock.patch("google.auth.default", return_value=(credentials, None))
Expand All @@ -66,22 +66,81 @@ def test_constructor_defaults(self):

self.assertIs(client_obj._credentials, credentials)
self.assertIsNone(client_obj._http_internal)
default.assert_called_once_with()
default.assert_called_once_with(scopes=None)

def test_constructor_explicit(self):
def test_ctor_explicit(self):
credentials = _make_credentials()
http = mock.sentinel.http
client_obj = self._make_one(credentials=credentials, _http=http)

self.assertIs(client_obj._credentials, credentials)
self.assertIs(client_obj._http_internal, http)

def test_constructor_bad_credentials(self):
def test_ctor_client_options_w_conflicting_creds(self):
from google.api_core.exceptions import DuplicateCredentialArgs

credentials = _make_credentials()
client_options = {'credentials_file': '/path/to/creds.json'}
with self.assertRaises(DuplicateCredentialArgs):
self._make_one(credentials=credentials, client_options=client_options)

def test_ctor_bad_credentials(self):
credentials = mock.sentinel.credentials

with self.assertRaises(ValueError):
self._make_one(credentials=credentials)

def test_ctor_client_options_w_creds_file_scopes(self):
credentials = _make_credentials()
credentials_file = '/path/to/creds.json'
scopes = ['SCOPE1', 'SCOPE2']
client_options = {'credentials_file': credentials_file, 'scopes': scopes}

patch = mock.patch("google.auth.load_credentials_from_file", return_value=(credentials, None))
with patch as load_credentials_from_file:
client_obj = self._make_one(client_options=client_options)

self.assertIs(client_obj._credentials, credentials)
self.assertIsNone(client_obj._http_internal)
load_credentials_from_file.assert_called_once_with(credentials_file, scopes=scopes)

def test_ctor_client_options_w_quota_project(self):
credentials = _make_credentials()
quota_project_id = 'quota-project-123'
client_options = {'quota_project_id': quota_project_id}

client_obj = self._make_one(credentials=credentials, client_options=client_options)

self.assertIs(client_obj._credentials, credentials.with_quota_project.return_value)
credentials.with_quota_project.assert_called_once_with(quota_project_id)

def test_ctor__http_property_existing(self):
credentials = _make_credentials()
http = object()
client = self._make_one(credentials=credentials, _http=http)
self.assertIs(client._http_internal, http)
self.assertIs(client._http, http)

def test_ctor__http_property_new(self):
from google.cloud.client import _CREDENTIALS_REFRESH_TIMEOUT

credentials = _make_credentials()
client = self._make_one(credentials=credentials)
self.assertIsNone(client._http_internal)

authorized_session_patch = mock.patch(
"google.auth.transport.requests.AuthorizedSession",
return_value=mock.sentinel.http,
)
with authorized_session_patch as AuthorizedSession:
self.assertIs(client._http, mock.sentinel.http)
# Check the mock.
AuthorizedSession.assert_called_once_with(credentials, refresh_timeout=_CREDENTIALS_REFRESH_TIMEOUT)
# Make sure the cached value is used on subsequent access.
self.assertIs(client._http_internal, mock.sentinel.http)
self.assertIs(client._http, mock.sentinel.http)
self.assertEqual(AuthorizedSession.call_count, 1)

def test_from_service_account_json(self):
from google.cloud import _helpers

Expand Down Expand Up @@ -114,32 +173,6 @@ def test_from_service_account_json_bad_args(self):
mock.sentinel.filename, credentials=mock.sentinel.credentials
)

def test__http_property_existing(self):
credentials = _make_credentials()
http = object()
client = self._make_one(credentials=credentials, _http=http)
self.assertIs(client._http_internal, http)
self.assertIs(client._http, http)

def test__http_property_new(self):
from google.cloud.client import _CREDENTIALS_REFRESH_TIMEOUT
credentials = _make_credentials()
client = self._make_one(credentials=credentials)
self.assertIsNone(client._http_internal)

authorized_session_patch = mock.patch(
"google.auth.transport.requests.AuthorizedSession",
return_value=mock.sentinel.http,
)
with authorized_session_patch as AuthorizedSession:
self.assertIs(client._http, mock.sentinel.http)
# Check the mock.
AuthorizedSession.assert_called_once_with(credentials, refresh_timeout=_CREDENTIALS_REFRESH_TIMEOUT)
# Make sure the cached value is used on subsequent access.
self.assertIs(client._http_internal, mock.sentinel.http)
self.assertIs(client._http, mock.sentinel.http)
self.assertEqual(AuthorizedSession.call_count, 1)


class TestClientWithProject(unittest.TestCase):
@staticmethod
Expand Down Expand Up @@ -167,7 +200,7 @@ def test_constructor_defaults(self):
self.assertEqual(client_obj.project, project)
self.assertIs(client_obj._credentials, credentials)
self.assertIsNone(client_obj._http_internal)
default.assert_called_once_with()
default.assert_called_once_with(scopes=None)
_determine_default_project.assert_called_once_with(None)

def test_constructor_missing_project(self):
Expand Down