protoc-gen-grpc-py generates well-typed, idiomatic gRPC stubs for protobuf-py, the ergonomic and modern Protobuf library for Python.
Fully typed clients and servicers you can read. asyncio and sync variants.
Plugs into your existing grpcio channels and servers.
It's a compatibility layer for projects already built on grpcio. The generated clients and servicers plug into your existing gRPC channels and servers, but serialize protobuf-py messages directly. This lets you upgrade to a better Protobuf package without touching your RPC stack.
Tip
For new projects, use Connect for Python instead. Connect speaks the gRPC and gRPC-Web protocols in addition to its own, so existing gRPC clients can call a Connect server unchanged. But you also get plain HTTP APIs you can curl, first-class streaming, and generated clients for every major language, including your frontend. protoc-gen-grpc-py only exists so current gRPC codebases can get the improvements of protobuf-py today without a full rewrite.
Generated code requires the runtime libraries protobuf-py and grpcio. The plugin works with Protobuf compilers like buf.
$ uv add protobuf-py grpcio
$ uv add --dev protoc-gen-py protoc-gen-grpc-py buf-binAdd protoc-gen-grpc-py alongside protoc-gen-py in your buf.gen.yaml:
version: v2
inputs:
- directory: proto
plugins:
# Generates message types (*_pb.py).
- local: protoc-gen-py
out: src/gen
# Generates gRPC service stubs (*_pb_grpc.py).
- local: protoc-gen-grpc-py
out: src/genTo generate code for all Protobuf files within your project, run:
$ uv run -- buf generateA *_pb_grpc.py file is generated for each proto file that declares a service. Message types come from protoc-gen-py, which also manages __init__.py files.
For each service, the plugin emits a client whose methods are fully typed, so editors and type checkers understand every RPC method, its request and response types, and its streaming shape:
async with grpc.aio.insecure_channel("localhost:50051") as channel:
client = ElizaServiceClient(channel)
response = await client.say(SayRequest(sentence="Hello!"))
print(response.sentence)Each service also gets a servicer base class with typed method signatures and an add_to_server method:
class ElizaService(ElizaServiceServicer):
async def say(
self, request: SayRequest, context: grpc.aio.ServicerContext
) -> SayResponse:
return SayResponse(sentence=f"You said: {request.sentence}")
async def serve() -> None:
server = grpc.aio.server()
ElizaService().add_to_server(server)
server.add_insecure_port("[::]:50051")
await server.start()
await server.wait_for_termination()Every client and servicer is generated in both asyncio (grpc.aio) and synchronous flavors:
with grpc.insecure_channel("localhost:50051") as channel:
client = ElizaServiceClientSync(channel) #synchronous servicer
response = client.say(SayRequest(sentence="Hello!"))Requests and responses are protobuf-py messages. They have readable generated code, typed oneofs with pattern matching, real IntEnum enums, and a high-performance Rust encoder/decoder, with zero dependency on the legacy google-protobuf runtime.
The plugin also works with protoc as a standard protoc plugin:
$ uv run protoc --proto_path proto \
--py_out src/gen \
--grpc-py_out src/gen \
proto/a.proto proto/b.proto proto/c.protoSee the example for a complete client and server using the generated stubs, including server, client, and bidirectional streaming.