python-betterproto icon indicating copy to clipboard operation
python-betterproto copied to clipboard

Empty Message in oneof is removed in serialized wire

Open lambdalisue opened this issue 4 years ago • 4 comments

It seems an empty message in oneof clause, like foo in the following example, is lost after serialization.

syntax = "proto3";

package hello;

message Foo {}

message Greeting {
  oneof payload {
    Foo foo = 1;
  }
}

Detail

I've expected that before and after is equivalent in the following code

from dataclasses import dataclass
import betterproto


@dataclass(eq=False, repr=False)
class Foo(betterproto.Message):
    pass


@dataclass(eq=False, repr=False)
class Greeting(betterproto.Message):
    foo: Foo = betterproto.message_field(1, group="payload")


def test_which_one_of():
    before = Greeting()
    before.foo = Foo()

    n, v = betterproto.which_one_of(before, "payload")
    assert n == "foo"
    assert isinstance(v, Foo)

    after = Greeting().parse(bytes(before))
    n, v = betterproto.which_one_of(after, "payload")
    assert n == "foo"
    assert isinstance(v, Foo)

but above shows

$ poetry run pytest test.py
========================================= test session starts =========================================
platform darwin -- Python 3.9.0, pytest-6.2.1, py-1.10.0, pluggy-0.13.1
Using --randomly-seed=3216976549
rootdir: /xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, configfile: pyproject.toml
plugins: cov-2.11.1, asyncio-0.14.0, randomly-3.5.0, icdiff-0.5
collected 1 item

test.py F                                                                                        [100%]

============================================== FAILURES ===============================================
__________________________________________ test_which_one_of __________________________________________

    def test_which_one_of():
        before = Greeting()
        before.foo = Foo()

        n, v = betterproto.which_one_of(before, "payload")
        assert n == "foo"
        assert isinstance(v, Foo)

        after = Greeting().parse(bytes(before))
        n, v = betterproto.which_one_of(after, "payload")
>       assert n == "foo"
E       AssertionError: assert equals failed
E         ''     'foo'

test.py:25: AssertionError
======================================= short test summary info =======================================

lambdalisue avatar Feb 01 '21 14:02 lambdalisue

I've noticed that bytes(...) returns b'' on both case

syntax = "proto3";

package hello;

message Foo {}
message Bar {}

message Greeting {
  oneof payload {
    Foo foo = 1;
    Bar bar = 2;
  }
}
    before = Greeting()
    before.foo = Foo()
    print(bytes(before))

    before = Greeting()
    before.bar = Bar()
    print(bytes(before))

So there is no way to distinguish and I guess it's a kinda protobuf's spec, right?

lambdalisue avatar Feb 01 '21 15:02 lambdalisue

No. It seems it's not spec. I've generated code with protobuf and write the following script (ref https://github.com/protocolbuffers/protobuf/issues/5012)

from hello_pb2 import Greeting, Foo, Bar

greeting = Greeting()
print(greeting.SerializeToString())

greeting.foo.CopyFrom(Foo())
print(greeting.SerializeToString())

greeting.bar.CopyFrom(Bar())
print(greeting.SerializeToString())

Above produce the following result.

b''
b'\n\x00'
b'\x12\x00'

lambdalisue avatar Feb 01 '21 16:02 lambdalisue

Maybe related to https://github.com/danielgtaylor/python-betterproto/issues/179 ?

lambdalisue avatar Feb 01 '21 16:02 lambdalisue

And maybe fixed by https://github.com/danielgtaylor/python-betterproto/pull/176, right?

lambdalisue avatar Feb 01 '21 16:02 lambdalisue

Appears to have been fixed by #176

Gobot1234 avatar Aug 31 '22 13:08 Gobot1234