diff --git a/google/cloud/datastore/client.py b/google/cloud/datastore/client.py index 0a446630..67651db3 100644 --- a/google/cloud/datastore/client.py +++ b/google/cloud/datastore/client.py @@ -16,6 +16,7 @@ import os import google.api_core.client_options +from google.auth.credentials import AnonymousCredentials from google.cloud._helpers import _LocalStack from google.cloud._helpers import _determine_default_project as _base_default_project from google.cloud.client import ClientWithProject @@ -27,9 +28,6 @@ from google.cloud.datastore.key import Key from google.cloud.datastore.query import Query from google.cloud.datastore.transaction import Transaction -from google.cloud.environment_vars import DISABLE_GRPC -from google.cloud.environment_vars import GCD_DATASET -from google.cloud.environment_vars import GCD_HOST try: from google.cloud.datastore._gapic import make_datastore_api @@ -54,13 +52,20 @@ _DATASTORE_BASE_URL = "https://datastore.googleapis.com" """Datastore API request URL base.""" +DATASTORE_EMULATOR_HOST = "DATASTORE_EMULATOR_HOST" +"""Environment variable defining host for datastore emulator server.""" +DATASTORE_DATASET = "DATASTORE_DATASET" +"""Environment variable defining default dataset ID under GCD.""" +DISABLE_GRPC = "GOOGLE_CLOUD_DISABLE_GRPC" +"""Environment variable acting as flag to disable gRPC.""" + _USE_GRPC = _HAVE_GRPC and not os.getenv(DISABLE_GRPC, False) def _get_gcd_project(): """Gets the GCD application ID if it can be inferred.""" - return os.getenv(GCD_DATASET) + return os.getenv(DATASTORE_DATASET) def _determine_default_project(project=None): @@ -266,6 +271,15 @@ def __init__( _http=None, _use_grpc=None, ): + emulator_host = os.getenv(DATASTORE_EMULATOR_HOST) + + if emulator_host is not None: + if credentials is not None: + raise ValueError( + "Explicit credentials are incompatible with the emulator" + ) + credentials = AnonymousCredentials() + super(Client, self).__init__( project=project, credentials=credentials, @@ -277,14 +291,15 @@ def __init__( self._client_options = client_options self._batch_stack = _LocalStack() self._datastore_api_internal = None + if _use_grpc is None: self._use_grpc = _USE_GRPC else: self._use_grpc = _use_grpc - try: - host = os.environ[GCD_HOST] - self._base_url = "http://" + host - except KeyError: + + if emulator_host is not None: + self._base_url = "http://" + emulator_host + else: api_endpoint = _DATASTORE_BASE_URL if client_options: if type(client_options) == dict: diff --git a/tests/system/test_system.py b/tests/system/test_system.py index 577bd748..85995cc9 100644 --- a/tests/system/test_system.py +++ b/tests/system/test_system.py @@ -22,10 +22,9 @@ from google.cloud._helpers import UTC from google.cloud import datastore from google.cloud.datastore.helpers import GeoPoint -from google.cloud.environment_vars import GCD_DATASET +from google.cloud.datastore.client import DATASTORE_DATASET from google.cloud.exceptions import Conflict -from test_utils.system import EmulatorCreds from test_utils.system import unique_resource_id from tests.system.utils import clear_datastore @@ -44,28 +43,31 @@ class Config(object): def clone_client(client): - return datastore.Client( - project=client.project, - namespace=client.namespace, - credentials=client._credentials, - _http=client._http, - ) + emulator_dataset = os.getenv(DATASTORE_DATASET) + + if emulator_dataset is None: + return datastore.Client( + project=client.project, + namespace=client.namespace, + credentials=client._credentials, + _http=client._http, + ) + else: + return datastore.Client( + project=client.project, namespace=client.namespace, _http=client._http, + ) def setUpModule(): - emulator_dataset = os.getenv(GCD_DATASET) + emulator_dataset = os.getenv(DATASTORE_DATASET) # Isolated namespace so concurrent test runs don't collide. test_namespace = "ns" + unique_resource_id() if emulator_dataset is None: Config.CLIENT = datastore.Client(namespace=test_namespace) else: - credentials = EmulatorCreds() http = requests.Session() # Un-authorized. Config.CLIENT = datastore.Client( - project=emulator_dataset, - namespace=test_namespace, - credentials=credentials, - _http=http, + project=emulator_dataset, namespace=test_namespace, _http=http, ) @@ -240,7 +242,7 @@ def setUpClass(cls): cls.CLIENT.namespace = None # In the emulator, re-populating the datastore is cheap. - if os.getenv(GCD_DATASET) is not None: + if os.getenv(DATASTORE_DATASET) is not None: # Populate the datastore with the cloned client. populate_datastore.add_characters(client=cls.CLIENT) @@ -251,7 +253,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): # In the emulator, destroy the query entities. - if os.getenv(GCD_DATASET) is not None: + if os.getenv(DATASTORE_DATASET) is not None: # Use the client for this test instead of the global. clear_datastore.remove_all_entities(client=cls.CLIENT) @@ -484,7 +486,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): # In the emulator, destroy the query entities. - if os.getenv(GCD_DATASET) is not None: + if os.getenv(DATASTORE_DATASET) is not None: # Use the client for this test instead of the global. clear_datastore.remove_all_entities(client=cls.CLIENT) diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index ab186a8d..1bf4c333 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -52,10 +52,10 @@ def test_no_value(self): self.assertIsNone(project) def test_value_set(self): - from google.cloud.datastore.client import GCD_DATASET + from google.cloud.datastore.client import DATASTORE_DATASET MOCK_PROJECT = object() - environ = {GCD_DATASET: MOCK_PROJECT} + environ = {DATASTORE_DATASET: MOCK_PROJECT} with mock.patch("os.getenv", new=environ.get): project = self._call_fut() self.assertEqual(project, MOCK_PROJECT) @@ -235,18 +235,33 @@ def test_constructor_use_grpc_default(self): ) self.assertTrue(client4._use_grpc) - def test_constructor_gcd_host(self): - from google.cloud.environment_vars import GCD_HOST + def test_constructor_w_emulator_w_creds(self): + from google.cloud.datastore.client import DATASTORE_EMULATOR_HOST host = "localhost:1234" - fake_environ = {GCD_HOST: host} + fake_environ = {DATASTORE_EMULATOR_HOST: host} project = "PROJECT" creds = _make_credentials() http = object() with mock.patch("os.environ", new=fake_environ): - client = self._make_one(project=project, credentials=creds, _http=http) - self.assertEqual(client.base_url, "http://" + host) + with self.assertRaises(ValueError): + self._make_one(project=project, credentials=creds, _http=http) + + def test_constructor_w_emulator_wo_creds(self): + from google.auth.credentials import AnonymousCredentials + from google.cloud.datastore.client import DATASTORE_EMULATOR_HOST + + host = "localhost:1234" + fake_environ = {DATASTORE_EMULATOR_HOST: host} + project = "PROJECT" + http = object() + + with mock.patch("os.environ", new=fake_environ): + client = self._make_one(project=project, _http=http) + + self.assertEqual(client.base_url, "http://" + host) + self.assertIsInstance(client._credentials, AnonymousCredentials) def test_base_url_property(self): from google.cloud.datastore.client import _DATASTORE_BASE_URL