Search code examples
pythonprotocol-buffers

Why default value of a field does not change in the compiled proto file?


I have a proto file as below:

message MyMessage
{
    optional uint32 field1 = 1 [default = 5];
}

And when I compile it with below command,

protoc -I=. --python_betterproto_out=. my_message.proto

I get below init.py file:

@dataclass(eq=False, repr=False)
class MyMessage(betterproto.Message):
    field1: int = betterproto.uint32_field(1)

Finally, I want to get the binary encoded representation of a message object as below, :

meessage = MyMessage()
message.field1 = 0
encoded = bytes(message)

It does not serialize field1. The reason is specified in the __bytes__ function (the link):

            # Default (zero) values are not serialized. Two exceptions are
            # if this is the selected oneof item or if we know we have to
            # serialize an empty message (i.e. zero value was explicitly
            # set by the user).
            

I want to know, why the zero is still the default value, despite of setting it to 5 in `.proto' file? and how can I fix it?

I tried the oneof solution and it worked. But I don't want to use that.


Solution

  • Here's my repro that demonstrates that betterproto does not appear to reflect the [default = 5] while protobuf does.

    python3 -m venv venv
    source venv/bin/activate
    
    python3 -m pip "betterproto[compiler]"
    python3 -m pip protobuf
    

    Ensure protoc is in the PATH:

    PATH=${PATH}:/path/to/protoc-22.4-linux-x86_64/bin
    

    Then:

    NAME="foo.proto"
    protoc \
    --proto_path=${PWD} \
    --python_betterproto_out=${PWD} \
    --python_out=${PWD} \
    --pyi_out=${PWD} \
    ${PWD}/${NAME}
    

    Then: main.py:

    import foo
    import foo_pb2
    
    # betterproto prints default Default (!) of 0
    m = foo.MyMessage()
    print(m.field1)
    
    # protobuf prints annotated Default of 5
    m = foo_pb2.MyMessage()
    print(m.field1)
    

    Prints:

    0
    5
    

    And, protobuf generates ${NAME}_pb2.py which includes a serialized copy of the FileDescriptor which uses FileDescriptorProto.

    We can use protoc to decode this:

    FILE="..." # AddSerializedFile byte string from ${NAME}_pb2.py
    
    printf ${FILE} \
    | protoc \
      --decode=google.protobuf.FileDescriptorProto \
      --proto_path=path/to/descriptor.proto
    

    Which yields:

    name: "{NAME}"
    package: "{PACKAGE}"
    message_type {
      name: "MyMessage"
      field {
        name: "field1"
        number: 1
        label: LABEL_OPTIONAL
        type: TYPE_UINT32
        default_value: "5"
      }
    }