kgully
kgully

Reputation: 662

bazel build and run grpc server with python

I am trying to setup a larger bazel infrastructure to

  1. Create .py files from a .proto file for gRPC using rules_proto_grpc_python
  2. Run the server code that implements the interfaces defined in 1 via a py_binary

However, I am getting the error

google.protobuf.runtime_version.VersionError: Detected incompatible Protobuf Gencode/Runtime versions
when loading helloworld/hello.proto: gencode 5.29.1 runtime 5.27.2. Runtime version
cannot be older than the linked gencode version. See Protobuf version
guarantees at https://protobuf.dev/support/cross-version-runtime-guarantee.

indicating that the protobuf version that rules_proto_grpc_python uses to generate the .py files is different from the version used at runtime. I tried to require protobuf>=5.29.1 for the `py_binary`` but that is not possible due to conficts with other packages in the project. In this minimal example, it is possible but issues several warnings about conflicting symlinks which seems concerning as well. Is there a way to either

  1. Specify the protobuf version to use for rules_proto_grpc_python
  2. Generate the .py files from the .proto in another way?

Edit 2025-02-27

I managed to get this to work by playing around with the requirements in my larger repository. Specifying the minimum protobuf version causes it to work.

Here are the relevant files to make a minimal example

The top-level MODULE.bazel:

# MODULE.bazel

bazel_dep(name = "aspect_bazel_lib", version = "2.7.8")
bazel_dep(name = "aspect_rules_py", version = "1.3.2")
bazel_dep(name = "rules_python", version = "1.1.0")
bazel_dep(name = "rules_proto", version = "7.1.0")
bazel_dep(name = "rules_proto_grpc_python", version = "5.0.0")


# deps for python projects.
_PY_MINOR_VER = 10

_PY_VERSION = "3.%s" % _PY_MINOR_VER

_PY_VER_ = "python_3_%s" % _PY_MINOR_VER

python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(
    python_version = _PY_VERSION,
    is_default = True,
)

# You can use this repo mapping to ensure that your BUILD.bazel files don't need
# to be updated when the python version changes to a different `3.12` version.
use_repo(
    python,
    "python_3_%s" % _PY_MINOR_VER,
)

pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
pip.parse(
    hub_name = "pypi",
    # We need to use the same version here as in the `python.toolchain` call.
    python_version = _PY_VERSION,
    requirements_lock = "//:requirements_lock.txt",
)
use_repo(pip, "pypi")

Inside a directory /proto_library/proto/hello.proto

# proto_library/proto/hello.proto

syntax = "proto3";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

The BUILD.bazel alongside the proto file

# proto_library/proto/BUILD.bazel

load("@rules_proto//proto:defs.bzl", "proto_library")
load("@rules_proto_grpc_python//:defs.bzl", "py_proto_library", "py_grpc_library")


proto_library(
    name = "hello_proto",
    srcs = ["hello.proto"],
    # Add these to control Python package structure
    strip_import_prefix = "/proto_library/proto",
    import_prefix = "helloworld",
    visibility = ["//visibility:public"],
)

py_proto_library(
    name = "hello_py_proto",
    protos = [":hello_proto"],
    visibility = ["//visibility:public"],
)

py_grpc_library(
    name = "hello_py_grpc",
    protos = [":hello_proto"],
    visibility = ["//visibility:public"],
    deps = [":hello_py_proto"],
)

The python code to implement the server

# proto_library/src/hello_server.py

import logging
import sys
from concurrent import futures

import grpc
from helloworld import hello_pb2, hello_pb2_grpc


class Greeter(hello_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        return hello_pb2.HelloReply(message="Hello, %s!" % request.name)


def serve():
    port = "50051"
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    hello_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
    server.add_insecure_port("[::]:" + port)
    server.start()
    print("Server started, listening on " + port)
    server.wait_for_termination()


if __name__ == "__main__":
    logging.basicConfig()
    serve()

And finally, the BUILD file inside proto_library

# proto_library/BUILD.bazel

load("@aspect_rules_py//py:defs.bzl", "py_binary", "py_library")
load("@pypi//:requirements.bzl", "requirement")


py_binary(
    name = "hello_test",
    srcs = ["src/hello_server.py"],
    deps = [
        "//proto_library/proto:hello_py_proto",
        "//proto_library/proto:hello_py_grpc",
        requirement("grpcio"),
        requirement("protobuf"),
    ],
    # Add these to handle Python packaging
    imports = [".."],  # Make parent directories importable
    visibility = ["//visibility:public"],
    package_collisions = "warning",
    # Create proper Python package structure
    main = "hello_server.py",
)

Upvotes: 0

Views: 38

Answers (0)

Related Questions