Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move '_catch_remap_gax_error' to 'core.exceptions'. #3444

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 54 additions & 4 deletions core/google/cloud/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,27 @@
# Avoid the grpc and google.cloud.grpc collision.
from __future__ import absolute_import

import contextlib
import copy
import json
import six

from google.cloud._helpers import _to_bytes
import sys

_HTTP_CODE_TO_EXCEPTION = {} # populated at end of module
import six

try:
from google.gax.errors import GaxError
from google.gax.grpc import exc_to_code
from grpc import StatusCode
from grpc._channel import _Rendezvous
except ImportError: # pragma: NO COVER
_HAVE_GRPC = False
_Rendezvous = None
else:
_HAVE_GRPC = True

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.


from google.cloud._helpers import _to_bytes

_HTTP_CODE_TO_EXCEPTION = {} # populated at end of module


# pylint: disable=invalid-name
Expand Down Expand Up @@ -250,3 +259,44 @@ def _walk_subclasses(klass):
code = getattr(_eklass, 'code', None)
if code is not None:
_HTTP_CODE_TO_EXCEPTION[code] = _eklass

_GRPC_ERROR_MAPPING = {

This comment was marked as spam.

StatusCode.UNKNOWN: InternalServerError,
StatusCode.INVALID_ARGUMENT: BadRequest,
StatusCode.DEADLINE_EXCEEDED: GatewayTimeout,
StatusCode.NOT_FOUND: NotFound,
StatusCode.ALREADY_EXISTS: Conflict,
StatusCode.PERMISSION_DENIED: Forbidden,
StatusCode.UNAUTHENTICATED: Unauthorized,
StatusCode.RESOURCE_EXHAUSTED: TooManyRequests,
StatusCode.FAILED_PRECONDITION: PreconditionFailed,
StatusCode.ABORTED: Conflict,
StatusCode.OUT_OF_RANGE: BadRequest,
StatusCode.UNIMPLEMENTED: MethodNotImplemented,
StatusCode.INTERNAL: InternalServerError,
StatusCode.UNAVAILABLE: ServiceUnavailable,
StatusCode.DATA_LOSS: InternalServerError,
}


@contextlib.contextmanager
def _catch_remap_gax_error():

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

"""Remap GAX 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 GaxError as exc:
error_code = exc_to_code(exc.cause)
error_class = _GRPC_ERROR_MAPPING.get(error_code)
if error_class is None:
raise
else:
new_exc = error_class(exc.cause.details())
six.reraise(error_class, new_exc, sys.exc_info()[2])
1 change: 1 addition & 0 deletions core/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
'protobuf >= 3.0.0',
'google-auth >= 0.4.0, < 2.0.0dev',
'google-auth-httplib2',
'google-gax>=0.15.7, <0.16dev',

This comment was marked as spam.

This comment was marked as spam.

'six',
]

Expand Down
65 changes: 65 additions & 0 deletions core/tests/unit/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import unittest

from google.cloud.exceptions import _HAVE_GRPC

This comment was marked as spam.



class Test_GoogleCloudError(unittest.TestCase):

Expand Down Expand Up @@ -145,6 +147,69 @@ def test_without_use_json(self):
self.assertEqual(list(exception.errors), [])


@unittest.skipUnless(_HAVE_GRPC, 'No gRPC')
class Test__catch_remap_gax_error(unittest.TestCase):

def _call_fut(self):
from google.cloud.exceptions import _catch_remap_gax_error

return _catch_remap_gax_error()

@staticmethod
def _fake_method(exc, result=None):
if exc is None:
return result
else:
raise exc

@staticmethod
def _make_rendezvous(status_code, details):
from grpc._channel import _RPCState
from google.cloud.exceptions import GrpcRendezvous

exc_state = _RPCState((), None, None, status_code, details)
return GrpcRendezvous(exc_state, None, None, None)

def test_success(self):
expected = object()
with self._call_fut():
result = self._fake_method(None, expected)
self.assertIs(result, expected)

def test_non_grpc_err(self):
exc = RuntimeError('Not a gRPC error')
with self.assertRaises(RuntimeError):
with self._call_fut():
self._fake_method(exc)

def test_gax_error(self):
from google.gax.errors import GaxError
from grpc import StatusCode
from google.cloud.exceptions import Forbidden

# First, create low-level GrpcRendezvous exception.
details = 'Some error details.'
cause = self._make_rendezvous(StatusCode.PERMISSION_DENIED, details)
# Then put it into a high-level GaxError.
msg = 'GAX Error content.'
exc = GaxError(msg, cause=cause)

with self.assertRaises(Forbidden):
with self._call_fut():
self._fake_method(exc)

def test_gax_error_not_mapped(self):
from google.gax.errors import GaxError
from grpc import StatusCode

cause = self._make_rendezvous(StatusCode.CANCELLED, None)
exc = GaxError(None, cause=cause)

with self.assertRaises(GaxError):
with self._call_fut():
self._fake_method(exc)


class _Response(object):
def __init__(self, status):
self.status = status
49 changes: 1 addition & 48 deletions datastore/google/cloud/datastore/_gax.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,14 @@

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


import contextlib
import sys

from google.cloud.gapic.datastore.v1 import datastore_client
from google.gax.errors import GaxError
from google.gax.grpc import exc_to_code
from google.gax.utils import metrics
from grpc import insecure_channel
from grpc import StatusCode
import six

from google.cloud._helpers import make_secure_channel
from google.cloud._http import DEFAULT_USER_AGENT
from google.cloud import exceptions
from google.cloud.exceptions import _catch_remap_gax_error

from google.cloud.datastore import __version__

Expand All @@ -40,46 +33,6 @@
_GRPC_EXTRA_OPTIONS = (
('x-goog-api-client', _HEADER_STR),
)
_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 _catch_remap_gax_error():
"""Remap GAX 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 GaxError as exc:
error_code = exc_to_code(exc.cause)
error_class = _GRPC_ERROR_MAPPING.get(error_code)
if error_class is None:
raise
else:
new_exc = error_class(exc.cause.details())
six.reraise(error_class, new_exc, sys.exc_info()[2])


class GAPICDatastoreAPI(datastore_client.DatastoreClient):
Expand Down
63 changes: 0 additions & 63 deletions datastore/tests/unit/test__gax.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,69 +19,6 @@
from google.cloud.datastore.client import _HAVE_GRPC


@unittest.skipUnless(_HAVE_GRPC, 'No gRPC')
class Test__catch_remap_gax_error(unittest.TestCase):

def _call_fut(self):
from google.cloud.datastore._gax import _catch_remap_gax_error

return _catch_remap_gax_error()

@staticmethod
def _fake_method(exc, result=None):
if exc is None:
return result
else:
raise exc

@staticmethod
def _make_rendezvous(status_code, details):
from grpc._channel import _RPCState
from google.cloud.exceptions import GrpcRendezvous

exc_state = _RPCState((), None, None, status_code, details)
return GrpcRendezvous(exc_state, None, None, None)

def test_success(self):
expected = object()
with self._call_fut():
result = self._fake_method(None, expected)
self.assertIs(result, expected)

def test_non_grpc_err(self):
exc = RuntimeError('Not a gRPC error')
with self.assertRaises(RuntimeError):
with self._call_fut():
self._fake_method(exc)

def test_gax_error(self):
from google.gax.errors import GaxError
from grpc import StatusCode
from google.cloud.exceptions import Forbidden

# First, create low-level GrpcRendezvous exception.
details = 'Some error details.'
cause = self._make_rendezvous(StatusCode.PERMISSION_DENIED, details)
# Then put it into a high-level GaxError.
msg = 'GAX Error content.'
exc = GaxError(msg, cause=cause)

with self.assertRaises(Forbidden):
with self._call_fut():
self._fake_method(exc)

def test_gax_error_not_mapped(self):
from google.gax.errors import GaxError
from grpc import StatusCode

cause = self._make_rendezvous(StatusCode.CANCELLED, None)
exc = GaxError(None, cause=cause)

with self.assertRaises(GaxError):
with self._call_fut():
self._fake_method(exc)


@unittest.skipUnless(_HAVE_GRPC, 'No gRPC')
class TestGAPICDatastoreAPI(unittest.TestCase):

Expand Down