python-3.xprotocol-buffersgrpcgrpc-python

gRPC client could not receive proper attribute when multiple enum is defined in one file


I defined two enum (AnsType and ErrorType) in a single .proto file and I used both of them in a message. However, the client can not parse both enum. Why does it happen and how can I solve it?

The definition of the proto file is helloworld.proto:

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

enum AnsType {
  OTHER = 0;
  YES = 1;
  NO = 2;
}

enum ErrorType {
  UNDEFINED = 0;
  OOM = 1;
}


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

  rpc SayHelloStreamReply (HelloRequest) returns (stream 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;
  AnsType status = 2;
  ErrorType error = 3;
}

Server side python code greeter_server.py:

from concurrent import futures
import logging

import grpc
import helloworld_pb2
import helloworld_pb2_grpc


class Greeter(helloworld_pb2_grpc.GreeterServicer):

    def SayHello(self, request, context):
        print(f'Receive {request}')
        return helloworld_pb2.HelloReply(
            message='Hello, %s!' % request.name,
            status=helloworld_pb2.AnsType.YES,
            error=helloworld_pb2.ErrorType.UNDEFINED,
            )


def serve():
    port = '50051'
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    helloworld_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()

Client side python code greeter_client.py:

from __future__ import print_function
import sys

import logging

import grpc
import helloworld_pb2
import helloworld_pb2_grpc


def run():
    # NOTE(gRPC Python Team): .close() is possible on a channel and should be
    # used in circumstances in which the with statement does not fit the needs
    # of the code.
    print("Will try to greet world ...")
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = helloworld_pb2_grpc.GreeterStub(channel)
        response = stub.SayHello(helloworld_pb2.HelloRequest(name='you'))
    print(response)
    print("Greeter client received: " + response.message)


if __name__ == '__main__':
    logging.basicConfig()
    run()

The above code derives from a tutorial (https://grpc.io/docs/languages/python/quickstart/).

The output from the client side is:

Will try to greet world ...
message: "Hello, you!"

Greeter client received: Hello, you!

However, what I expected output is

Will try to greet world ...
message: "Hello, you!"
status: OTHER
error: UNDEFINED

Greeter client received: Hello, you!

Thank you in advance.


Solution

  • Your code works.

    You may be unfamiliar with Protobuf use of default values.

    In Python, Protobuf enums are integers with a default value of zero.

    Protobuf doesn't serialize default valued fields and so, AnsType.OTHER(==0) and ErrorType.UNDEFINED(==0) won't be present in the message received by the client.

    However, you can e.g. assert response.error == helloworld_pb2.Error.UNDEFINED or helloworld_pb2.Error.Value("UNDEFINED")

    See Default Values