Skip to content

Instantly share code, notes, and snippets.

@rmariano
Last active July 17, 2019 13:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rmariano/aa95f29314e3cd23b9833f69264f550d to your computer and use it in GitHub Desktop.
Save rmariano/aa95f29314e3cd23b9833f69264f550d to your computer and use it in GitHub Desktop.
Proposal for exposing the aiogrpc interface to users

Aio gRPC

A Proposal from the user's perspective

As a user, to use the new implementation of gRCP in the asynchronous version, I will compile by passing a specific flag:

$ python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. proto/echo.proto --aiogrpc

This compiles the stubs, generated with the asynchronous code.

Example of the code generated by the asynchronous version:

# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
import grpc
from grpc.aio import AioChannel, AioServer
from proto import echo_pb2 as proto_dot_echo__pb2


class EchoStub(object):
  # missing associated documentation comment in .proto file
  pass

  def __init__(self, channel: AioChannel):
    """Constructor.

    Args:
      channel: A grpc.aio.AioChannel.
    """
    self.Hi = channel.unary_unary(
        '/echo.Echo/Hi',
        request_serializer=proto_dot_echo__pb2.EchoRequest.SerializeToString,
        response_deserializer=proto_dot_echo__pb2.EchoReply.FromString,
        )


class EchoServicer(object):
  # missing associated documentation comment in .proto file
  pass

  async def Hi(self, request, context):
    # missing associated documentation comment in .proto file
    pass
    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
    context.set_details('Method not implemented!')
    raise NotImplementedError('Method not implemented!')

def add_EchoServicer_to_server(servicer, server: AioServer):
  rpc_method_handlers = {
      'Hi': grpc.unary_unary_rpc_method_handler(
          servicer.Hi,
          request_deserializer=proto_dot_echo__pb2.EchoRequest.FromString,
          response_serializer=proto_dot_echo__pb2.EchoReply.SerializeToString,
      ),
  }
  generic_handler = grpc.method_handlers_generic_handler(
      'echo.Echo', rpc_method_handlers)
  server.add_generic_rpc_handlers((generic_handler,))

Main changes that are introduced in this code (when the flag is passed):

  • The channel is a different class now (it's an asynchronous channel)
  • All callable objects returned by this channel, are asynchronous (their callable methods are defined as async def __call__(...))
  • The same for the server (also asynchronous, new type).

Code as it would be used from the client

import grpc.aio

channel = grpc.aio.insecure_channel("127.0.0.1:50051")
stub = echo_pb2_grpc.EchoStub(channel)
response = await stub.Hi(echo_pb2.EchoRequest(message="ping"))

Or, with an asynchronous context manager:

import grpc.aio
import echo_pb2_grpc
import echo_pb2

async with grpc.aio.insecure_channel("127.0.0.1:50051") as channel:
    stub = echo_pb2_grpc.EchoStub(channel)
    response = await stub.Hi(echo_pb2.EchoRequest(message="ping"))

Main traits of the implementation

The control over the version of the code (synchronous vs. asynchronous) is totally up to the user, by providing the --aiogrpc flag at compilation time. The API is used by the user explicitly, when determining from where to import the code (import grpc.aio).

Open questions

  1. Would it make sense to also generate the grpc stub code with an asynchronous name? Like import echo_pb2_grpc_aio
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment