Skip to content
This repository was archived by the owner on May 7, 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
31 changes: 23 additions & 8 deletions google/cloud/datastore/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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,
Expand All @@ -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:
Expand Down
36 changes: 19 additions & 17 deletions tests/system/test_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
)


Expand Down Expand Up @@ -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)

Expand All @@ -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)

Expand Down Expand Up @@ -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)

Expand Down
29 changes: 22 additions & 7 deletions tests/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down