From c8772096c29440b24d40b50a3196e9adb942d508 Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Mon, 13 Mar 2017 12:50:21 -0700 Subject: [PATCH 1/2] Using GAPIC datastore object (and an HTTP equivalent) for lookup. This is the last method to be ported over so the Connection() base class as well as the _DatastoreAPIOverGRPC and _DatastoreAPIOverHttp helper classes have been totally removed. This commit represents a "working" implementation (i.e. system tests pass) but the unit tests have yet to be updated. --- datastore/google/cloud/datastore/_gax.py | 47 ------ datastore/google/cloud/datastore/_http.py | 147 +++--------------- datastore/google/cloud/datastore/batch.py | 2 +- datastore/google/cloud/datastore/client.py | 58 +++++-- datastore/google/cloud/datastore/query.py | 2 - .../google/cloud/datastore/transaction.py | 1 - 6 files changed, 65 insertions(+), 192 deletions(-) diff --git a/datastore/google/cloud/datastore/_gax.py b/datastore/google/cloud/datastore/_gax.py index fac5770239d0..4f075330d3e5 100644 --- a/datastore/google/cloud/datastore/_gax.py +++ b/datastore/google/cloud/datastore/_gax.py @@ -19,16 +19,13 @@ import sys from google.cloud.gapic.datastore.v1 import datastore_client -from google.cloud.proto.datastore.v1 import datastore_pb2_grpc from google.gax.errors import GaxError from google.gax.grpc import exc_to_code from google.gax.utils import metrics from grpc import StatusCode import six -from google.cloud._helpers import make_insecure_stub from google.cloud._helpers import make_secure_channel -from google.cloud._helpers import make_secure_stub from google.cloud._http import DEFAULT_USER_AGENT from google.cloud import exceptions @@ -92,50 +89,6 @@ def _grpc_catch_rendezvous(): six.reraise(error_class, new_exc, sys.exc_info()[2]) -class _DatastoreAPIOverGRPC(object): - """Helper mapping datastore API methods. - - Makes requests to send / receive protobuf content over gRPC. - - Methods make bare API requests without any helpers for constructing - the requests or parsing the responses. - - :type connection: :class:`Connection` - :param connection: A connection object that contains helpful - information for making requests. - """ - - def __init__(self, connection): - parse_result = six.moves.urllib_parse.urlparse( - connection.api_base_url) - host = parse_result.hostname - if parse_result.scheme == 'https': - self._stub = make_secure_stub( - connection.credentials, DEFAULT_USER_AGENT, - datastore_pb2_grpc.DatastoreStub, host, - extra_options=_GRPC_EXTRA_OPTIONS) - else: - self._stub = make_insecure_stub( - datastore_pb2_grpc.DatastoreStub, host) - - def lookup(self, project, request_pb): - """Perform a ``lookup`` request. - - :type project: str - :param project: The project to connect to. This is - usually your project name in the cloud console. - - :type request_pb: :class:`.datastore_pb2.LookupRequest` - :param request_pb: The request protobuf object. - - :rtype: :class:`.datastore_pb2.LookupResponse` - :returns: The returned protobuf response object. - """ - request_pb.project_id = project - with _grpc_catch_rendezvous(): - return self._stub.Lookup(request_pb) - - class GAPICDatastoreAPI(datastore_client.DatastoreClient): """An API object that sends proto-over-gRPC requests. diff --git a/datastore/google/cloud/datastore/_http.py b/datastore/google/cloud/datastore/_http.py index 910da9f0dbf6..0723a97a0de4 100644 --- a/datastore/google/cloud/datastore/_http.py +++ b/datastore/google/cloud/datastore/_http.py @@ -14,22 +14,13 @@ """Connections to Google Cloud Datastore API servers.""" -import os - from google.rpc import status_pb2 from google.cloud import _http as connection_module -from google.cloud.environment_vars import DISABLE_GRPC from google.cloud import exceptions from google.cloud.proto.datastore.v1 import datastore_pb2 as _datastore_pb2 from google.cloud.datastore import __version__ -try: - from google.cloud.datastore._gax import _DatastoreAPIOverGRPC - _HAVE_GRPC = True -except ImportError: # pragma: NO COVER - _DatastoreAPIOverGRPC = None - _HAVE_GRPC = False DATASTORE_API_HOST = 'datastore.googleapis.com' @@ -42,8 +33,6 @@ '/{project}:{method}') """A template for the URL of a particular API call.""" -_DISABLE_GRPC = os.getenv(DISABLE_GRPC, False) -_USE_GRPC = _HAVE_GRPC and not _DISABLE_GRPC _CLIENT_INFO = connection_module.CLIENT_INFO_TEMPLATE.format(__version__) @@ -148,121 +137,45 @@ def build_api_url(project, method, base_url): project=project, method=method) -class _DatastoreAPIOverHttp(object): - """Helper mapping datastore API methods. - - Makes requests to send / receive protobuf content over HTTP/1.1. +class HTTPDatastoreAPI(object): + """An API object that sends proto-over-HTTP requests. - Methods make bare API requests without any helpers for constructing - the requests or parsing the responses. + Intended to provide the same methods as the GAPIC ``DatastoreClient``. - :type connection: :class:`Connection` - :param connection: A connection object that contains helpful - information for making requests. + :type client: :class:`~google.cloud.datastore.client.Client` + :param client: The client that provides configuration. """ - def __init__(self, connection): - self.connection = connection + def __init__(self, client): + self.client = client - def lookup(self, project, request_pb): + def lookup(self, project, read_options, key_pbs): """Perform a ``lookup`` request. :type project: str :param project: The project to connect to. This is usually your project name in the cloud console. - :type request_pb: :class:`.datastore_pb2.LookupRequest` - :param request_pb: The request protobuf object. - - :rtype: :class:`.datastore_pb2.LookupResponse` - :returns: The returned protobuf response object. - """ - return _rpc(self.connection.http, project, 'lookup', - self.connection.api_base_url, - request_pb, _datastore_pb2.LookupResponse) - - -class Connection(connection_module.Connection): - """A connection to the Google Cloud Datastore via the Protobuf API. - - This class should understand only the basic types (and protobufs) - in method arguments, however it should be capable of returning advanced - types. - - :type client: :class:`~google.cloud.datastore.client.Client` - :param client: The client that owns the current connection. - """ - - def __init__(self, client): - super(Connection, self).__init__(client) - self.api_base_url = client._base_url - if _USE_GRPC: - self._datastore_api = _DatastoreAPIOverGRPC(self) - else: - self._datastore_api = _DatastoreAPIOverHttp(self) - - def lookup(self, project, key_pbs, - eventual=False, transaction_id=None): - """Lookup keys from a project in the Cloud Datastore. - - Maps the ``DatastoreService.Lookup`` protobuf RPC. - - This uses mostly protobufs - (:class:`.entity_pb2.Key` as input and :class:`.entity_pb2.Entity` - as output). It is used under the hood in - :meth:`Client.get() <.datastore.client.Client.get>`: - - .. code-block:: python - - >>> from google.cloud import datastore - >>> client = datastore.Client(project='project') - >>> key = client.key('MyKind', 1234) - >>> client.get(key) - [] - - Using a :class:`Connection` directly: - - .. code-block:: python - - >>> connection.lookup('project', [key.to_protobuf()]) - [] - - :type project: str - :param project: The project to look up the keys in. + :type read_options: :class:`.datastore_pb2.ReadOptions` + :param read_options: The options for this lookup. Contains a + either the transaction for the read or + ``STRONG`` or ``EVENTUAL`` read consistency. :type key_pbs: list of :class:`.entity_pb2.Key` :param key_pbs: The keys to retrieve from the datastore. - :type eventual: bool - :param eventual: If False (the default), request ``STRONG`` read - consistency. If True, request ``EVENTUAL`` read - consistency. - - :type transaction_id: str - :param transaction_id: If passed, make the request in the scope of - the given transaction. Incompatible with - ``eventual==True``. - :rtype: :class:`.datastore_pb2.LookupResponse` - :returns: The returned protobuf for the lookup request. + :returns: The returned protobuf response object. """ - lookup_request = _datastore_pb2.LookupRequest(keys=key_pbs) - _set_read_options(lookup_request, eventual, transaction_id) - return self._datastore_api.lookup(project, lookup_request) - - -class HTTPDatastoreAPI(object): - """An API object that sends proto-over-HTTP requests. - - Intended to provide the same methods as the GAPIC ``DatastoreClient``. - - :type client: :class:`~google.cloud.datastore.client.Client` - :param client: The client that provides configuration. - """ - - def __init__(self, client): - self.client = client + request_pb = _datastore_pb2.LookupRequest( + project_id=project, + read_options=read_options, + keys=key_pbs, + ) + return _rpc(self.client._http, project, 'lookup', + self.client._base_url, + request_pb, _datastore_pb2.LookupResponse) def run_query(self, project, partition_id, read_options, query=None, gql_query=None): @@ -390,21 +303,3 @@ def allocate_ids(self, project, key_pbs): return _rpc(self.client._http, project, 'allocateIds', self.client._base_url, request_pb, _datastore_pb2.AllocateIdsResponse) - - -def _set_read_options(request, eventual, transaction_id): - """Validate rules for read options, and assign to the request. - - Helper method for ``lookup()`` and ``run_query``. - - :raises: :class:`ValueError` if ``eventual`` is ``True`` and the - ``transaction_id`` is not ``None``. - """ - if eventual and (transaction_id is not None): - raise ValueError('eventual must be False when in a transaction') - - opts = request.read_options - if eventual: - opts.read_consistency = _datastore_pb2.ReadOptions.EVENTUAL - elif transaction_id: - opts.transaction = transaction_id diff --git a/datastore/google/cloud/datastore/batch.py b/datastore/google/cloud/datastore/batch.py index a5b80e432c9a..b20ba7047670 100644 --- a/datastore/google/cloud/datastore/batch.py +++ b/datastore/google/cloud/datastore/batch.py @@ -249,7 +249,7 @@ def _commit(self): self.project, mode, self._mutations, transaction=self._id) _, updated_keys = _parse_commit_response(commit_response_pb) # If the back-end returns without error, we are guaranteed that - # :meth:`Connection.commit` will return keys that match (length and + # ``commit`` will return keys that match (length and # order) directly ``_partial_key_entities``. for new_key_pb, entity in zip(updated_keys, self._partial_key_entities): diff --git a/datastore/google/cloud/datastore/client.py b/datastore/google/cloud/datastore/client.py index 34f14735a592..2579fd85b097 100644 --- a/datastore/google/cloud/datastore/client.py +++ b/datastore/google/cloud/datastore/client.py @@ -15,6 +15,8 @@ import os +from google.cloud.proto.datastore.v1 import datastore_pb2 as _datastore_pb2 + from google.cloud._helpers import _LocalStack from google.cloud._helpers import ( _determine_default_project as _base_default_project) @@ -23,7 +25,6 @@ from google.cloud.environment_vars import GCD_DATASET from google.cloud.environment_vars import GCD_HOST -from google.cloud.datastore._http import Connection from google.cloud.datastore._http import HTTPDatastoreAPI from google.cloud.datastore import helpers from google.cloud.datastore.batch import Batch @@ -78,15 +79,18 @@ def _determine_default_project(project=None): return project -def _extended_lookup(connection, project, key_pbs, +def _extended_lookup(datastore_api, project, key_pbs, missing=None, deferred=None, eventual=False, transaction_id=None): """Repeat lookup until all keys found (unless stop requested). Helper function for :meth:`Client.get_multi`. - :type connection: :class:`google.cloud.datastore._http.Connection` - :param connection: The connection used to connect to datastore. + :type datastore_api: + :class:`google.cloud.datastore._http.HTTPDatastoreAPI` + or :class:`google.cloud.datastore._gax.GAPICDatastoreAPI` + :param datastore_api: The datastore API object used to connect + to datastore. :type project: str :param project: The project to make the request for. @@ -127,15 +131,11 @@ def _extended_lookup(connection, project, key_pbs, results = [] loop_num = 0 + read_options = _get_read_options(eventual, transaction_id) while loop_num < _MAX_LOOPS: # loop against possible deferred. loop_num += 1 - - lookup_response = connection.lookup( - project=project, - key_pbs=key_pbs, - eventual=eventual, - transaction_id=transaction_id, - ) + lookup_response = datastore_api.lookup( + project, read_options, key_pbs) # Accumulate the new results. results.extend(result.entity for result in lookup_response.found) @@ -210,9 +210,6 @@ def __init__(self, project=None, namespace=None, self._base_url = 'http://' + host except KeyError: self._base_url = _DATASTORE_BASE_URL - # NOTE: Make sure all properties are set before passing to - # ``Connection`` (e.g. ``_base_url``). - self._connection = Connection(self) @staticmethod def _determine_default(project): @@ -347,7 +344,7 @@ def get_multi(self, keys, missing=None, deferred=None, transaction=None): transaction = self.current_transaction entity_pbs = _extended_lookup( - connection=self._connection, + datastore_api=self._datastore_api, project=self.project, key_pbs=[k.to_protobuf() for k in keys], missing=missing, @@ -569,3 +566,34 @@ def do_something(entity): if 'namespace' not in kwargs: kwargs['namespace'] = self.namespace return Query(self, **kwargs) + + +def _get_read_options(eventual, transaction_id): + """Validate rules for read options, and assign to the request. + + Helper method for ``lookup()`` and ``run_query``. + + :type eventual: bool + :param eventual: Flag indicating if ``EVENTUAL`` or ``STRONG`` + consistency should be used. + + :type transaction_id: bytes + :param transaction_id: A transaction identifier (may be null). + + :rtype: :class:`.datastore_pb2.ReadOptions` + :returns: The read options corresponding to the inputs. + :raises: :class:`ValueError` if ``eventual`` is ``True`` and the + ``transaction_id`` is not ``None``. + """ + if transaction_id is None: + if eventual: + return _datastore_pb2.ReadOptions( + read_consistency=_datastore_pb2.ReadOptions.EVENTUAL) + else: + return _datastore_pb2.ReadOptions() + else: + if eventual: + raise ValueError('eventual must be False when in a transaction') + else: + return _datastore_pb2.ReadOptions( + transaction=transaction_id) diff --git a/datastore/google/cloud/datastore/query.py b/datastore/google/cloud/datastore/query.py index 27189bdb12b2..ebbe72eed52a 100644 --- a/datastore/google/cloud/datastore/query.py +++ b/datastore/google/cloud/datastore/query.py @@ -362,8 +362,6 @@ def fetch(self, limit=None, offset=0, start_cursor=None, end_cursor=None, :rtype: :class:`Iterator` :returns: The iterator for the query. - :raises: ValueError if ``connection`` is not passed and no implicit - default has been set. """ if client is None: client = self._client diff --git a/datastore/google/cloud/datastore/transaction.py b/datastore/google/cloud/datastore/transaction.py index c1cd6a01321a..00d4ac1d891b 100644 --- a/datastore/google/cloud/datastore/transaction.py +++ b/datastore/google/cloud/datastore/transaction.py @@ -196,7 +196,6 @@ def rollback(self): This method has necessary side-effects: - - Sets the current connection's transaction reference to None. - Sets the current transaction's ID to None. """ try: From b01768cfdb2cbcc2e52bcb76008e0f987f237f54 Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Mon, 13 Mar 2017 14:05:41 -0700 Subject: [PATCH 2/2] Updating unit test for removal of datastore Connection. Also moved the Connection.lookup() method onto GAPIC and GAPIC-like HTTP API object. --- datastore/unit_tests/test__gax.py | 105 +----------- datastore/unit_tests/test__http.py | 228 +++++++------------------ datastore/unit_tests/test_client.py | 247 +++++++++++++++++----------- 3 files changed, 209 insertions(+), 371 deletions(-) diff --git a/datastore/unit_tests/test__gax.py b/datastore/unit_tests/test__gax.py index 433162542ea9..bb7dd6ac1773 100644 --- a/datastore/unit_tests/test__gax.py +++ b/datastore/unit_tests/test__gax.py @@ -16,7 +16,7 @@ import mock -from google.cloud.datastore._http import _HAVE_GRPC +from google.cloud.datastore.client import _HAVE_GRPC @unittest.skipUnless(_HAVE_GRPC, 'No gRPC') @@ -112,95 +112,6 @@ def test_gax_error_not_mapped(self): self._fake_method(exc) -class Test_DatastoreAPIOverGRPC(unittest.TestCase): - - @staticmethod - def _get_target_class(): - from google.cloud.datastore._gax import _DatastoreAPIOverGRPC - - return _DatastoreAPIOverGRPC - - def _make_one(self, stub, connection=None, secure=True): - if secure: - patch = mock.patch( - 'google.cloud.datastore._gax.make_secure_stub', - return_value=stub) - base_url = 'https://test.invalid' - else: - patch = mock.patch( - 'google.cloud.datastore._gax.make_insecure_stub', - return_value=stub) - base_url = 'http://test.invalid' - - if connection is None: - connection = mock.Mock( - credentials=object(), - api_base_url=base_url, - spec=['credentials', 'api_base_url'], - ) - - with patch as make_stub_mock: - api_obj = self._get_target_class()(connection) - return api_obj, make_stub_mock - - def test_constructor(self): - from google.cloud._http import DEFAULT_USER_AGENT - import google.cloud.datastore._gax as MUT - - host = 'test.invalid' - conn = mock.Mock( - credentials=object(), - api_base_url='https://' + host, - spec=['credentials', 'api_base_url'], - ) - - stub = _GRPCStub() - datastore_api, make_stub_mock = self._make_one( - stub, connection=conn) - - self.assertIs(datastore_api._stub, stub) - make_stub_mock.assert_called_once_with( - conn.credentials, - DEFAULT_USER_AGENT, - MUT.datastore_pb2_grpc.DatastoreStub, - host, - extra_options=MUT._GRPC_EXTRA_OPTIONS, - ) - - def test_constructor_insecure(self): - from google.cloud.proto.datastore.v1 import datastore_pb2_grpc - - host = 'test.invalid' - conn = mock.Mock( - credentials=object(), - api_base_url='http://' + host, - spec=['credentials', 'api_base_url'], - ) - - stub = _GRPCStub() - datastore_api, make_stub_mock = self._make_one( - stub, connection=conn, secure=False) - - self.assertIs(datastore_api._stub, stub) - make_stub_mock.assert_called_once_with( - datastore_pb2_grpc.DatastoreStub, - host, - ) - - def test_lookup(self): - return_val = object() - stub = _GRPCStub(return_val) - datastore_api, _ = self._make_one(stub=stub) - - request_pb = mock.Mock(project_id=None, spec=['project_id']) - project = 'PROJECT' - result = datastore_api.lookup(project, request_pb) - self.assertIs(result, return_val) - self.assertEqual(request_pb.project_id, project) - self.assertEqual(stub.method_calls, - [(request_pb, 'Lookup')]) - - @unittest.skipUnless(_HAVE_GRPC, 'No gRPC') class TestGAPICDatastoreAPI(unittest.TestCase): @@ -263,17 +174,3 @@ def test_it(self, make_chan, mock_klass): mock_klass.assert_called_once_with( channel=mock.sentinel.channel, lib_name='gccl', lib_version=__version__) - - -class _GRPCStub(object): - - def __init__(self, return_val=None): - self.return_val = return_val - self.method_calls = [] - - def _method(self, request_pb, name): - self.method_calls.append((request_pb, name)) - return self.return_val - - def Lookup(self, request_pb): - return self._method(request_pb, 'Lookup') diff --git a/datastore/unit_tests/test__http.py b/datastore/unit_tests/test__http.py index 748eba93b7f3..db364ec4dd61 100644 --- a/datastore/unit_tests/test__http.py +++ b/datastore/unit_tests/test__http.py @@ -114,99 +114,29 @@ def test_it(self): base_url) -class Test_DatastoreAPIOverHttp(unittest.TestCase): +class TestHTTPDatastoreAPI(unittest.TestCase): @staticmethod def _get_target_class(): - from google.cloud.datastore._http import _DatastoreAPIOverHttp - - return _DatastoreAPIOverHttp - - def _make_one(self, *args, **kw): - return self._get_target_class()(*args, **kw) - - def test_constructor(self): - connection = object() - ds_api = self._make_one(connection) - self.assertIs(ds_api.connection, connection) - - def test_lookup(self): - from google.cloud.proto.datastore.v1 import datastore_pb2 - - connection = mock.Mock( - api_base_url='test.invalid', spec=['http', 'api_base_url']) - ds_api = self._make_one(connection) - - project = 'project' - request_pb = object() - - patch = mock.patch( - 'google.cloud.datastore._http._rpc', - return_value=mock.sentinel.looked_up) - with patch as mock_rpc: - result = ds_api.lookup(project, request_pb) - self.assertIs(result, mock.sentinel.looked_up) - - mock_rpc.assert_called_once_with( - connection.http, project, 'lookup', - connection.api_base_url, - request_pb, datastore_pb2.LookupResponse) + from google.cloud.datastore._http import HTTPDatastoreAPI + return HTTPDatastoreAPI -class TestConnection(unittest.TestCase): + def _make_one(self, *args, **kwargs): + return self._get_target_class()(*args, **kwargs) @staticmethod - def _get_target_class(): - from google.cloud.datastore._http import Connection - - return Connection - - def _make_one(self, client, use_grpc=False): - with mock.patch('google.cloud.datastore._http._USE_GRPC', - new=use_grpc): - return self._get_target_class()(client) + def _make_query_pb(kind): + from google.cloud.proto.datastore.v1 import query_pb2 - def test_inherited_url(self): - client = mock.Mock(_base_url='test.invalid', spec=['_base_url']) - conn = self._make_one(client) - self.assertEqual(conn.api_base_url, client._base_url) + return query_pb2.Query( + kind=[query_pb2.KindExpression(name=kind)], + ) def test_constructor(self): - client = mock.Mock(spec=['_base_url']) - conn = self._make_one(client) - self.assertIs(conn._client, client) - - def test_constructor_without_grpc(self): - connections = [] - client = mock.Mock(spec=['_base_url']) - return_val = object() - - def mock_api(connection): - connections.append(connection) - return return_val - - patch = mock.patch( - 'google.cloud.datastore._http._DatastoreAPIOverHttp', - new=mock_api) - with patch: - conn = self._make_one(client, use_grpc=False) - - self.assertIs(conn._client, client) - self.assertIs(conn._datastore_api, return_val) - self.assertEqual(connections, [conn]) - - def test_constructor_with_grpc(self): - client = mock.Mock(spec=['_base_url']) - - patch = mock.patch( - 'google.cloud.datastore._http._DatastoreAPIOverGRPC', - return_value=mock.sentinel.ds_api) - with patch as mock_klass: - conn = self._make_one(client, use_grpc=True) - mock_klass.assert_called_once_with(conn) - - self.assertIs(conn._client, client) - self.assertIs(conn._datastore_api, mock.sentinel.ds_api) + client = object() + ds_api = self._make_one(client) + self.assertIs(ds_api.client, client) def test_lookup_single_key_empty_response(self): from google.cloud.proto.datastore.v1 import datastore_pb2 @@ -214,6 +144,7 @@ def test_lookup_single_key_empty_response(self): project = 'PROJECT' key_pb = _make_key_pb(project) rsp_pb = datastore_pb2.LookupResponse() + read_options = datastore_pb2.ReadOptions() # Create mock HTTP and client with response. http = Http({'status': '200'}, rsp_pb.SerializeToString()) @@ -221,12 +152,12 @@ def test_lookup_single_key_empty_response(self): _http=http, _base_url='test.invalid', spec=['_http', '_base_url']) # Make request. - conn = self._make_one(client) - response = conn.lookup(project, [key_pb]) + ds_api = self._make_one(client) + response = ds_api.lookup(project, read_options, [key_pb]) # Check the result and verify the callers. self.assertEqual(response, rsp_pb) - uri = _build_expected_url(conn.api_base_url, project, 'lookup') + uri = _build_expected_url(client._base_url, project, 'lookup') self.assertEqual(len(response.found), 0) self.assertEqual(len(response.missing), 0) self.assertEqual(len(response.deferred), 0) @@ -234,9 +165,8 @@ def test_lookup_single_key_empty_response(self): _verify_protobuf_call(self, cw, uri) request = datastore_pb2.LookupRequest() request.ParseFromString(cw['body']) - keys = list(request.keys) - self.assertEqual(len(keys), 1) - self.assertEqual(key_pb, keys[0]) + self.assertEqual(list(request.keys), [key_pb]) + self.assertEqual(request.read_options, read_options) def test_lookup_single_key_empty_response_w_eventual(self): from google.cloud.proto.datastore.v1 import datastore_pb2 @@ -244,6 +174,8 @@ def test_lookup_single_key_empty_response_w_eventual(self): project = 'PROJECT' key_pb = _make_key_pb(project) rsp_pb = datastore_pb2.LookupResponse() + read_options = datastore_pb2.ReadOptions( + read_consistency=datastore_pb2.ReadOptions.EVENTUAL) # Create mock HTTP and client with response. http = Http({'status': '200'}, rsp_pb.SerializeToString()) @@ -251,12 +183,12 @@ def test_lookup_single_key_empty_response_w_eventual(self): _http=http, _base_url='test.invalid', spec=['_http', '_base_url']) # Make request. - conn = self._make_one(client) - response = conn.lookup(project, [key_pb], eventual=True) + ds_api = self._make_one(client) + response = ds_api.lookup(project, read_options, [key_pb]) # Check the result and verify the callers. self.assertEqual(response, rsp_pb) - uri = _build_expected_url(conn.api_base_url, project, 'lookup') + uri = _build_expected_url(client._base_url, project, 'lookup') self.assertEqual(len(response.found), 0) self.assertEqual(len(response.missing), 0) self.assertEqual(len(response.deferred), 0) @@ -264,22 +196,8 @@ def test_lookup_single_key_empty_response_w_eventual(self): _verify_protobuf_call(self, cw, uri) request = datastore_pb2.LookupRequest() request.ParseFromString(cw['body']) - keys = list(request.keys) - self.assertEqual(len(keys), 1) - self.assertEqual(key_pb, keys[0]) - self.assertEqual(request.read_options.read_consistency, - datastore_pb2.ReadOptions.EVENTUAL) - self.assertEqual(request.read_options.transaction, b'') - - def test_lookup_single_key_empty_response_w_eventual_and_transaction(self): - project = 'PROJECT' - transaction = b'TRANSACTION' - key_pb = _make_key_pb(project) - - client = mock.Mock(spec=['_base_url']) - conn = self._make_one(client) - self.assertRaises(ValueError, conn.lookup, project, [key_pb], - eventual=True, transaction_id=transaction) + self.assertEqual(list(request.keys), [key_pb]) + self.assertEqual(request.read_options, read_options) def test_lookup_single_key_empty_response_w_transaction(self): from google.cloud.proto.datastore.v1 import datastore_pb2 @@ -288,6 +206,7 @@ def test_lookup_single_key_empty_response_w_transaction(self): transaction = b'TRANSACTION' key_pb = _make_key_pb(project) rsp_pb = datastore_pb2.LookupResponse() + read_options = datastore_pb2.ReadOptions(transaction=transaction) # Create mock HTTP and client with response. http = Http({'status': '200'}, rsp_pb.SerializeToString()) @@ -295,12 +214,12 @@ def test_lookup_single_key_empty_response_w_transaction(self): _http=http, _base_url='test.invalid', spec=['_http', '_base_url']) # Make request. - conn = self._make_one(client) - response = conn.lookup(project, [key_pb], transaction_id=transaction) + ds_api = self._make_one(client) + response = ds_api.lookup(project, read_options, [key_pb]) # Check the result and verify the callers. self.assertEqual(response, rsp_pb) - uri = _build_expected_url(conn.api_base_url, project, 'lookup') + uri = _build_expected_url(client._base_url, project, 'lookup') self.assertEqual(len(response.found), 0) self.assertEqual(len(response.missing), 0) self.assertEqual(len(response.deferred), 0) @@ -308,10 +227,8 @@ def test_lookup_single_key_empty_response_w_transaction(self): _verify_protobuf_call(self, cw, uri) request = datastore_pb2.LookupRequest() request.ParseFromString(cw['body']) - keys = list(request.keys) - self.assertEqual(len(keys), 1) - self.assertEqual(key_pb, keys[0]) - self.assertEqual(request.read_options.transaction, transaction) + self.assertEqual(list(request.keys), [key_pb]) + self.assertEqual(request.read_options, read_options) def test_lookup_single_key_nonempty_response(self): from google.cloud.proto.datastore.v1 import datastore_pb2 @@ -323,6 +240,7 @@ def test_lookup_single_key_nonempty_response(self): entity = entity_pb2.Entity() entity.key.CopyFrom(key_pb) rsp_pb.found.add(entity=entity) + read_options = datastore_pb2.ReadOptions() # Create mock HTTP and client with response. http = Http({'status': '200'}, rsp_pb.SerializeToString()) @@ -330,12 +248,12 @@ def test_lookup_single_key_nonempty_response(self): _http=http, _base_url='test.invalid', spec=['_http', '_base_url']) # Make request. - conn = self._make_one(client) - response = conn.lookup(project, [key_pb]) + ds_api = self._make_one(client) + response = ds_api.lookup(project, read_options, [key_pb]) # Check the result and verify the callers. self.assertEqual(response, rsp_pb) - uri = _build_expected_url(conn.api_base_url, project, 'lookup') + uri = _build_expected_url(client._base_url, project, 'lookup') self.assertEqual(len(response.found), 1) self.assertEqual(len(response.missing), 0) self.assertEqual(len(response.deferred), 0) @@ -346,9 +264,8 @@ def test_lookup_single_key_nonempty_response(self): _verify_protobuf_call(self, cw, uri) request = datastore_pb2.LookupRequest() request.ParseFromString(cw['body']) - keys = list(request.keys) - self.assertEqual(len(keys), 1) - self.assertEqual(key_pb, keys[0]) + self.assertEqual(list(request.keys), [key_pb]) + self.assertEqual(request.read_options, read_options) def test_lookup_multiple_keys_empty_response(self): from google.cloud.proto.datastore.v1 import datastore_pb2 @@ -357,6 +274,7 @@ def test_lookup_multiple_keys_empty_response(self): key_pb1 = _make_key_pb(project) key_pb2 = _make_key_pb(project, id_=2345) rsp_pb = datastore_pb2.LookupResponse() + read_options = datastore_pb2.ReadOptions() # Create mock HTTP and client with response. http = Http({'status': '200'}, rsp_pb.SerializeToString()) @@ -364,12 +282,12 @@ def test_lookup_multiple_keys_empty_response(self): _http=http, _base_url='test.invalid', spec=['_http', '_base_url']) # Make request. - conn = self._make_one(client) - response = conn.lookup(project, [key_pb1, key_pb2]) + ds_api = self._make_one(client) + response = ds_api.lookup(project, read_options, [key_pb1, key_pb2]) # Check the result and verify the callers. self.assertEqual(response, rsp_pb) - uri = _build_expected_url(conn.api_base_url, project, 'lookup') + uri = _build_expected_url(client._base_url, project, 'lookup') self.assertEqual(len(response.found), 0) self.assertEqual(len(response.missing), 0) self.assertEqual(len(response.deferred), 0) @@ -377,10 +295,8 @@ def test_lookup_multiple_keys_empty_response(self): _verify_protobuf_call(self, cw, uri) request = datastore_pb2.LookupRequest() request.ParseFromString(cw['body']) - keys = list(request.keys) - self.assertEqual(len(keys), 2) - self.assertEqual(key_pb1, keys[0]) - self.assertEqual(key_pb2, keys[1]) + self.assertEqual(list(request.keys), [key_pb1, key_pb2]) + self.assertEqual(request.read_options, read_options) def test_lookup_multiple_keys_w_missing(self): from google.cloud.proto.datastore.v1 import datastore_pb2 @@ -393,6 +309,7 @@ def test_lookup_multiple_keys_w_missing(self): er_1.entity.key.CopyFrom(key_pb1) er_2 = rsp_pb.missing.add() er_2.entity.key.CopyFrom(key_pb2) + read_options = datastore_pb2.ReadOptions() # Create mock HTTP and client with response. http = Http({'status': '200'}, rsp_pb.SerializeToString()) @@ -400,12 +317,12 @@ def test_lookup_multiple_keys_w_missing(self): _http=http, _base_url='test.invalid', spec=['_http', '_base_url']) # Make request. - conn = self._make_one(client) - response = conn.lookup(project, [key_pb1, key_pb2]) + ds_api = self._make_one(client) + response = ds_api.lookup(project, read_options, [key_pb1, key_pb2]) # Check the result and verify the callers. self.assertEqual(response, rsp_pb) - uri = _build_expected_url(conn.api_base_url, project, 'lookup') + uri = _build_expected_url(client._base_url, project, 'lookup') self.assertEqual(len(response.found), 0) self.assertEqual(len(response.deferred), 0) missing_keys = [result.entity.key for result in response.missing] @@ -414,14 +331,11 @@ def test_lookup_multiple_keys_w_missing(self): _verify_protobuf_call(self, cw, uri) request = datastore_pb2.LookupRequest() request.ParseFromString(cw['body']) - keys = list(request.keys) - self.assertEqual(len(keys), 2) - self.assertEqual(key_pb1, keys[0]) - self.assertEqual(key_pb2, keys[1]) + self.assertEqual(list(request.keys), [key_pb1, key_pb2]) + self.assertEqual(request.read_options, read_options) def test_lookup_multiple_keys_w_deferred(self): from google.cloud.proto.datastore.v1 import datastore_pb2 - from google.cloud import _http as connection_module from google.cloud.datastore._http import _CLIENT_INFO @@ -431,6 +345,7 @@ def test_lookup_multiple_keys_w_deferred(self): rsp_pb = datastore_pb2.LookupResponse() rsp_pb.deferred.add().CopyFrom(key_pb1) rsp_pb.deferred.add().CopyFrom(key_pb2) + read_options = datastore_pb2.ReadOptions() # Create mock HTTP and client with response. http = Http({'status': '200'}, rsp_pb.SerializeToString()) @@ -438,12 +353,12 @@ def test_lookup_multiple_keys_w_deferred(self): _http=http, _base_url='test.invalid', spec=['_http', '_base_url']) # Make request. - conn = self._make_one(client) - response = conn.lookup(project, [key_pb1, key_pb2]) + ds_api = self._make_one(client) + response = ds_api.lookup(project, read_options, [key_pb1, key_pb2]) # Check the result and verify the callers. self.assertEqual(response, rsp_pb) - uri = _build_expected_url(conn.api_base_url, project, 'lookup') + uri = _build_expected_url(client._base_url, project, 'lookup') self.assertEqual(len(response.found), 0) self.assertEqual(len(response.missing), 0) self.assertEqual(list(response.deferred), [key_pb1, key_pb2]) @@ -453,42 +368,15 @@ def test_lookup_multiple_keys_w_deferred(self): self.assertEqual(cw['method'], 'POST') expected_headers = { 'Content-Type': 'application/x-protobuf', - 'User-Agent': conn.USER_AGENT, - 'Content-Length': '48', + 'User-Agent': connection_module.DEFAULT_USER_AGENT, + 'Content-Length': str(len(cw['body'])), connection_module.CLIENT_INFO_HEADER: _CLIENT_INFO, } self.assertEqual(cw['headers'], expected_headers) request = datastore_pb2.LookupRequest() request.ParseFromString(cw['body']) - keys = list(request.keys) - self.assertEqual(len(keys), 2) - self.assertEqual(key_pb1, keys[0]) - self.assertEqual(key_pb2, keys[1]) - - -class TestHTTPDatastoreAPI(unittest.TestCase): - - @staticmethod - def _get_target_class(): - from google.cloud.datastore._http import HTTPDatastoreAPI - - return HTTPDatastoreAPI - - def _make_one(self, *args, **kwargs): - return self._get_target_class()(*args, **kwargs) - - @staticmethod - def _make_query_pb(kind): - from google.cloud.proto.datastore.v1 import query_pb2 - - return query_pb2.Query( - kind=[query_pb2.KindExpression(name=kind)], - ) - - def test_constructor(self): - client = object() - ds_api = self._make_one(client) - self.assertIs(ds_api.client, client) + self.assertEqual(list(request.keys), [key_pb1, key_pb2]) + self.assertEqual(request.read_options, read_options) def test_run_query_w_eventual_no_transaction(self): from google.cloud.proto.datastore.v1 import datastore_pb2 diff --git a/datastore/unit_tests/test_client.py b/datastore/unit_tests/test_client.py index fdc7394af419..8f4197c51419 100644 --- a/datastore/unit_tests/test_client.py +++ b/datastore/unit_tests/test_client.py @@ -120,17 +120,6 @@ class TestClient(unittest.TestCase): PROJECT = 'PROJECT' - def setUp(self): - from google.cloud.datastore import client as MUT - - self.original_cnxn_class = MUT.Connection - MUT.Connection = _MockConnection - - def tearDown(self): - from google.cloud.datastore import client as MUT - - MUT.Connection = self.original_cnxn_class - @staticmethod def _get_target_class(): from google.cloud.datastore.client import Client @@ -179,7 +168,6 @@ def fallback_mock(project): self.assertEqual(client.project, other) self.assertIsNone(client.namespace) - self.assertIsInstance(client._connection, _MockConnection) self.assertIs(client._credentials, creds) self.assertIsNone(client._http_internal) self.assertEqual(client._base_url, _DATASTORE_BASE_URL) @@ -201,7 +189,6 @@ def test_constructor_w_explicit_inputs(self): http=http) self.assertEqual(client.project, other) self.assertEqual(client.namespace, namespace) - self.assertIsInstance(client._connection, _MockConnection) self.assertIs(client._credentials, creds) self.assertIs(client._http_internal, http) self.assertIsNone(client.current_batch) @@ -355,17 +342,25 @@ def test_get_multi_no_keys(self): self.assertEqual(results, []) def test_get_multi_miss(self): + from google.cloud.proto.datastore.v1 import datastore_pb2 from google.cloud.datastore.key import Key creds = _make_credentials() client = self._make_one(credentials=creds) - client._connection._add_lookup_result() + ds_api = _make_datastore_api() + client._datastore_api_internal = ds_api + key = Key('Kind', 1234, project=self.PROJECT) results = client.get_multi([key]) self.assertEqual(results, []) + read_options = datastore_pb2.ReadOptions() + ds_api.lookup.assert_called_once_with( + self.PROJECT, read_options, [key.to_protobuf()]) + def test_get_multi_miss_w_missing(self): from google.cloud.proto.datastore.v1 import entity_pb2 + from google.cloud.proto.datastore.v1 import datastore_pb2 from google.cloud.datastore.key import Key KIND = 'Kind' @@ -381,14 +376,21 @@ def test_get_multi_miss_w_missing(self): creds = _make_credentials() client = self._make_one(credentials=creds) # Set missing entity on mock connection. - client._connection._add_lookup_result(missing=[missed]) + lookup_response = _make_lookup_response(missing=[missed]) + ds_api = _make_datastore_api(lookup_response=lookup_response) + client._datastore_api_internal = ds_api key = Key(KIND, ID, project=self.PROJECT) missing = [] entities = client.get_multi([key], missing=missing) self.assertEqual(entities, []) - self.assertEqual([missed.key.to_protobuf() for missed in missing], - [key.to_protobuf()]) + key_pb = key.to_protobuf() + self.assertEqual( + [missed.key.to_protobuf() for missed in missing], [key_pb]) + + read_options = datastore_pb2.ReadOptions() + ds_api.lookup.assert_called_once_with( + self.PROJECT, read_options, [key_pb]) def test_get_multi_w_missing_non_empty(self): from google.cloud.datastore.key import Key @@ -413,22 +415,31 @@ def test_get_multi_w_deferred_non_empty(self): [key], deferred=deferred) def test_get_multi_miss_w_deferred(self): + from google.cloud.proto.datastore.v1 import datastore_pb2 from google.cloud.datastore.key import Key key = Key('Kind', 1234, project=self.PROJECT) + key_pb = key.to_protobuf() # Set deferred entity on mock connection. creds = _make_credentials() client = self._make_one(credentials=creds) - client._connection._add_lookup_result(deferred=[key.to_protobuf()]) + lookup_response = _make_lookup_response(deferred=[key_pb]) + ds_api = _make_datastore_api(lookup_response=lookup_response) + client._datastore_api_internal = ds_api deferred = [] entities = client.get_multi([key], deferred=deferred) self.assertEqual(entities, []) - self.assertEqual([def_key.to_protobuf() for def_key in deferred], - [key.to_protobuf()]) + self.assertEqual( + [def_key.to_protobuf() for def_key in deferred], [key_pb]) + + read_options = datastore_pb2.ReadOptions() + ds_api.lookup.assert_called_once_with( + self.PROJECT, read_options, [key_pb]) def test_get_multi_w_deferred_from_backend_but_not_passed(self): + from google.cloud.proto.datastore.v1 import datastore_pb2 from google.cloud.proto.datastore.v1 import entity_pb2 from google.cloud.datastore.entity import Entity from google.cloud.datastore.key import Key @@ -445,9 +456,15 @@ def test_get_multi_w_deferred_from_backend_but_not_passed(self): creds = _make_credentials() client = self._make_one(credentials=creds) - # mock up two separate requests - client._connection._add_lookup_result([entity1_pb], deferred=[key2_pb]) - client._connection._add_lookup_result([entity2_pb]) + # Mock up two separate requests. Using an iterable as side_effect + # allows multiple return values. + lookup_response1 = _make_lookup_response( + results=[entity1_pb], deferred=[key2_pb]) + lookup_response2 = _make_lookup_response(results=[entity2_pb]) + ds_api = _make_datastore_api() + ds_api.lookup = mock.Mock( + side_effect=[lookup_response1, lookup_response2], spec=[]) + client._datastore_api_internal = ds_api missing = [] found = client.get_multi([key1, key2], missing=missing) @@ -463,102 +480,104 @@ def test_get_multi_w_deferred_from_backend_but_not_passed(self): self.assertEqual(found[1].key.path, key2.path) self.assertEqual(found[1].key.project, key2.project) - cw = client._connection._lookup_cw - self.assertEqual(len(cw), 2) - - ds_id, k_pbs, eventual, tid = cw[0] - self.assertEqual(ds_id, self.PROJECT) - self.assertEqual(len(k_pbs), 2) - self.assertEqual(key1_pb, k_pbs[0]) - self.assertEqual(key2_pb, k_pbs[1]) - self.assertFalse(eventual) - self.assertIsNone(tid) - - ds_id, k_pbs, eventual, tid = cw[1] - self.assertEqual(ds_id, self.PROJECT) - self.assertEqual(len(k_pbs), 1) - self.assertEqual(key2_pb, k_pbs[0]) - self.assertFalse(eventual) - self.assertIsNone(tid) + self.assertEqual(ds_api.lookup.call_count, 2) + read_options = datastore_pb2.ReadOptions() + ds_api.lookup.assert_any_call( + self.PROJECT, read_options, [key2_pb]) + ds_api.lookup.assert_any_call( + self.PROJECT, read_options, [key1_pb, key2_pb]) def test_get_multi_hit(self): + from google.cloud.proto.datastore.v1 import datastore_pb2 from google.cloud.datastore.key import Key - KIND = 'Kind' - ID = 1234 - PATH = [{'kind': KIND, 'id': ID}] + kind = 'Kind' + id_ = 1234 + path = [{'kind': kind, 'id': id_}] # Make a found entity pb to be returned from mock backend. - entity_pb = _make_entity_pb(self.PROJECT, KIND, ID, 'foo', 'Foo') + entity_pb = _make_entity_pb(self.PROJECT, kind, id_, 'foo', 'Foo') # Make a connection to return the entity pb. creds = _make_credentials() client = self._make_one(credentials=creds) - client._connection._add_lookup_result([entity_pb]) + lookup_response = _make_lookup_response(results=[entity_pb]) + ds_api = _make_datastore_api(lookup_response=lookup_response) + client._datastore_api_internal = ds_api - key = Key(KIND, ID, project=self.PROJECT) + key = Key(kind, id_, project=self.PROJECT) result, = client.get_multi([key]) new_key = result.key # Check the returned value is as expected. self.assertIsNot(new_key, key) self.assertEqual(new_key.project, self.PROJECT) - self.assertEqual(new_key.path, PATH) + self.assertEqual(new_key.path, path) self.assertEqual(list(result), ['foo']) self.assertEqual(result['foo'], 'Foo') + read_options = datastore_pb2.ReadOptions() + ds_api.lookup.assert_called_once_with( + self.PROJECT, read_options, [key.to_protobuf()]) + def test_get_multi_hit_w_transaction(self): + from google.cloud.proto.datastore.v1 import datastore_pb2 from google.cloud.datastore.key import Key - TXN_ID = '123' - KIND = 'Kind' - ID = 1234 - PATH = [{'kind': KIND, 'id': ID}] + txn_id = b'123' + kind = 'Kind' + id_ = 1234 + path = [{'kind': kind, 'id': id_}] # Make a found entity pb to be returned from mock backend. - entity_pb = _make_entity_pb(self.PROJECT, KIND, ID, 'foo', 'Foo') + entity_pb = _make_entity_pb(self.PROJECT, kind, id_, 'foo', 'Foo') # Make a connection to return the entity pb. creds = _make_credentials() client = self._make_one(credentials=creds) - client._connection._add_lookup_result([entity_pb]) + lookup_response = _make_lookup_response(results=[entity_pb]) + ds_api = _make_datastore_api(lookup_response=lookup_response) + client._datastore_api_internal = ds_api - key = Key(KIND, ID, project=self.PROJECT) + key = Key(kind, id_, project=self.PROJECT) txn = client.transaction() - txn._id = TXN_ID + txn._id = txn_id result, = client.get_multi([key], transaction=txn) new_key = result.key # Check the returned value is as expected. self.assertIsNot(new_key, key) self.assertEqual(new_key.project, self.PROJECT) - self.assertEqual(new_key.path, PATH) + self.assertEqual(new_key.path, path) self.assertEqual(list(result), ['foo']) self.assertEqual(result['foo'], 'Foo') - cw = client._connection._lookup_cw - self.assertEqual(len(cw), 1) - _, _, _, transaction_id = cw[0] - self.assertEqual(transaction_id, TXN_ID) + read_options = datastore_pb2.ReadOptions(transaction=txn_id) + ds_api.lookup.assert_called_once_with( + self.PROJECT, read_options, [key.to_protobuf()]) def test_get_multi_hit_multiple_keys_same_project(self): + from google.cloud.proto.datastore.v1 import datastore_pb2 from google.cloud.datastore.key import Key - KIND = 'Kind' - ID1 = 1234 - ID2 = 2345 + kind = 'Kind' + id1 = 1234 + id2 = 2345 # Make a found entity pb to be returned from mock backend. - entity_pb1 = _make_entity_pb(self.PROJECT, KIND, ID1) - entity_pb2 = _make_entity_pb(self.PROJECT, KIND, ID2) + entity_pb1 = _make_entity_pb(self.PROJECT, kind, id1) + entity_pb2 = _make_entity_pb(self.PROJECT, kind, id2) # Make a connection to return the entity pbs. creds = _make_credentials() client = self._make_one(credentials=creds) - client._connection._add_lookup_result([entity_pb1, entity_pb2]) + lookup_response = _make_lookup_response( + results=[entity_pb1, entity_pb2]) + ds_api = _make_datastore_api(lookup_response=lookup_response) + client._datastore_api_internal = ds_api - key1 = Key(KIND, ID1, project=self.PROJECT) - key2 = Key(KIND, ID2, project=self.PROJECT) + key1 = Key(kind, id1, project=self.PROJECT) + key2 = Key(kind, id2, project=self.PROJECT) retrieved1, retrieved2 = client.get_multi([key1, key2]) # Check values match. @@ -567,6 +586,11 @@ def test_get_multi_hit_multiple_keys_same_project(self): self.assertEqual(retrieved2.key.path, key2.path) self.assertEqual(dict(retrieved2), {}) + read_options = datastore_pb2.ReadOptions() + ds_api.lookup.assert_called_once_with( + self.PROJECT, read_options, + [key1.to_protobuf(), key2.to_protobuf()]) + def test_get_multi_hit_multiple_keys_different_project(self): from google.cloud.datastore.key import Key @@ -588,18 +612,20 @@ def test_get_multi_hit_multiple_keys_different_project(self): def test_get_multi_max_loops(self): from google.cloud.datastore.key import Key - KIND = 'Kind' - ID = 1234 + kind = 'Kind' + id_ = 1234 # Make a found entity pb to be returned from mock backend. - entity_pb = _make_entity_pb(self.PROJECT, KIND, ID, 'foo', 'Foo') + entity_pb = _make_entity_pb(self.PROJECT, kind, id_, 'foo', 'Foo') # Make a connection to return the entity pb. creds = _make_credentials() client = self._make_one(credentials=creds) - client._connection._add_lookup_result([entity_pb]) + lookup_response = _make_lookup_response(results=[entity_pb]) + ds_api = _make_datastore_api(lookup_response=lookup_response) + client._datastore_api_internal = ds_api - key = Key(KIND, ID, project=self.PROJECT) + key = Key(kind, id_, project=self.PROJECT) deferred = [] missing = [] @@ -614,6 +640,7 @@ def test_get_multi_max_loops(self): self.assertEqual(result, []) self.assertEqual(missing, []) self.assertEqual(deferred, []) + ds_api.lookup.assert_not_called() def test_put(self): _called_with = [] @@ -987,32 +1014,39 @@ def test_query_w_namespace_collision(self): client, project=self.PROJECT, namespace=namespace2, kind=kind) -class _MockConnection(object): +class Test__get_read_options(unittest.TestCase): + + def _call_fut(self, eventual, transaction_id): + from google.cloud.datastore.client import _get_read_options + + return _get_read_options(eventual, transaction_id) + + def test_eventual_w_transaction(self): + with self.assertRaises(ValueError): + self._call_fut(True, b'123') + + def test_eventual_wo_transaction(self): + from google.cloud.proto.datastore.v1 import datastore_pb2 - def __init__(self, credentials=None, http=None): - self.credentials = credentials - self.http = http - self._lookup_cw = [] - self._lookup = [] + read_options = self._call_fut(True, None) + expected = datastore_pb2.ReadOptions( + read_consistency=datastore_pb2.ReadOptions.EVENTUAL) + self.assertEqual(read_options, expected) - def _add_lookup_result(self, results=(), missing=(), deferred=()): - self._lookup.append((list(results), list(missing), list(deferred))) + def test_default_w_transaction(self): + from google.cloud.proto.datastore.v1 import datastore_pb2 - def lookup(self, project, key_pbs, eventual=False, transaction_id=None): - self._lookup_cw.append((project, key_pbs, eventual, transaction_id)) - triple, self._lookup = self._lookup[0], self._lookup[1:] - results, missing, deferred = triple + txn_id = b'123abc-easy-as' + read_options = self._call_fut(False, txn_id) + expected = datastore_pb2.ReadOptions(transaction=txn_id) + self.assertEqual(read_options, expected) - entity_results_found = [ - mock.Mock(entity=result, spec=['entity']) for result in results] - entity_results_missing = [ - mock.Mock(entity=missing_entity, spec=['entity']) - for missing_entity in missing] - return mock.Mock( - found=entity_results_found, - missing=entity_results_missing, - deferred=deferred, - spec=['found', 'missing', 'deferred']) + def test_default_wo_transaction(self): + from google.cloud.proto.datastore.v1 import datastore_pb2 + + read_options = self._call_fut(False, None) + expected = datastore_pb2.ReadOptions() + self.assertEqual(read_options, expected) class _NoCommitBatch(object): @@ -1139,7 +1173,26 @@ def _make_commit_response(*keys): return datastore_pb2.CommitResponse(mutation_results=mutation_results) -def _make_datastore_api(*keys): +def _make_lookup_response(results=(), missing=(), deferred=()): + entity_results_found = [ + mock.Mock(entity=result, spec=['entity']) for result in results] + entity_results_missing = [ + mock.Mock(entity=missing_entity, spec=['entity']) + for missing_entity in missing] + return mock.Mock( + found=entity_results_found, + missing=entity_results_missing, + deferred=deferred, + spec=['found', 'missing', 'deferred']) + + +def _make_datastore_api(*keys, **kwargs): commit_method = mock.Mock( return_value=_make_commit_response(*keys), spec=[]) - return mock.Mock(commit=commit_method, spec=['commit']) + lookup_response = kwargs.pop( + 'lookup_response', _make_lookup_response()) + lookup_method = mock.Mock( + return_value=lookup_response, spec=[]) + return mock.Mock( + commit=commit_method, lookup=lookup_method, + spec=['commit', 'lookup'])