Skip to content
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
42 changes: 34 additions & 8 deletions google/cloud/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@

import io
import json
import os
from pickle import PicklingError

import six

import google.api_core.client_options
import google.api_core.exceptions
import google.auth
from google.auth import environment_vars
import google.auth.credentials
import google.auth.transport.requests
from google.cloud._helpers import _determine_default_project
Expand Down Expand Up @@ -188,26 +190,50 @@ class _ClientProjectMixin(object):
"""Mixin to allow setting the project on the client.

:type project: str
:param project: the project which the client acts on behalf of. If not
passed falls back to the default inferred from the
environment.
:param project:
(Optional) the project which the client acts on behalf of. If not
passed, falls back to the default inferred from the environment.

:type credentials: :class:`google.auth.credentials.Credentials`
:param credentials:
(Optional) credentials used to discover a project, if not passed.

:raises: :class:`EnvironmentError` if the project is neither passed in nor
set in the environment. :class:`ValueError` if the project value
is invalid.
set on the credentials or in the environment. :class:`ValueError`
if the project value is invalid.
"""

def __init__(self, project=None):
project = self._determine_default(project)
def __init__(self, project=None, credentials=None):
# This test duplicates the one from `google.auth.default`, but earlier,
# for backward compatibility: we want the environment variable to
# override any project set on the credentials. See:
# https://github.com/googleapis/python-cloud-core/issues/27
if project is None:
project = os.getenv(
environment_vars.PROJECT,
os.getenv(environment_vars.LEGACY_PROJECT),
)

# Project set on explicit credentials overrides discovery from
# SDK / GAE / GCE.
if project is None and credentials is not None:
project = getattr(credentials, "project_id", None)

if project is None:
project = self._determine_default(project)

if project is None:
raise EnvironmentError(
"Project was not passed and could not be "
"determined from the environment."
)

if isinstance(project, six.binary_type):
project = project.decode("utf-8")

if not isinstance(project, six.string_types):
raise ValueError("Project must be a string.")

self.project = project

@staticmethod
Expand Down Expand Up @@ -246,5 +272,5 @@ class ClientWithProject(Client, _ClientProjectMixin):
_SET_PROJECT = True # Used by from_service_account_json()

def __init__(self, project=None, credentials=None, client_options=None, _http=None):
_ClientProjectMixin.__init__(self, project=project)
_ClientProjectMixin.__init__(self, project=project, credentials=credentials)
Client.__init__(self, credentials=credentials, client_options=client_options, _http=_http)
126 changes: 126 additions & 0 deletions tests/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,132 @@ def test_from_service_account_json_bad_args(self):
)


class Test_ClientProjectMixin(unittest.TestCase):
@staticmethod
def _get_target_class():
from google.cloud.client import _ClientProjectMixin

return _ClientProjectMixin

def _make_one(self, *args, **kw):
return self._get_target_class()(*args, **kw)

def test_ctor_defaults_wo_envvar(self):
environ = {}
patch_env = mock.patch("os.environ", new=environ)
patch_default = mock.patch(
"google.cloud.client._determine_default_project",
return_value=None,
)
with patch_env:
with patch_default as patched:
with self.assertRaises(EnvironmentError):
self._make_one()

patched.assert_called_once_with(None)

def test_ctor_defaults_w_envvar(self):
from google.auth.environment_vars import PROJECT

project = "some-project-123"
environ = {PROJECT: project}
patch_env = mock.patch("os.environ", new=environ)
with patch_env:
client = self._make_one()

self.assertEqual(client.project, project)

def test_ctor_defaults_w_legacy_envvar(self):
from google.auth.environment_vars import LEGACY_PROJECT

project = "some-project-123"
environ = {LEGACY_PROJECT: project}
patch_env = mock.patch("os.environ", new=environ)
with patch_env:
client = self._make_one()

self.assertEqual(client.project, project)

def test_ctor_w_explicit_project(self):
explicit_project = "explicit-project-456"
patch_default = mock.patch(
"google.cloud.client._determine_default_project",
return_value=None,
)
with patch_default as patched:
client = self._make_one(project=explicit_project)

self.assertEqual(client.project, explicit_project)

patched.assert_not_called()

def test_ctor_w_explicit_project_bytes(self):
explicit_project = b"explicit-project-456"
patch_default = mock.patch(
"google.cloud.client._determine_default_project",
return_value=None,
)
with patch_default as patched:
client = self._make_one(project=explicit_project)

self.assertEqual(client.project, explicit_project.decode("utf-8"))

patched.assert_not_called()

def test_ctor_w_explicit_project_invalid(self):
explicit_project = object()
patch_default = mock.patch(
"google.cloud.client._determine_default_project",
return_value=None,
)
with patch_default as patched:
with self.assertRaises(ValueError):
self._make_one(project=explicit_project)

patched.assert_not_called()

@staticmethod
def _make_credentials(**kw):
from google.auth.credentials import Credentials

class _Credentials(Credentials):
def __init__(self, **kw):
self.__dict__.update(kw)

def refresh(self): # pragma: NO COVER
pass

return _Credentials(**kw)

def test_ctor_w_explicit_credentials_wo_project(self):
default_project = "default-project-123"
credentials = self._make_credentials()
patch_default = mock.patch(
"google.cloud.client._determine_default_project",
return_value=default_project,
)
with patch_default as patched:
client = self._make_one(credentials=credentials)

self.assertEqual(client.project, default_project)

patched.assert_called_once_with(None)

def test_ctor_w_explicit_credentials_w_project(self):
project = "credentials-project-456"
credentials = self._make_credentials(project_id=project)
patch_default = mock.patch(
"google.cloud.client._determine_default_project",
return_value=None,
)
with patch_default as patched:
client = self._make_one(credentials=credentials)

self.assertEqual(client.project, project)

patched.assert_not_called()


class TestClientWithProject(unittest.TestCase):
@staticmethod
def _get_target_class():
Expand Down