Skip to content

Commit fe94da6

Browse files
committed
Move 'datastore.exceptions_catch_remap_gax_error' to 'core.exceptions'.
Toward #3175.
1 parent 963d997 commit fe94da6

5 files changed

Lines changed: 120 additions & 113 deletions

File tree

core/google/cloud/exceptions.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,26 @@
2020
# Avoid the grpc and google.cloud.grpc collision.
2121
from __future__ import absolute_import
2222

23+
import contextlib
2324
import copy
25+
import sys
2426

2527
import six
2628

27-
from google.cloud._helpers import _to_bytes
28-
2929
try:
30+
from google.gax.errors import GaxError
31+
from google.gax.grpc import exc_to_code
32+
from grpc import StatusCode
3033
from grpc._channel import _Rendezvous
3134
except ImportError: # pragma: NO COVER
35+
_HAVE_GRPC = False
3236
_Rendezvous = None
37+
else:
38+
_HAVE_GRPC = True
39+
40+
from google.cloud._helpers import _to_bytes
41+
42+
_HTTP_CODE_TO_EXCEPTION = {} # populated at end of module
3343

3444
_HTTP_CODE_TO_EXCEPTION = {} # populated at end of module
3545

@@ -244,8 +254,50 @@ def _walk_subclasses(klass):
244254
yield subsub
245255

246256

257+
@contextlib.contextmanager
258+
def _catch_remap_gax_error():
259+
"""Remap GAX exceptions that happen in context.
260+
261+
.. _code.proto: https://github.com/googleapis/googleapis/blob/\
262+
master/google/rpc/code.proto
263+
264+
Remaps gRPC exceptions to the classes defined in
265+
:mod:`~google.cloud.exceptions` (according to the description
266+
in `code.proto`_).
267+
"""
268+
try:
269+
yield
270+
except GaxError as exc:
271+
error_code = exc_to_code(exc.cause)
272+
error_class = _GRPC_ERROR_MAPPING.get(error_code)
273+
if error_class is None:
274+
raise
275+
else:
276+
new_exc = error_class(exc.cause.details())
277+
six.reraise(error_class, new_exc, sys.exc_info()[2])
278+
279+
247280
# Build the code->exception class mapping.
248281
for _eklass in _walk_subclasses(GoogleCloudError):
249282
code = getattr(_eklass, 'code', None)
250283
if code is not None:
251284
_HTTP_CODE_TO_EXCEPTION[code] = _eklass
285+
286+
if _HAVE_GRPC:
287+
_GRPC_ERROR_MAPPING = {
288+
StatusCode.UNKNOWN: InternalServerError,
289+
StatusCode.INVALID_ARGUMENT: BadRequest,
290+
StatusCode.DEADLINE_EXCEEDED: GatewayTimeout,
291+
StatusCode.NOT_FOUND: NotFound,
292+
StatusCode.ALREADY_EXISTS: Conflict,
293+
StatusCode.PERMISSION_DENIED: Forbidden,
294+
StatusCode.UNAUTHENTICATED: Unauthorized,
295+
StatusCode.RESOURCE_EXHAUSTED: TooManyRequests,
296+
StatusCode.FAILED_PRECONDITION: PreconditionFailed,
297+
StatusCode.ABORTED: Conflict,
298+
StatusCode.OUT_OF_RANGE: BadRequest,
299+
StatusCode.UNIMPLEMENTED: MethodNotImplemented,
300+
StatusCode.INTERNAL: InternalServerError,
301+
StatusCode.UNAVAILABLE: ServiceUnavailable,
302+
StatusCode.DATA_LOSS: InternalServerError,
303+
}

core/nox.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def unit_tests(session, python_version):
3535
'pytest',
3636
'pytest-cov',
3737
'grpcio >= 1.0.2',
38+
'google-gax >= 0.15, < 0.16.dev',
3839
)
3940
session.install('-e', '.')
4041

core/tests/unit/test_exceptions.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import json
16+
import unittest
1617

1718
import requests
1819
from six.moves import http_client
@@ -125,3 +126,66 @@ def test_from_http_response_bad_json_content():
125126
assert isinstance(exception, exceptions.NotFound)
126127
assert exception.code == http_client.NOT_FOUND
127128
assert exception.message == 'POST https://example.com/: unknown error'
129+
130+
131+
@unittest.skipUnless(exceptions._HAVE_GRPC, 'No gRPC')
132+
class Test__catch_remap_gax_error(unittest.TestCase):
133+
134+
def _call_fut(self):
135+
from google.cloud.exceptions import _catch_remap_gax_error
136+
137+
return _catch_remap_gax_error()
138+
139+
@staticmethod
140+
def _fake_method(exc, result=None):
141+
if exc is None:
142+
return result
143+
else:
144+
raise exc
145+
146+
@staticmethod
147+
def _make_rendezvous(status_code, details):
148+
from grpc._channel import _RPCState
149+
from google.cloud.exceptions import GrpcRendezvous
150+
151+
exc_state = _RPCState((), None, None, status_code, details)
152+
return GrpcRendezvous(exc_state, None, None, None)
153+
154+
def test_success(self):
155+
expected = object()
156+
with self._call_fut():
157+
result = self._fake_method(None, expected)
158+
self.assertIs(result, expected)
159+
160+
def test_non_grpc_err(self):
161+
exc = RuntimeError('Not a gRPC error')
162+
with self.assertRaises(RuntimeError):
163+
with self._call_fut():
164+
self._fake_method(exc)
165+
166+
def test_gax_error(self):
167+
from google.gax.errors import GaxError
168+
from grpc import StatusCode
169+
from google.cloud.exceptions import Forbidden
170+
171+
# First, create low-level GrpcRendezvous exception.
172+
details = 'Some error details.'
173+
cause = self._make_rendezvous(StatusCode.PERMISSION_DENIED, details)
174+
# Then put it into a high-level GaxError.
175+
msg = 'GAX Error content.'
176+
exc = GaxError(msg, cause=cause)
177+
178+
with self.assertRaises(Forbidden):
179+
with self._call_fut():
180+
self._fake_method(exc)
181+
182+
def test_gax_error_not_mapped(self):
183+
from google.gax.errors import GaxError
184+
from grpc import StatusCode
185+
186+
cause = self._make_rendezvous(StatusCode.CANCELLED, None)
187+
exc = GaxError(None, cause=cause)
188+
189+
with self.assertRaises(GaxError):
190+
with self._call_fut():
191+
self._fake_method(exc)

datastore/google/cloud/datastore/_gax.py

Lines changed: 1 addition & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,14 @@
1414

1515
"""Helpers for making API requests via GAX / gRPC."""
1616

17-
18-
import contextlib
19-
import sys
20-
2117
from google.cloud.gapic.datastore.v1 import datastore_client
22-
from google.gax.errors import GaxError
23-
from google.gax.grpc import exc_to_code
2418
from google.gax.utils import metrics
2519
from grpc import insecure_channel
26-
from grpc import StatusCode
2720
import six
2821

2922
from google.cloud._helpers import make_secure_channel
3023
from google.cloud._http import DEFAULT_USER_AGENT
31-
from google.cloud import exceptions
24+
from google.cloud.exceptions import _catch_remap_gax_error
3225

3326
from google.cloud.datastore import __version__
3427

@@ -40,46 +33,6 @@
4033
_GRPC_EXTRA_OPTIONS = (
4134
('x-goog-api-client', _HEADER_STR),
4235
)
43-
_GRPC_ERROR_MAPPING = {
44-
StatusCode.UNKNOWN: exceptions.InternalServerError,
45-
StatusCode.INVALID_ARGUMENT: exceptions.BadRequest,
46-
StatusCode.DEADLINE_EXCEEDED: exceptions.GatewayTimeout,
47-
StatusCode.NOT_FOUND: exceptions.NotFound,
48-
StatusCode.ALREADY_EXISTS: exceptions.Conflict,
49-
StatusCode.PERMISSION_DENIED: exceptions.Forbidden,
50-
StatusCode.UNAUTHENTICATED: exceptions.Unauthorized,
51-
StatusCode.RESOURCE_EXHAUSTED: exceptions.TooManyRequests,
52-
StatusCode.FAILED_PRECONDITION: exceptions.PreconditionFailed,
53-
StatusCode.ABORTED: exceptions.Conflict,
54-
StatusCode.OUT_OF_RANGE: exceptions.BadRequest,
55-
StatusCode.UNIMPLEMENTED: exceptions.MethodNotImplemented,
56-
StatusCode.INTERNAL: exceptions.InternalServerError,
57-
StatusCode.UNAVAILABLE: exceptions.ServiceUnavailable,
58-
StatusCode.DATA_LOSS: exceptions.InternalServerError,
59-
}
60-
61-
62-
@contextlib.contextmanager
63-
def _catch_remap_gax_error():
64-
"""Remap GAX exceptions that happen in context.
65-
66-
.. _code.proto: https://github.com/googleapis/googleapis/blob/\
67-
master/google/rpc/code.proto
68-
69-
Remaps gRPC exceptions to the classes defined in
70-
:mod:`~google.cloud.exceptions` (according to the description
71-
in `code.proto`_).
72-
"""
73-
try:
74-
yield
75-
except GaxError as exc:
76-
error_code = exc_to_code(exc.cause)
77-
error_class = _GRPC_ERROR_MAPPING.get(error_code)
78-
if error_class is None:
79-
raise
80-
else:
81-
new_exc = error_class(exc.cause.details())
82-
six.reraise(error_class, new_exc, sys.exc_info()[2])
8336

8437

8538
class GAPICDatastoreAPI(datastore_client.DatastoreClient):

datastore/tests/unit/test__gax.py

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -19,69 +19,6 @@
1919
from google.cloud.datastore.client import _HAVE_GRPC
2020

2121

22-
@unittest.skipUnless(_HAVE_GRPC, 'No gRPC')
23-
class Test__catch_remap_gax_error(unittest.TestCase):
24-
25-
def _call_fut(self):
26-
from google.cloud.datastore._gax import _catch_remap_gax_error
27-
28-
return _catch_remap_gax_error()
29-
30-
@staticmethod
31-
def _fake_method(exc, result=None):
32-
if exc is None:
33-
return result
34-
else:
35-
raise exc
36-
37-
@staticmethod
38-
def _make_rendezvous(status_code, details):
39-
from grpc._channel import _RPCState
40-
from google.cloud.exceptions import GrpcRendezvous
41-
42-
exc_state = _RPCState((), None, None, status_code, details)
43-
return GrpcRendezvous(exc_state, None, None, None)
44-
45-
def test_success(self):
46-
expected = object()
47-
with self._call_fut():
48-
result = self._fake_method(None, expected)
49-
self.assertIs(result, expected)
50-
51-
def test_non_grpc_err(self):
52-
exc = RuntimeError('Not a gRPC error')
53-
with self.assertRaises(RuntimeError):
54-
with self._call_fut():
55-
self._fake_method(exc)
56-
57-
def test_gax_error(self):
58-
from google.gax.errors import GaxError
59-
from grpc import StatusCode
60-
from google.cloud.exceptions import Forbidden
61-
62-
# First, create low-level GrpcRendezvous exception.
63-
details = 'Some error details.'
64-
cause = self._make_rendezvous(StatusCode.PERMISSION_DENIED, details)
65-
# Then put it into a high-level GaxError.
66-
msg = 'GAX Error content.'
67-
exc = GaxError(msg, cause=cause)
68-
69-
with self.assertRaises(Forbidden):
70-
with self._call_fut():
71-
self._fake_method(exc)
72-
73-
def test_gax_error_not_mapped(self):
74-
from google.gax.errors import GaxError
75-
from grpc import StatusCode
76-
77-
cause = self._make_rendezvous(StatusCode.CANCELLED, None)
78-
exc = GaxError(None, cause=cause)
79-
80-
with self.assertRaises(GaxError):
81-
with self._call_fut():
82-
self._fake_method(exc)
83-
84-
8522
@unittest.skipUnless(_HAVE_GRPC, 'No gRPC')
8623
class TestGAPICDatastoreAPI(unittest.TestCase):
8724

0 commit comments

Comments
 (0)