Skip to content

Add underlying HTTP details to GRPCError #171

New issue

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

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

Already on GitHub? # to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
39 changes: 26 additions & 13 deletions grpclib/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from .metadata import Deadline, USER_AGENT, decode_grpc_message, encode_timeout
from .metadata import encode_metadata, decode_metadata, _MetadataLike, _Metadata
from .metadata import _STATUS_DETAILS_KEY, decode_bin_value
from .exceptions import GRPCError, ProtocolError, StreamTerminatedError
from .exceptions import GRPCError, ProtocolError, StreamTerminatedError, HTTPDetails
from .encoding.base import GRPC_CONTENT_TYPE, CodecBase, StatusDetailsCodecBase
from .encoding.proto import ProtoCodec, ProtoStatusDetailsCodec
from .encoding.proto import _googleapis_available
Expand Down Expand Up @@ -294,13 +294,15 @@ def _raise_for_status(self, headers_map: Dict[str, str]) -> None:
if status is not None and status != _H2_OK:
grpc_status = _H2_TO_GRPC_STATUS_MAP.get(status, Status.UNKNOWN)
raise GRPCError(grpc_status,
'Received :status = {!r}'.format(status))
'Received :status = {!r}'.format(status),
http_details=HTTPDetails(status, headers_map))

def _raise_for_content_type(self, headers_map: Dict[str, str]) -> None:
content_type = headers_map.get('content-type')
if content_type is None:
raise GRPCError(Status.UNKNOWN,
'Missing content-type header')
'Missing content-type header',
http_details=HTTPDetails(headers_map.get(":status"), headers_map))

base_content_type, _, sub_type = content_type.partition('+')
sub_type = sub_type or ProtoCodec.__content_subtype__
Expand All @@ -310,19 +312,23 @@ def _raise_for_content_type(self, headers_map: Dict[str, str]) -> None:
):
raise GRPCError(Status.UNKNOWN,
'Invalid content-type: {!r}'
.format(content_type))
.format(content_type),
http_details=HTTPDetails(headers_map.get(":status"), headers_map))

def _process_grpc_status(
self, headers_map: Dict[str, str],
) -> Tuple[Status, Optional[str], Any]:
grpc_status = headers_map.get('grpc-status')
if grpc_status is None:
raise GRPCError(Status.UNKNOWN, 'Missing grpc-status header')
raise GRPCError(Status.UNKNOWN,
'Missing grpc-status header',
http_details=HTTPDetails(headers_map.get(":status"), headers_map))
try:
status = Status(int(grpc_status))
except ValueError:
raise GRPCError(Status.UNKNOWN, ('Invalid grpc-status: {!r}'
.format(grpc_status)))
raise GRPCError(Status.UNKNOWN,
'Invalid grpc-status: {!r}'.format(grpc_status),
http_details=HTTPDetails(headers_map.get(":status"), headers_map))
else:
message, details = None, None
if status is not Status.OK:
Expand All @@ -339,10 +345,15 @@ def _process_grpc_status(
return status, message, details

def _raise_for_grpc_status(
self, status: Status, message: Optional[str], details: Any,
self,
status: Status,
message: Optional[str],
details: Any,
headers: Dict[str, str] = None
) -> None:
if status is not Status.OK:
raise GRPCError(status, message, details)
http_status = headers.get(":status") if headers is not None else None
raise GRPCError(status, message, details, HTTPDetails(http_status, headers))

async def recv_initial_metadata(self) -> None:
"""Coroutine to wait for headers with initial metadata from the server.
Expand Down Expand Up @@ -390,7 +401,7 @@ async def recv_initial_metadata(self) -> None:
)
self.trailing_metadata = tm

self._raise_for_grpc_status(status, message, details)
self._raise_for_grpc_status(status, message, details, headers_map)
else:
im = decode_metadata(headers)
im, = await self._dispatch.recv_initial_metadata(im)
Expand Down Expand Up @@ -523,20 +534,22 @@ async def _maybe_finish(self) -> None:
await self.recv_trailing_metadata()

def _maybe_raise(self) -> None:
headers_map = {}
if self._stream.headers is not None:
self._raise_for_status(dict(self._stream.headers))
headers_map = dict(self._stream.headers)
self._raise_for_status(headers_map)
if self._stream.trailers is not None:
status, message, details = self._process_grpc_status(
dict(self._stream.trailers),
)
self._raise_for_grpc_status(status, message, details)
self._raise_for_grpc_status(status, message, details, headers)
elif self._stream.headers is not None:
headers_map = dict(self._stream.headers)
if 'grpc-status' in headers_map:
status, message, details = self._process_grpc_status(
headers_map,
)
self._raise_for_grpc_status(status, message, details)
self._raise_for_grpc_status(status, message, details, headers_map)

async def __aexit__(
self,
Expand Down
21 changes: 16 additions & 5 deletions grpclib/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
from typing import Optional, Any
from typing import Optional, Any, Dict
from dataclasses import dataclass, field

from .const import Status


@dataclass(frozen=True)
class HTTPDetails:
status: str = field(default="")
headers: Dict[str, str] = field(default_factory=dict)


class GRPCError(Exception):
"""Expected error, may be raised during RPC call

Expand All @@ -26,11 +33,13 @@ class GRPCError(Exception):
`(e.g. server returned unsupported` ``:content-type`` `header)`

"""

def __init__(
self,
status: Status,
message: Optional[str] = None,
details: Any = None,
self,
status: Status,
message: Optional[str] = None,
details: Any = None,
http_details: HTTPDetails = None
) -> None:
super().__init__(status, message, details)
#: :py:class:`~grpclib.const.Status` of the error
Expand All @@ -39,6 +48,8 @@ def __init__(
self.message = message
#: Error details
self.details = details
#: Http details
self.http_details = http_details


class ProtocolError(Exception):
Expand Down