OneOf and empty Message issue
When try to use empty Message (Message without fields) in one_of group, I cannot convert it to dict or bool.
Code to reproduce:
syntax = "proto3";
message CommandGetInfo
{}
message CommandGetName
{}
message Test {
oneof foo {
bool on = 1;
int32 count = 2;
string name = 3;
CommandGetInfo get_info = 4;
CommandGetName get_name = 5;
}
}
import betterproto
from oneof import Test, CommandGetInfo, CommandGetName
def print_one_of(msg: Test):
def _to_bool(_value) -> bool:
return True if _value else False
msg_one_of = betterproto.which_one_of(msg, "foo")
print(f"msg={msg}")
print(f"msg_one_of={msg_one_of}")
print(f"msg to dict={msg.to_dict()}")
print(f"to bool test:")
print(f"\tmsg.on={_to_bool(msg.on)}")
print(f"\tmsg.count={_to_bool(msg.count)}")
print(f"\tmsg.name={_to_bool(msg.name)}")
print(f"\tmsg.get_info={_to_bool(msg.get_info)}")
print(f"\tmsg.get_name={_to_bool(msg.get_name)}")
print(f"to bytes: {bytes(msg)}")
print("\n")
test = Test()
print_one_of(msg=test)
test.get_name = CommandGetName()
print_one_of(msg=test)
test.on = True
print_one_of(msg=test)
test.count = 57
print_one_of(msg=test)
test.name = "Name"
print_one_of(msg=test)
test.get_info = CommandGetInfo()
print_one_of(msg=test)
Output:
msg=Test(on=False, count=0, name='', get_info=CommandGetInfo(), get_name=CommandGetName())
msg_one_of=('', None)
msg to dict={}
to bool test:
msg.on=False
msg.count=False
msg.name=False
msg.get_info=True
msg.get_name=True
to bytes: b''
msg=Test(on=False, count=0, name='', get_info=CommandGetInfo(), get_name=CommandGetName())
msg_one_of=('get_name', CommandGetName())
msg to dict={}
to bool test:
msg.on=False
msg.count=False
msg.name=False
msg.get_info=True
msg.get_name=True
to bytes: b''
msg=Test(on=True, count=0, name='', get_info=CommandGetInfo(), get_name=CommandGetName())
msg_one_of=('on', True)
msg to dict={'on': True}
to bool test:
msg.on=True
msg.count=False
msg.name=False
msg.get_info=True
msg.get_name=True
to bytes: b'\x08\x01'
msg=Test(on=False, count=57, name='', get_info=CommandGetInfo(), get_name=CommandGetName())
msg_one_of=('count', 57)
msg to dict={'count': 57}
to bool test:
msg.on=False
msg.count=True
msg.name=False
msg.get_info=True
msg.get_name=True
to bytes: b'\x109'
msg=Test(on=False, count=0, name='Name', get_info=CommandGetInfo(), get_name=CommandGetName())
msg_one_of=('name', 'Name')
msg to dict={'name': 'Name'}
to bool test:
msg.on=False
msg.count=False
msg.name=True
msg.get_info=True
msg.get_name=True
to bytes: b'\x1a\x04Name'
msg=Test(on=False, count=0, name='', get_info=CommandGetInfo(), get_name=CommandGetName())
msg_one_of=('get_info', CommandGetInfo())
msg to dict={}
to bool test:
msg.on=False
msg.count=False
msg.name=False
msg.get_info=True
msg.get_name=True
to bytes: b''
We can see that bool values are always set to True for empty proto messages, serializing them to dictionary gives empty dictionary, serializing to bytes also gives empty value.
I tested on python versions 3.9 and 3.10. Betterproto: 1.2.5
Please can you test pip install betterproto --pre?
With betterproto version 2.0.0b4 to_dict works, but unlike version 1.2.4 the field as bool is always set to False for empty proto messages:
msg=Test(on=False, count=0, name='', get_info=CommandGetInfo(), get_name=CommandGetName())
msg_one_of=('', None)
msg to dict={}
to bool test:
msg.on=False
msg.count=False
msg.name=False
msg.get_info=False
msg.get_name=False
to bytes: b''
msg=Test(on=False, count=0, name='', get_info=CommandGetInfo(), get_name=CommandGetName())
msg_one_of=('get_name', CommandGetName())
msg to dict={'getName': {}}
to bool test:
msg.on=False
msg.count=False
msg.name=False
msg.get_info=False
msg.get_name=False
to bytes: b'*\x00'
msg=Test(on=True, count=0, name='', get_info=CommandGetInfo(), get_name=CommandGetName())
msg_one_of=('on', True)
msg to dict={'on': True}
to bool test:
msg.on=True
msg.count=False
msg.name=False
msg.get_info=False
msg.get_name=False
to bytes: b'\x08\x01'
msg=Test(on=False, count=57, name='', get_info=CommandGetInfo(), get_name=CommandGetName())
msg_one_of=('count', 57)
msg to dict={'count': 57}
to bool test:
msg.on=False
msg.count=True
msg.name=False
msg.get_info=False
msg.get_name=False
to bytes: b'\x109'
msg=Test(on=False, count=0, name='Name', get_info=CommandGetInfo(), get_name=CommandGetName())
msg_one_of=('name', 'Name')
msg to dict={'name': 'Name'}
to bool test:
msg.on=False
msg.count=False
msg.name=True
msg.get_info=False
msg.get_name=False
to bytes: b'\x1a\x04Name'
msg=Test(on=False, count=0, name='', get_info=CommandGetInfo(), get_name=CommandGetName())
msg_one_of=('get_info', CommandGetInfo())
msg to dict={'getInfo': {}}
to bool test:
msg.on=False
msg.count=False
msg.name=False
msg.get_info=False
msg.get_name=False
to bytes: b'"\x00'
Process finished with exit code 0
I think this is fixed in 2.0.0b6, but appears to be broken with pydantic_dataclasses enabled. AFAICT it's doing some extra validation twice - once when creating the Message, and once when serializing the message. The first one works, but the second fails and throws an error.