Skip to content

Commit f5713b0

Browse files
authored
feat: named db support (#882)
1 parent 8640476 commit f5713b0

26 files changed

+738
-149
lines changed

CONTRIBUTING.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ Running System Tests
146146

147147
- To run system tests for a given package, you can execute::
148148

149+
$ export SYSTEM_TESTS_DATABASE=system-tests-named-db
149150
$ nox -e system
150151

151152
.. note::
@@ -188,6 +189,7 @@ Running System Tests
188189

189190
# Create the indexes
190191
$ gcloud datastore indexes create tests/system/index.yaml
192+
$ gcloud alpha datastore indexes create --database=$SYSTEM_TESTS_DATABASE tests/system/index.yaml
191193

192194

193195
*************

google/cloud/ndb/_datastore_api.py

Lines changed: 85 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import logging
2020

2121
from google.api_core import exceptions as core_exceptions
22+
from google.api_core import gapic_v1
2223
from google.cloud.datastore import helpers
2324
from google.cloud.datastore_v1.types import datastore as datastore_pb2
2425
from google.cloud.datastore_v1.types import entity as entity_pb2
@@ -56,7 +57,7 @@ def stub():
5657
return context.client.stub
5758

5859

59-
def make_call(rpc_name, request, retries=None, timeout=None):
60+
def make_call(rpc_name, request, retries=None, timeout=None, metadata=()):
6061
"""Make a call to the Datastore API.
6162
6263
Args:
@@ -68,6 +69,8 @@ def make_call(rpc_name, request, retries=None, timeout=None):
6869
If :data:`0` is passed, the call is attempted only once.
6970
timeout (float): Timeout, in seconds, to pass to gRPC call. If
7071
:data:`None` is passed, will use :data:`_DEFAULT_TIMEOUT`.
72+
metadata (Sequence[Tuple[str, str]]): Strings which should be
73+
sent along with the request as metadata.
7174
7275
Returns:
7376
tasklets.Future: Future for the eventual response for the API call.
@@ -85,7 +88,7 @@ def make_call(rpc_name, request, retries=None, timeout=None):
8588
def rpc_call():
8689
context = context_module.get_toplevel_context()
8790

88-
call = method.future(request, timeout=timeout)
91+
call = method.future(request, timeout=timeout, metadata=metadata)
8992
rpc = _remote.RemoteCall(call, rpc_name)
9093
utils.logging_debug(log, rpc)
9194
utils.logging_debug(log, "timeout={}", timeout)
@@ -282,7 +285,7 @@ def lookup_callback(self, rpc):
282285
future.set_result(entity)
283286

284287

285-
def _datastore_lookup(keys, read_options, retries=None, timeout=None):
288+
def _datastore_lookup(keys, read_options, retries=None, timeout=None, metadata=()):
286289
"""Issue a Lookup call to Datastore using gRPC.
287290
288291
Args:
@@ -295,18 +298,24 @@ def _datastore_lookup(keys, read_options, retries=None, timeout=None):
295298
If :data:`0` is passed, the call is attempted only once.
296299
timeout (float): Timeout, in seconds, to pass to gRPC call. If
297300
:data:`None` is passed, will use :data:`_DEFAULT_TIMEOUT`.
301+
metadata (Sequence[Tuple[str, str]]): Strings which should be
302+
sent along with the request as metadata.
298303
299304
Returns:
300305
tasklets.Future: Future object for eventual result of lookup.
301306
"""
302307
client = context_module.get_context().client
303308
request = datastore_pb2.LookupRequest(
304309
project_id=client.project,
310+
database_id=client.database,
305311
keys=[key for key in keys],
306312
read_options=read_options,
307313
)
314+
metadata = _add_routing_info(metadata, request)
308315

309-
return make_call("lookup", request, retries=retries, timeout=timeout)
316+
return make_call(
317+
"lookup", request, retries=retries, timeout=timeout, metadata=metadata
318+
)
310319

311320

312321
def get_read_options(options, default_read_consistency=None):
@@ -843,7 +852,7 @@ def _complete(key_pb):
843852
return False
844853

845854

846-
def _datastore_commit(mutations, transaction, retries=None, timeout=None):
855+
def _datastore_commit(mutations, transaction, retries=None, timeout=None, metadata=()):
847856
"""Call Commit on Datastore.
848857
849858
Args:
@@ -857,6 +866,8 @@ def _datastore_commit(mutations, transaction, retries=None, timeout=None):
857866
If :data:`0` is passed, the call is attempted only once.
858867
timeout (float): Timeout, in seconds, to pass to gRPC call. If
859868
:data:`None` is passed, will use :data:`_DEFAULT_TIMEOUT`.
869+
metadata (Sequence[Tuple[str, str]]): Strings which should be
870+
sent along with the request as metadata.
860871
861872
Returns:
862873
tasklets.Tasklet: A future for
@@ -870,12 +881,16 @@ def _datastore_commit(mutations, transaction, retries=None, timeout=None):
870881
client = context_module.get_context().client
871882
request = datastore_pb2.CommitRequest(
872883
project_id=client.project,
884+
database_id=client.database,
873885
mode=mode,
874886
mutations=mutations,
875887
transaction=transaction,
876888
)
889+
metadata = _add_routing_info(metadata, request)
877890

878-
return make_call("commit", request, retries=retries, timeout=timeout)
891+
return make_call(
892+
"commit", request, retries=retries, timeout=timeout, metadata=metadata
893+
)
879894

880895

881896
def allocate(keys, options):
@@ -973,7 +988,7 @@ def allocate_ids_callback(self, rpc):
973988
future.set_result(key)
974989

975990

976-
def _datastore_allocate_ids(keys, retries=None, timeout=None):
991+
def _datastore_allocate_ids(keys, retries=None, timeout=None, metadata=()):
977992
"""Calls ``AllocateIds`` on Datastore.
978993
979994
Args:
@@ -984,15 +999,22 @@ def _datastore_allocate_ids(keys, retries=None, timeout=None):
984999
If :data:`0` is passed, the call is attempted only once.
9851000
timeout (float): Timeout, in seconds, to pass to gRPC call. If
9861001
:data:`None` is passed, will use :data:`_DEFAULT_TIMEOUT`.
1002+
metadata (Sequence[Tuple[str, str]]): Strings which should be
1003+
sent along with the request as metadata.
9871004
9881005
Returns:
9891006
tasklets.Future: A future for
9901007
:class:`google.cloud.datastore_v1.datastore_pb2.AllocateIdsResponse`
9911008
"""
9921009
client = context_module.get_context().client
993-
request = datastore_pb2.AllocateIdsRequest(project_id=client.project, keys=keys)
1010+
request = datastore_pb2.AllocateIdsRequest(
1011+
project_id=client.project, database_id=client.database, keys=keys
1012+
)
1013+
metadata = _add_routing_info(metadata, request)
9941014

995-
return make_call("allocate_ids", request, retries=retries, timeout=timeout)
1015+
return make_call(
1016+
"allocate_ids", request, retries=retries, timeout=timeout, metadata=metadata
1017+
)
9961018

9971019

9981020
@tasklets.tasklet
@@ -1018,7 +1040,7 @@ def begin_transaction(read_only, retries=None, timeout=None):
10181040
raise tasklets.Return(response.transaction)
10191041

10201042

1021-
def _datastore_begin_transaction(read_only, retries=None, timeout=None):
1043+
def _datastore_begin_transaction(read_only, retries=None, timeout=None, metadata=()):
10221044
"""Calls ``BeginTransaction`` on Datastore.
10231045
10241046
Args:
@@ -1029,6 +1051,8 @@ def _datastore_begin_transaction(read_only, retries=None, timeout=None):
10291051
If :data:`0` is passed, the call is attempted only once.
10301052
timeout (float): Timeout, in seconds, to pass to gRPC call. If
10311053
:data:`None` is passed, will use :data:`_DEFAULT_TIMEOUT`.
1054+
metadata (Sequence[Tuple[str, str]]): Strings which should be
1055+
sent along with the request as metadata.
10321056
10331057
Returns:
10341058
tasklets.Tasklet: A future for
@@ -1045,10 +1069,19 @@ def _datastore_begin_transaction(read_only, retries=None, timeout=None):
10451069
)
10461070

10471071
request = datastore_pb2.BeginTransactionRequest(
1048-
project_id=client.project, transaction_options=options
1072+
project_id=client.project,
1073+
database_id=client.database,
1074+
transaction_options=options,
1075+
)
1076+
metadata = _add_routing_info(metadata, request)
1077+
1078+
return make_call(
1079+
"begin_transaction",
1080+
request,
1081+
retries=retries,
1082+
timeout=timeout,
1083+
metadata=metadata,
10491084
)
1050-
1051-
return make_call("begin_transaction", request, retries=retries, timeout=timeout)
10521085

10531086

10541087
@tasklets.tasklet
@@ -1069,7 +1102,7 @@ def rollback(transaction, retries=None, timeout=None):
10691102
yield _datastore_rollback(transaction, retries=retries, timeout=timeout)
10701103

10711104

1072-
def _datastore_rollback(transaction, retries=None, timeout=None):
1105+
def _datastore_rollback(transaction, retries=None, timeout=None, metadata=()):
10731106
"""Calls Rollback in Datastore.
10741107
10751108
Args:
@@ -1079,14 +1112,50 @@ def _datastore_rollback(transaction, retries=None, timeout=None):
10791112
If :data:`0` is passed, the call is attempted only once.
10801113
timeout (float): Timeout, in seconds, to pass to gRPC call. If
10811114
:data:`None` is passed, will use :data:`_DEFAULT_TIMEOUT`.
1115+
metadata (Sequence[Tuple[str, str]]): Strings which should be
1116+
sent along with the request as metadata.
10821117
10831118
Returns:
10841119
tasklets.Tasklet: Future for
10851120
:class:`google.cloud.datastore_v1.datastore_pb2.RollbackResponse`
10861121
"""
10871122
client = context_module.get_context().client
10881123
request = datastore_pb2.RollbackRequest(
1089-
project_id=client.project, transaction=transaction
1124+
project_id=client.project,
1125+
database_id=client.database,
1126+
transaction=transaction,
10901127
)
1128+
metadata = _add_routing_info(metadata, request)
1129+
1130+
return make_call(
1131+
"rollback", request, retries=retries, timeout=timeout, metadata=metadata
1132+
)
1133+
1134+
1135+
def _add_routing_info(metadata, request):
1136+
"""Adds routing header info to the given metadata.
1137+
1138+
Args:
1139+
metadata (Sequence[Tuple[str, str]]): Strings which should be
1140+
sent along with the request as metadata. Not modified.
1141+
request (Any): An appropriate request object for the call, eg,
1142+
`entity_pb2.LookupRequest` for calling ``Lookup``.
1143+
1144+
Returns:
1145+
Sequence[Tuple[str, str]]: Sequence with routing info added,
1146+
if it is included in the request.
1147+
"""
1148+
header_params = {}
1149+
1150+
if request.project_id:
1151+
header_params["project_id"] = request.project_id
1152+
1153+
if request.database_id:
1154+
header_params["database_id"] = request.database_id
1155+
1156+
if header_params:
1157+
return tuple(metadata) + (
1158+
gapic_v1.routing_header.to_grpc_metadata(header_params),
1159+
)
10911160

1092-
return make_call("rollback", request, retries=retries, timeout=timeout)
1161+
return tuple(metadata)

google/cloud/ndb/_datastore_query.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,17 +1010,22 @@ def _datastore_run_query(query):
10101010
"""
10111011
query_pb = _query_to_protobuf(query)
10121012
partition_id = entity_pb2.PartitionId(
1013-
project_id=query.project, namespace_id=query.namespace
1013+
project_id=query.project,
1014+
database_id=query.database,
1015+
namespace_id=query.namespace,
10141016
)
10151017
read_options = _datastore_api.get_read_options(query)
10161018
request = datastore_pb2.RunQueryRequest(
10171019
project_id=query.project,
1020+
database_id=query.database,
10181021
partition_id=partition_id,
10191022
query=query_pb,
10201023
read_options=read_options,
10211024
)
1025+
metadata = _datastore_api._add_routing_info((), request)
1026+
10221027
response = yield _datastore_api.make_call(
1023-
"run_query", request, timeout=query.timeout
1028+
"run_query", request, timeout=query.timeout, metadata=metadata
10241029
)
10251030
utils.logging_debug(log, response)
10261031
raise tasklets.Return(response)

google/cloud/ndb/_gql.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,7 @@ def __init__(self, query_string, _app=None, _auth_domain=None, namespace=None):
9898
9999
Args:
100100
query_string (str): properly formatted GQL query string.
101-
namespace (str): the namespace to use for this query.
102-
101+
namespace (str): The namespace to use for this query. Defaults to the client's value.
103102
Raises:
104103
exceptions.BadQueryError: if the query is not parsable.
105104
"""
@@ -853,7 +852,10 @@ def _key_function(values):
853852
context = context_module.get_context()
854853
client = context.client
855854
return key.Key(
856-
*values, namespace=context.get_namespace(), project=client.project
855+
*values,
856+
project=client.project,
857+
database=client.database,
858+
namespace=context.get_namespace(),
857859
)
858860
_raise_cast_error(
859861
"Key requires even number of operands or single string, {}".format(values)

google/cloud/ndb/client.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
"""A client for NDB which manages credentials, project, namespace."""
15+
"""A client for NDB which manages credentials, project, namespace, and database."""
1616

1717
import contextlib
1818
import grpc
@@ -92,17 +92,25 @@ class Client(google_client.ClientWithProject):
9292
client_options (Optional[:class:`~google.api_core.client_options.ClientOptions` or :class:`dict`])
9393
Client options used to set user options on the client.
9494
API Endpoint should be set through client_options.
95+
database (Optional[str]): Database to access. Defaults to the (default) database.
9596
"""
9697

9798
SCOPE = ("https://www.googleapis.com/auth/datastore",)
9899
"""The scopes required for authenticating as a Cloud Datastore consumer."""
99100

100101
def __init__(
101-
self, project=None, namespace=None, credentials=None, client_options=None
102+
self,
103+
project=None,
104+
namespace=None,
105+
credentials=None,
106+
client_options=None,
107+
database=None,
102108
):
103109
self.namespace = namespace
110+
self.host = os.environ.get(environment_vars.GCD_HOST, DATASTORE_API_HOST)
104111
self.client_info = _CLIENT_INFO
105112
self._client_options = client_options
113+
self.database = database
106114

107115
# Use insecure connection when using Datastore Emulator, otherwise
108116
# use secure connection

0 commit comments

Comments
 (0)