Errors

GRPCError is a main error you should expect on the client-side and raise occasionally on the server-side.

Error Details

There is a possibility to send and receive rich error details, which may provide much more context than status and message alone. These details are encoded using google.rpc.Status message and sent with trailing metadata. This message becomes available after optional package install:

$ pip3 install googleapis-common-protos

There are some already defined error details in the google.rpc.error_details_pb2 module, but you’re not limited to them, you can send any message you want.

Here is how to send these details from the server-side:

from google.rpc.error_details_pb2 import BadRequest

async def Method(self, stream):
    ...
    raise GRPCError(
        Status.INVALID_ARGUMENT,
        'Request validation failed',
        [
            BadRequest(
                field_violations=[
                    BadRequest.FieldViolation(
                        field='title',
                        description='This field is required',
                    ),
                ],
            ),
        ],
    )

Here is how to dig into every detail on the client-side:

from google.rpc.error_details_pb2 import BadRequest

try:
    reply = await stub.Method(Request(...))
except GRPCError as err:
    if err.details:
        for detail in err.details:
            if isinstance(detail, BadRequest):
                for violation in detail.field_violations:
                    print(f'{violation.field}: {violation.description}')

Note

In order to automatically decode these messages (details), you have to import them, otherwise you will see such stubs in the list of error details:

Unknown('google.rpc.QuotaFailure')

Client-Side

Here is an example to illustrate how errors propagate from inside the grpclib methods back to the caller:

async with stub.SomeMethod.open() as stream:
    await stream.send_message(Request(...))
    reply = await stream.recv_message()  # gRPC error received during this call

Exceptions are propagated this way:

  1. CancelledError is raised inside recv_message() coroutine to interrupt it

  2. recv_message() coroutine handles this error and raise StreamTerminatedError instead or other error when it is possible to explain why coroutine was cancelled

  3. when the open() context-manager exits, it may handle transitive errors such as StreamTerminatedError and raise proper GRPCError instead when possible

So here is a rule of thumb: expect GRPCError outside the open() context-manager:

try:
    async with stub.SomeMethod.open() as stream:
        await stream.send_message(Request(...))
        reply = await stream.recv_message()
except GRPCError as error:
    print(error.status, error.message)

Server-Side

Here is an example to illustrate how request cancellation is performed:

class Greeter(GreeterBase):
    async def SayHello(self, stream):
        try:
            ...
            await asyncio.sleep(1)  # cancel happens here
            ...
        finally:
            pass  # cleanup
  1. Task running SayHello coroutine gets cancelled and CancelledError is raised inside it

  2. You can use try..finally clause and/or context managers to properly cleanup used resources

  3. When SayHello coroutine finishes, grpclib server internally re-raises CancelledError as TimeoutError or StreamTerminatedError to explain why request was cancelled

  4. If cancellation isn’t performed clearly, e.g. SayHello raises another exception instead of CancelledError, this error is logged.

Reference

exception grpclib.exceptions.GRPCError(status: Status, message: Optional[str] = None, details: Optional[Any] = None)

Expected error, may be raised during RPC call

There can be multiple origins of this error. It can be generated on the server-side and on the client-side. If this error originates from the server, on the wire this error is represented as grpc-status and grpc-message trailers. Possible values of the grpc-status trailer are described in the gRPC protocol definition. In grpclib these values are represented as Status enum.

Here are possible origins of this error:

  • you may raise this error to cancel current call on the server-side or return non-OK Status using send_trailing_metadata() method (e.g. resource not found)

  • server may return non-OK grpc-status in different failure conditions (e.g. invalid request)

  • client raises this error for non-OK grpc-status from the server

  • client may raise this error in different failure conditions (e.g. server returned unsupported :content-type header)

status

Status of the error

message

Error message

details

Error details

exception grpclib.exceptions.ProtocolError

Unexpected error, raised by grpclib when your code violates gRPC protocol

This error means that you probably should fix your code.

exception grpclib.exceptions.StreamTerminatedError

Unexpected error, raised when we receive RST_STREAM frame from the other side

This error means that the other side decided to forcefully cancel current call, probably because of a protocol error.

class grpclib.const.Status(value)

Predefined gRPC status codes represented as enum

See also: https://github.com/grpc/grpc/blob/master/doc/statuscodes.md

OK = 0

The operation completed successfully

CANCELLED = 1

The operation was cancelled (typically by the caller)

UNKNOWN = 2

Generic status to describe error when it can’t be described using other statuses

INVALID_ARGUMENT = 3

Client specified an invalid argument

DEADLINE_EXCEEDED = 4

Deadline expired before operation could complete

NOT_FOUND = 5

Some requested entity was not found

ALREADY_EXISTS = 6

Some entity that we attempted to create already exists

PERMISSION_DENIED = 7

The caller does not have permission to execute the specified operation

RESOURCE_EXHAUSTED = 8

Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space

FAILED_PRECONDITION = 9

Operation was rejected because the system is not in a state required for the operation’s execution

ABORTED = 10

The operation was aborted

OUT_OF_RANGE = 11

Operation was attempted past the valid range

UNIMPLEMENTED = 12

Operation is not implemented or not supported/enabled in this service

INTERNAL = 13

Internal errors

UNAVAILABLE = 14

The service is currently unavailable

DATA_LOSS = 15

Unrecoverable data loss or corruption

UNAUTHENTICATED = 16

The request does not have valid authentication credentials for the operation