From 0109bbd91e1d54b06fdb11eb8c2d6046e562b0c8 Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Thu, 16 Feb 2017 19:17:40 -0800 Subject: [PATCH] Moving datastore GAX/gRPC helpers out of _http and into dedicated module. --- datastore/google/cloud/datastore/_gax.py | 197 ++++++++++++++ datastore/google/cloud/datastore/_http.py | 183 +------------ datastore/unit_tests/test__gax.py | 310 ++++++++++++++++++++++ datastore/unit_tests/test__http.py | 286 -------------------- 4 files changed, 510 insertions(+), 466 deletions(-) create mode 100644 datastore/google/cloud/datastore/_gax.py create mode 100644 datastore/unit_tests/test__gax.py diff --git a/datastore/google/cloud/datastore/_gax.py b/datastore/google/cloud/datastore/_gax.py new file mode 100644 index 000000000000..cd2739bd10ce --- /dev/null +++ b/datastore/google/cloud/datastore/_gax.py @@ -0,0 +1,197 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helpers for making API requests via GAX / gRPC.""" + + +import contextlib + +from grpc import StatusCode + +from google.cloud._helpers import make_insecure_stub +from google.cloud._helpers import make_secure_stub +from google.cloud import exceptions + +from google.cloud.grpc.datastore.v1 import datastore_pb2_grpc + + +_GRPC_ERROR_MAPPING = { + StatusCode.UNKNOWN: exceptions.InternalServerError, + StatusCode.INVALID_ARGUMENT: exceptions.BadRequest, + StatusCode.DEADLINE_EXCEEDED: exceptions.GatewayTimeout, + StatusCode.NOT_FOUND: exceptions.NotFound, + StatusCode.ALREADY_EXISTS: exceptions.Conflict, + StatusCode.PERMISSION_DENIED: exceptions.Forbidden, + StatusCode.UNAUTHENTICATED: exceptions.Unauthorized, + StatusCode.RESOURCE_EXHAUSTED: exceptions.TooManyRequests, + StatusCode.FAILED_PRECONDITION: exceptions.PreconditionFailed, + StatusCode.ABORTED: exceptions.Conflict, + StatusCode.OUT_OF_RANGE: exceptions.BadRequest, + StatusCode.UNIMPLEMENTED: exceptions.MethodNotImplemented, + StatusCode.INTERNAL: exceptions.InternalServerError, + StatusCode.UNAVAILABLE: exceptions.ServiceUnavailable, + StatusCode.DATA_LOSS: exceptions.InternalServerError, +} + + +@contextlib.contextmanager +def _grpc_catch_rendezvous(): + """Remap gRPC exceptions that happen in context. + + .. _code.proto: https://github.com/googleapis/googleapis/blob/\ + master/google/rpc/code.proto + + Remaps gRPC exceptions to the classes defined in + :mod:`~google.cloud.exceptions` (according to the description + in `code.proto`_). + """ + try: + yield + except exceptions.GrpcRendezvous as exc: + error_code = exc.code() + error_class = _GRPC_ERROR_MAPPING.get(error_code) + if error_class is None: + raise + else: + raise error_class(exc.details()) + + +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. + + :type secure: bool + :param secure: Flag indicating if a secure stub connection is needed. + """ + + def __init__(self, connection, secure): + if secure: + self._stub = make_secure_stub(connection.credentials, + connection.USER_AGENT, + datastore_pb2_grpc.DatastoreStub, + connection.host) + else: + self._stub = make_insecure_stub(datastore_pb2_grpc.DatastoreStub, + connection.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) + + def run_query(self, project, request_pb): + """Perform a ``runQuery`` 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.RunQueryRequest` + :param request_pb: The request protobuf object. + + :rtype: :class:`.datastore_pb2.RunQueryResponse` + :returns: The returned protobuf response object. + """ + request_pb.project_id = project + with _grpc_catch_rendezvous(): + return self._stub.RunQuery(request_pb) + + def begin_transaction(self, project, request_pb): + """Perform a ``beginTransaction`` 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.BeginTransactionRequest` + :param request_pb: The request protobuf object. + + :rtype: :class:`.datastore_pb2.BeginTransactionResponse` + :returns: The returned protobuf response object. + """ + request_pb.project_id = project + with _grpc_catch_rendezvous(): + return self._stub.BeginTransaction(request_pb) + + def commit(self, project, request_pb): + """Perform a ``commit`` 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.CommitRequest` + :param request_pb: The request protobuf object. + + :rtype: :class:`.datastore_pb2.CommitResponse` + :returns: The returned protobuf response object. + """ + request_pb.project_id = project + with _grpc_catch_rendezvous(): + return self._stub.Commit(request_pb) + + def rollback(self, project, request_pb): + """Perform a ``rollback`` 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.RollbackRequest` + :param request_pb: The request protobuf object. + + :rtype: :class:`.datastore_pb2.RollbackResponse` + :returns: The returned protobuf response object. + """ + request_pb.project_id = project + with _grpc_catch_rendezvous(): + return self._stub.Rollback(request_pb) + + def allocate_ids(self, project, request_pb): + """Perform an ``allocateIds`` 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.AllocateIdsRequest` + :param request_pb: The request protobuf object. + + :rtype: :class:`.datastore_pb2.AllocateIdsResponse` + :returns: The returned protobuf response object. + """ + request_pb.project_id = project + with _grpc_catch_rendezvous(): + return self._stub.AllocateIds(request_pb) diff --git a/datastore/google/cloud/datastore/_http.py b/datastore/google/cloud/datastore/_http.py index 4c01f60c84d2..d1defedfe632 100644 --- a/datastore/google/cloud/datastore/_http.py +++ b/datastore/google/cloud/datastore/_http.py @@ -14,46 +14,21 @@ """Connections to Google Cloud Datastore API servers.""" -import contextlib import os from google.rpc import status_pb2 -from google.cloud._helpers import make_insecure_stub -from google.cloud._helpers import make_secure_stub from google.cloud import _http as connection_module from google.cloud.environment_vars import DISABLE_GRPC from google.cloud.environment_vars import GCD_HOST from google.cloud import exceptions from google.cloud.grpc.datastore.v1 import datastore_pb2 as _datastore_pb2 try: - from grpc import StatusCode - from google.cloud.grpc.datastore.v1 import datastore_pb2_grpc + from google.cloud.datastore._gax import _DatastoreAPIOverGRPC + _HAVE_GRPC = True except ImportError: # pragma: NO COVER - _GRPC_ERROR_MAPPING = {} + _DatastoreAPIOverGRPC = None _HAVE_GRPC = False - datastore_pb2_grpc = None - StatusCode = None -else: - # NOTE: We don't include OK -> 200 or CANCELLED -> 499 - _GRPC_ERROR_MAPPING = { - StatusCode.UNKNOWN: exceptions.InternalServerError, - StatusCode.INVALID_ARGUMENT: exceptions.BadRequest, - StatusCode.DEADLINE_EXCEEDED: exceptions.GatewayTimeout, - StatusCode.NOT_FOUND: exceptions.NotFound, - StatusCode.ALREADY_EXISTS: exceptions.Conflict, - StatusCode.PERMISSION_DENIED: exceptions.Forbidden, - StatusCode.UNAUTHENTICATED: exceptions.Unauthorized, - StatusCode.RESOURCE_EXHAUSTED: exceptions.TooManyRequests, - StatusCode.FAILED_PRECONDITION: exceptions.PreconditionFailed, - StatusCode.ABORTED: exceptions.Conflict, - StatusCode.OUT_OF_RANGE: exceptions.BadRequest, - StatusCode.UNIMPLEMENTED: exceptions.MethodNotImplemented, - StatusCode.INTERNAL: exceptions.InternalServerError, - StatusCode.UNAVAILABLE: exceptions.ServiceUnavailable, - StatusCode.DATA_LOSS: exceptions.InternalServerError, - } - _HAVE_GRPC = True DATASTORE_API_HOST = 'datastore.googleapis.com' @@ -238,158 +213,6 @@ def allocate_ids(self, project, request_pb): _datastore_pb2.AllocateIdsResponse) -@contextlib.contextmanager -def _grpc_catch_rendezvous(): - """Re-map gRPC exceptions that happen in context. - - .. _code.proto: https://github.com/googleapis/googleapis/blob/\ - master/google/rpc/code.proto - - Remaps gRPC exceptions to the classes defined in - :mod:`~google.cloud.exceptions` (according to the description - in `code.proto`_). - """ - try: - yield - except exceptions.GrpcRendezvous as exc: - error_code = exc.code() - error_class = _GRPC_ERROR_MAPPING.get(error_code) - if error_class is None: - raise - else: - raise error_class(exc.details()) - - -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. - - :type secure: bool - :param secure: Flag indicating if a secure stub connection is needed. - """ - - def __init__(self, connection, secure): - if secure: - self._stub = make_secure_stub(connection.credentials, - connection.USER_AGENT, - datastore_pb2_grpc.DatastoreStub, - connection.host) - else: - self._stub = make_insecure_stub(datastore_pb2_grpc.DatastoreStub, - connection.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) - - def run_query(self, project, request_pb): - """Perform a ``runQuery`` 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.RunQueryRequest` - :param request_pb: The request protobuf object. - - :rtype: :class:`.datastore_pb2.RunQueryResponse` - :returns: The returned protobuf response object. - """ - request_pb.project_id = project - with _grpc_catch_rendezvous(): - return self._stub.RunQuery(request_pb) - - def begin_transaction(self, project, request_pb): - """Perform a ``beginTransaction`` 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.BeginTransactionRequest` - :param request_pb: The request protobuf object. - - :rtype: :class:`.datastore_pb2.BeginTransactionResponse` - :returns: The returned protobuf response object. - """ - request_pb.project_id = project - with _grpc_catch_rendezvous(): - return self._stub.BeginTransaction(request_pb) - - def commit(self, project, request_pb): - """Perform a ``commit`` 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.CommitRequest` - :param request_pb: The request protobuf object. - - :rtype: :class:`.datastore_pb2.CommitResponse` - :returns: The returned protobuf response object. - """ - request_pb.project_id = project - with _grpc_catch_rendezvous(): - return self._stub.Commit(request_pb) - - def rollback(self, project, request_pb): - """Perform a ``rollback`` 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.RollbackRequest` - :param request_pb: The request protobuf object. - - :rtype: :class:`.datastore_pb2.RollbackResponse` - :returns: The returned protobuf response object. - """ - request_pb.project_id = project - with _grpc_catch_rendezvous(): - return self._stub.Rollback(request_pb) - - def allocate_ids(self, project, request_pb): - """Perform an ``allocateIds`` 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.AllocateIdsRequest` - :param request_pb: The request protobuf object. - - :rtype: :class:`.datastore_pb2.AllocateIdsResponse` - :returns: The returned protobuf response object. - """ - request_pb.project_id = project - with _grpc_catch_rendezvous(): - return self._stub.AllocateIds(request_pb) - - class Connection(connection_module.Connection): """A connection to the Google Cloud Datastore via the Protobuf API. diff --git a/datastore/unit_tests/test__gax.py b/datastore/unit_tests/test__gax.py new file mode 100644 index 000000000000..4f49483207d7 --- /dev/null +++ b/datastore/unit_tests/test__gax.py @@ -0,0 +1,310 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest + +import mock + +from google.cloud.datastore._http import _HAVE_GRPC + + +USER_AGENT = 'you-sir-age-int' + + +@unittest.skipUnless(_HAVE_GRPC, 'No gRPC') +class Test__grpc_catch_rendezvous(unittest.TestCase): + + def _call_fut(self): + from google.cloud.datastore._gax import _grpc_catch_rendezvous + + return _grpc_catch_rendezvous() + + @staticmethod + def _fake_method(exc, result=None): + if exc is None: + return result + else: + raise exc + + def test_success(self): + expected = object() + with self._call_fut(): + result = self._fake_method(None, expected) + self.assertIs(result, expected) + + def test_failure_aborted(self): + from grpc import StatusCode + from grpc._channel import _RPCState + from google.cloud.exceptions import Conflict + from google.cloud.exceptions import GrpcRendezvous + + details = 'Bad things.' + exc_state = _RPCState((), None, None, StatusCode.ABORTED, details) + exc = GrpcRendezvous(exc_state, None, None, None) + with self.assertRaises(Conflict): + with self._call_fut(): + self._fake_method(exc) + + def test_failure_invalid_argument(self): + from grpc import StatusCode + from grpc._channel import _RPCState + from google.cloud.exceptions import BadRequest + from google.cloud.exceptions import GrpcRendezvous + + details = ('Cannot have inequality filters on multiple ' + 'properties: [created, priority]') + exc_state = _RPCState((), None, None, + StatusCode.INVALID_ARGUMENT, details) + exc = GrpcRendezvous(exc_state, None, None, None) + with self.assertRaises(BadRequest): + with self._call_fut(): + self._fake_method(exc) + + def test_failure_cancelled(self): + from grpc import StatusCode + from grpc._channel import _RPCState + from google.cloud.exceptions import GrpcRendezvous + + exc_state = _RPCState((), None, None, StatusCode.CANCELLED, None) + exc = GrpcRendezvous(exc_state, None, None, None) + with self.assertRaises(GrpcRendezvous): + with self._call_fut(): + self._fake_method(exc) + + def test_commit_failure_non_grpc_err(self): + exc = RuntimeError('Not a gRPC error') + with self.assertRaises(RuntimeError): + with self._call_fut(): + 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, mock_args=None): + if connection is None: + connection = mock.Mock( + credentials=object(), + host='CURR_HOST', + USER_AGENT=USER_AGENT, + spec=['credentials', 'host', 'USER_AGENT'], + ) + + if mock_args is None: + mock_args = [] + + def mock_make_stub(*args): + mock_args.append(args) + return stub + + if secure: + patch = mock.patch( + 'google.cloud.datastore._gax.make_secure_stub', + new=mock_make_stub) + else: + patch = mock.patch( + 'google.cloud.datastore._gax.make_insecure_stub', + new=mock_make_stub) + + with patch: + return self._get_target_class()(connection, secure) + + def test_constructor(self): + from google.cloud.grpc.datastore.v1 import datastore_pb2_grpc + + conn = mock.Mock( + credentials=object(), + host='CURR_HOST', + USER_AGENT=USER_AGENT, + spec=['credentials', 'host', 'USER_AGENT'], + ) + + stub = _GRPCStub() + mock_args = [] + datastore_api = self._make_one(stub, connection=conn, + mock_args=mock_args) + self.assertIs(datastore_api._stub, stub) + + self.assertEqual(mock_args, [( + conn.credentials, + conn.USER_AGENT, + datastore_pb2_grpc.DatastoreStub, + conn.host, + )]) + + def test_constructor_insecure(self): + from google.cloud.grpc.datastore.v1 import datastore_pb2_grpc + + conn = mock.Mock( + credentials=object(), + host='CURR_HOST:1234', + spec=['credentials', 'host'], + ) + + stub = _GRPCStub() + mock_args = [] + datastore_api = self._make_one(stub, connection=conn, + secure=False, + mock_args=mock_args) + self.assertIs(datastore_api._stub, stub) + + self.assertEqual(mock_args, [( + datastore_pb2_grpc.DatastoreStub, + conn.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')]) + + def test_run_query(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.run_query(project, request_pb) + self.assertIs(result, return_val) + self.assertEqual(request_pb.project_id, project) + self.assertEqual(stub.method_calls, + [(request_pb, 'RunQuery')]) + + def _run_query_failure_helper(self, exc, err_class): + stub = _GRPCStub(side_effect=exc) + datastore_api = self._make_one(stub=stub) + + request_pb = mock.Mock(project_id=None, spec=['project_id']) + project = 'PROJECT' + with self.assertRaises(err_class): + datastore_api.run_query(project, request_pb) + + self.assertEqual(request_pb.project_id, project) + self.assertEqual(stub.method_calls, + [(request_pb, 'RunQuery')]) + + @unittest.skipUnless(_HAVE_GRPC, 'No gRPC') + def test_run_query_invalid_argument(self): + from grpc import StatusCode + from grpc._channel import _RPCState + from google.cloud.exceptions import BadRequest + from google.cloud.exceptions import GrpcRendezvous + + details = ('Cannot have inequality filters on multiple ' + 'properties: [created, priority]') + exc_state = _RPCState((), None, None, + StatusCode.INVALID_ARGUMENT, details) + exc = GrpcRendezvous(exc_state, None, None, None) + self._run_query_failure_helper(exc, BadRequest) + + def test_begin_transaction(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.begin_transaction(project, request_pb) + self.assertIs(result, return_val) + self.assertEqual(request_pb.project_id, project) + self.assertEqual( + stub.method_calls, + [(request_pb, 'BeginTransaction')]) + + def test_commit_success(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.commit(project, request_pb) + self.assertIs(result, return_val) + self.assertEqual(request_pb.project_id, project) + self.assertEqual(stub.method_calls, + [(request_pb, 'Commit')]) + + def test_rollback(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.rollback(project, request_pb) + self.assertIs(result, return_val) + self.assertEqual(request_pb.project_id, project) + self.assertEqual(stub.method_calls, + [(request_pb, 'Rollback')]) + + def test_allocate_ids(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.allocate_ids(project, request_pb) + self.assertIs(result, return_val) + self.assertEqual(request_pb.project_id, project) + self.assertEqual( + stub.method_calls, + [(request_pb, 'AllocateIds')]) + + +class _GRPCStub(object): + + def __init__(self, return_val=None, side_effect=Exception): + self.return_val = return_val + self.side_effect = side_effect + self.method_calls = [] + + def _method(self, request_pb, name): + self.method_calls.append((request_pb, name)) + if self.side_effect is Exception: + return self.return_val + else: + raise self.side_effect + + def Lookup(self, request_pb): + return self._method(request_pb, 'Lookup') + + def RunQuery(self, request_pb): + return self._method(request_pb, 'RunQuery') + + def BeginTransaction(self, request_pb): + return self._method(request_pb, 'BeginTransaction') + + def Commit(self, request_pb): + return self._method(request_pb, 'Commit') + + def Rollback(self, request_pb): + return self._method(request_pb, 'Rollback') + + def AllocateIds(self, request_pb): + return self._method(request_pb, 'AllocateIds') diff --git a/datastore/unit_tests/test__http.py b/datastore/unit_tests/test__http.py index 7e47e7c2f65a..bd2c7ba98644 100644 --- a/datastore/unit_tests/test__http.py +++ b/datastore/unit_tests/test__http.py @@ -16,8 +16,6 @@ import mock -from google.cloud.datastore._http import _HAVE_GRPC - class Test_DatastoreAPIOverHttp(unittest.TestCase): @@ -110,253 +108,6 @@ def test__request_not_200(self): [{'method': METHOD, 'project': PROJECT}]) -@unittest.skipUnless(_HAVE_GRPC, 'No gRPC') -class Test__grpc_catch_rendezvous(unittest.TestCase): - - def _call_fut(self): - from google.cloud.datastore._http import _grpc_catch_rendezvous - - return _grpc_catch_rendezvous() - - @staticmethod - def _fake_method(exc, result=None): - if exc is None: - return result - else: - raise exc - - def test_success(self): - expected = object() - with self._call_fut(): - result = self._fake_method(None, expected) - self.assertIs(result, expected) - - def test_failure_aborted(self): - from grpc import StatusCode - from grpc._channel import _RPCState - from google.cloud.exceptions import Conflict - from google.cloud.exceptions import GrpcRendezvous - - details = 'Bad things.' - exc_state = _RPCState((), None, None, StatusCode.ABORTED, details) - exc = GrpcRendezvous(exc_state, None, None, None) - with self.assertRaises(Conflict): - with self._call_fut(): - self._fake_method(exc) - - def test_failure_invalid_argument(self): - from grpc import StatusCode - from grpc._channel import _RPCState - from google.cloud.exceptions import BadRequest - from google.cloud.exceptions import GrpcRendezvous - - details = ('Cannot have inequality filters on multiple ' - 'properties: [created, priority]') - exc_state = _RPCState((), None, None, - StatusCode.INVALID_ARGUMENT, details) - exc = GrpcRendezvous(exc_state, None, None, None) - with self.assertRaises(BadRequest): - with self._call_fut(): - self._fake_method(exc) - - def test_failure_cancelled(self): - from grpc import StatusCode - from grpc._channel import _RPCState - from google.cloud.exceptions import GrpcRendezvous - - exc_state = _RPCState((), None, None, StatusCode.CANCELLED, None) - exc = GrpcRendezvous(exc_state, None, None, None) - with self.assertRaises(GrpcRendezvous): - with self._call_fut(): - self._fake_method(exc) - - def test_commit_failure_non_grpc_err(self): - exc = RuntimeError('Not a gRPC error') - with self.assertRaises(RuntimeError): - with self._call_fut(): - self._fake_method(exc) - - -class Test_DatastoreAPIOverGRPC(unittest.TestCase): - - @staticmethod - def _get_target_class(): - from google.cloud.datastore._http import _DatastoreAPIOverGRPC - - return _DatastoreAPIOverGRPC - - def _make_one(self, stub, connection=None, secure=True, mock_args=None): - if connection is None: - connection = _Connection(None) - connection.credentials = object() - connection.host = 'CURR_HOST' - - if mock_args is None: - mock_args = [] - - def mock_make_stub(*args): - mock_args.append(args) - return stub - - if secure: - patch = mock.patch( - 'google.cloud.datastore._http.make_secure_stub', - new=mock_make_stub) - else: - patch = mock.patch( - 'google.cloud.datastore._http.make_insecure_stub', - new=mock_make_stub) - - with patch: - return self._get_target_class()(connection, secure) - - def test_constructor(self): - from google.cloud.datastore import _http as MUT - - conn = _Connection(None) - conn.credentials = object() - conn.host = 'CURR_HOST' - - stub = _GRPCStub() - mock_args = [] - datastore_api = self._make_one(stub, connection=conn, - mock_args=mock_args) - self.assertIs(datastore_api._stub, stub) - - self.assertEqual(mock_args, [( - conn.credentials, - conn.USER_AGENT, - MUT.datastore_pb2_grpc.DatastoreStub, - conn.host, - )]) - - def test_constructor_insecure(self): - from google.cloud.datastore import _http as MUT - - conn = _Connection(None) - conn.credentials = object() - conn.host = 'CURR_HOST:1234' - - stub = _GRPCStub() - mock_args = [] - datastore_api = self._make_one(stub, connection=conn, - secure=False, - mock_args=mock_args) - self.assertIs(datastore_api._stub, stub) - - self.assertEqual(mock_args, [( - MUT.datastore_pb2_grpc.DatastoreStub, - conn.host, - )]) - - def test_lookup(self): - return_val = object() - stub = _GRPCStub(return_val) - datastore_api = self._make_one(stub=stub) - - request_pb = _RequestPB() - 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')]) - - def test_run_query(self): - return_val = object() - stub = _GRPCStub(return_val) - datastore_api = self._make_one(stub=stub) - - request_pb = _RequestPB() - project = 'PROJECT' - result = datastore_api.run_query(project, request_pb) - self.assertIs(result, return_val) - self.assertEqual(request_pb.project_id, project) - self.assertEqual(stub.method_calls, - [(request_pb, 'RunQuery')]) - - def _run_query_failure_helper(self, exc, err_class): - stub = _GRPCStub(side_effect=exc) - datastore_api = self._make_one(stub=stub) - - request_pb = _RequestPB() - project = 'PROJECT' - with self.assertRaises(err_class): - datastore_api.run_query(project, request_pb) - - self.assertEqual(request_pb.project_id, project) - self.assertEqual(stub.method_calls, - [(request_pb, 'RunQuery')]) - - @unittest.skipUnless(_HAVE_GRPC, 'No gRPC') - def test_run_query_invalid_argument(self): - from grpc import StatusCode - from grpc._channel import _RPCState - from google.cloud.exceptions import BadRequest - from google.cloud.exceptions import GrpcRendezvous - - details = ('Cannot have inequality filters on multiple ' - 'properties: [created, priority]') - exc_state = _RPCState((), None, None, - StatusCode.INVALID_ARGUMENT, details) - exc = GrpcRendezvous(exc_state, None, None, None) - self._run_query_failure_helper(exc, BadRequest) - - def test_begin_transaction(self): - return_val = object() - stub = _GRPCStub(return_val) - datastore_api = self._make_one(stub=stub) - - request_pb = _RequestPB() - project = 'PROJECT' - result = datastore_api.begin_transaction(project, request_pb) - self.assertIs(result, return_val) - self.assertEqual(request_pb.project_id, project) - self.assertEqual( - stub.method_calls, - [(request_pb, 'BeginTransaction')]) - - def test_commit_success(self): - return_val = object() - stub = _GRPCStub(return_val) - datastore_api = self._make_one(stub=stub) - - request_pb = _RequestPB() - project = 'PROJECT' - result = datastore_api.commit(project, request_pb) - self.assertIs(result, return_val) - self.assertEqual(request_pb.project_id, project) - self.assertEqual(stub.method_calls, - [(request_pb, 'Commit')]) - - def test_rollback(self): - return_val = object() - stub = _GRPCStub(return_val) - datastore_api = self._make_one(stub=stub) - - request_pb = _RequestPB() - project = 'PROJECT' - result = datastore_api.rollback(project, request_pb) - self.assertIs(result, return_val) - self.assertEqual(request_pb.project_id, project) - self.assertEqual(stub.method_calls, - [(request_pb, 'Rollback')]) - - def test_allocate_ids(self): - return_val = object() - stub = _GRPCStub(return_val) - datastore_api = self._make_one(stub=stub) - - request_pb = _RequestPB() - project = 'PROJECT' - result = datastore_api.allocate_ids(project, request_pb) - self.assertIs(result, return_val) - self.assertEqual(request_pb.project_id, project) - self.assertEqual( - stub.method_calls, - [(request_pb, 'AllocateIds')]) - - class TestConnection(unittest.TestCase): @staticmethod @@ -1147,40 +898,3 @@ def __init__(self, api_url): def build_api_url(self, **kwargs): self.build_kwargs.append(kwargs) return self.api_url - - -class _GRPCStub(object): - - def __init__(self, return_val=None, side_effect=Exception): - self.return_val = return_val - self.side_effect = side_effect - self.method_calls = [] - - def _method(self, request_pb, name): - self.method_calls.append((request_pb, name)) - if self.side_effect is Exception: - return self.return_val - else: - raise self.side_effect - - def Lookup(self, request_pb): - return self._method(request_pb, 'Lookup') - - def RunQuery(self, request_pb): - return self._method(request_pb, 'RunQuery') - - def BeginTransaction(self, request_pb): - return self._method(request_pb, 'BeginTransaction') - - def Commit(self, request_pb): - return self._method(request_pb, 'Commit') - - def Rollback(self, request_pb): - return self._method(request_pb, 'Rollback') - - def AllocateIds(self, request_pb): - return self._method(request_pb, 'AllocateIds') - - -class _RequestPB(object): - project_id = None