gRPC in Python. Part 3: Implementing gRPC streaming

Building fast and scalable APIs using gRPC

Code

The code for this article is available at: https://github.com/theundeadmonk/python-grpc-demo/

This is part of a series of gRPC in python. We will cover the following

  1. [Implementing a server](https://adityamattos.com/grpc-in-python-part-1-building-a-grpc-server)

  2. Implementing a client

  3. gRPC Streaming

  4. Advanced features like interceptors, etc.

  5. Serving frontends using gRPC gateway

What is gRPC Streaming?

One of the key features of gRPC is its support for streaming, which allows for bi-directional communication between a client and server.

Streaming is the ability to send and receive a continuous stream of data between a client and server, rather than sending discrete requests and responses. In gRPC, we have the following kinds of streaming available:

Response Streaming on the server:

Here, the client sends a request to the server and gets a stream to read a sequence of messages back. The client reads from the returned stream until there are no more messages.

Response Streaming on the client:

Here, the client writes a sequence of messages and sends them to the server, again using a provided stream. Once the client has finished writing the messages, it waits for the server to read them all and return its response.

Bidirectional Streaming:

Bidirectional streaming allows for both the client and server to send and receive a continuous stream of data. The two streams operate independently, so clients and servers can read and write in whatever order they like. This is useful if we're building a chat application or for real-time data streaming.

Implementing gRPC streaming

Let's add a streaming method to our Protobuf to support gRPC streaming. For this demo we're going to implement bidirectional streaming.

Let's edit our proto file and add the following method.

service Greeter {
  rpc Greet(GreetingRequest) returns (GreetingResponse );
  rpc Chat(stream GreetingRequest) returns (stream GreetingResponse);
}

Here we created a method called chat that accepts a stream of GreetingRequest and returns a stream of GreetingResponse. Now that we have this, we need to generate the compiled proto files again. [Link](https://adityamattos.com/grpc-in-python-part-1-building-a-grpc-server#heading-generating-types)

Server

We first need to implement the Chat method on the server and to do this, we add a new Chat method in our greeter.py servicer. Let's edit our greeter.py file and add the following.

class Greeter(GreeterServicer):
    def Greet(self, request: GreetingRequest, context: grpc.ServicerContext) -> GreetingResponse:
        return GreetingResponse(greeting='Hello %s!' % request.name)
    ### This is the method we have to implement
    def Chat(self, request_iterator, context: grpc.ServicerContext):
        for request in request_iterator:
            yield GreetingResponse(greeting=f"Welcome, {request.name}!")

The implementation is pretty straightforward. It accepts an iterator of requests and for each of them, we return a response with the word "Welcome" in front of the request name. This was the only change we needed to make on the server.

Client

To call this method from our client, we can edit our client code to call our Chat method. Open the client.py file and add the following method.

def chat():
    with grpc.insecure_channel('localhost:50051') as channel:
        greeter_stub = GreeterStub(channel)

        request_iterator = iter(
            [
                GreetingRequest(name="Alice"), 
                GreetingRequest(name="Bob")
            ]
        )
        response_iterator = greeter_stub.Chat(request_iterator)
        for response in response_iterator:
            print(response.message)

Here, we're passing in an iterator or stream of messages to the server and we receive an iterator of server responses back.

Finally, let's edit out pyproject.toml file to add a script to invoke the Chat method on the client.

[tool.poetry.scripts]
run-grpc-server = "python_grpc_demo.server.server:serve"
run-grpc-client = "python_grpc_demo.client.client:run"
run-grpc-chat = "python_grpc_demo.client.client:chat"

Putting it all together:

We can run our server and client and take a look at the responses we recieve.

$ poetry run run-grpc-chat
Welcome, Alice!
Welcome, Bob!

That's it. Next time, we'll take a look at adding interceptors to our gRPC code.