Python + gRPC + RPC + Streaming

REST works fine until it doesn’t. When you have service-to-service communication that needs low latency, binary efficiency, or streaming, you hit REST’s ceiling quickly. gRPC solves this with Protocol Buffers for compact serialization and HTTP/2 for multiplexed transport.

TL;DR: Build a Python gRPC service with unary RPC and streaming, from proto file definition to client/server implementation.
Stack: Python, gRPC, Protocol Buffers (protobuf), grpcio
Level: Intermediate
Reading time: ~8 min

Think of a .proto file as a typed contract between your services: both sides agree on the message shapes before any code runs. If the contract breaks, the build fails, not production.

Why gRPC

gRPC uses HTTP/2 for transport and Protocol Buffers for serialization, which makes it faster and more compact than traditional REST over HTTP/1.1 with JSON. Protobuf’s binary serialization produces smaller messages, HTTP/2 multiplexing lets multiple requests share a connection, and persistent connections remove the overhead of reconnecting for every call.

Create a proto file

The proto file is the source of truth for your API. It defines the service methods and message types. The generated Python code comes from this file.

syntax = "proto3";

package order;

service OrderService {
  rpc CreateOrder (OrderRequest) returns (OrderConfirmation) {}
}

message OrderRequest {
  string customer_name = 1;
  string product_name = 2;
  int32 quantity = 3;
}

message OrderConfirmation {
  string order_id = 1;
  string message = 2;
}

Install grpcio

pip install grpcio

Generate Python stubs

python3 -m grpc_tools.protoc -I. --python_out=./order_request --grpc_python_out=./order_request order_request/order.proto

Implement server.py

import grpc
from concurrent import futures
import order_pb2
import order_pb2_grpc

class OrderService(order_pb2_grpc.OrderServiceServicer):
    def CreateOrder(self, request, context):
        order_id = "ORDER-123"
        message = f"Order created! ID: {order_id}"
        return order_pb2.OrderConfirmation(order_id=order_id, message=message)

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    order_pb2_grpc.add_OrderServiceServicer_to_server(OrderService(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    server.wait_for_termination()

if __name__ == '__main__':
    serve()

Implement client.py

import grpc
import order_pb2
import order_pb2_grpc

def run():
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = order_pb2_grpc.OrderServiceStub(channel)
        response = stub.CreateOrder(order_pb2.OrderRequest(
            customer_name="Joao Silva",
            product_name="Produto A",
            quantity=2
        ))
        print(f"Answer from server: {response.message}")

if __name__ == '__main__':
    run()

What you’ve built

A working Python gRPC service with a proto contract defining messages and methods, a server that implements those methods, and a client that calls them. The same pattern scales to bidirectional streaming and server-side streaming by changing the proto definition and service implementation.

Next steps

  • Add server-side streaming by using stream in the proto return type and yielding responses from the server method.
  • Add TLS or mutual TLS authentication between your gRPC services, especially if they communicate across network boundaries.
  • Integrate with a service mesh like Envoy or Istio to get load balancing, retries, and observability on your gRPC traffic without touching application code.

Questions or feedback? Find me on LinkedIn or GitHub.

Leave a Comment