From 265c07560746545b359daf6de8d0b840a2813020 Mon Sep 17 00:00:00 2001 From: Brady Kieffer Date: Wed, 8 Jul 2020 13:18:53 -0400 Subject: [PATCH 01/11] Serialize default values in oneofs when calling to_dict() or to_json() This change is consistent with the official protobuf implementation. If a default value is set when using a oneof, and then a message is translated from message -> JSON -> message, the default value is kept in tact. Also, if no default value is set, they remain null. --- betterproto/__init__.py | 9 ++++- .../oneof_default_value_serialization.proto | 8 ++++ .../test_oneof_default_value_serialization.py | 38 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto create mode 100644 betterproto/tests/inputs/oneof_default_value_serialization/test_oneof_default_value_serialization.py diff --git a/betterproto/__init__.py b/betterproto/__init__.py index 6fb20041e..7718c2d4b 100644 --- a/betterproto/__init__.py +++ b/betterproto/__init__.py @@ -814,7 +814,14 @@ def to_dict( if v or include_default_values: output[cased_name] = v - elif v != self._get_field_default(field_name) or include_default_values: + elif ( + v != self._get_field_default(field_name) + or include_default_values + or ( + meta.group is not None + and self._group_current.get(meta.group) == field_name + ) + ): if meta.proto_type in INT_64_TYPES: if isinstance(v, list): output[cased_name] = [str(n) for n in v] diff --git a/betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto b/betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto new file mode 100644 index 000000000..6ec111951 --- /dev/null +++ b/betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +message Test{ + oneof value_type { + bool bool_value = 1; + int64 int64_value = 2; + } +} \ No newline at end of file diff --git a/betterproto/tests/inputs/oneof_default_value_serialization/test_oneof_default_value_serialization.py b/betterproto/tests/inputs/oneof_default_value_serialization/test_oneof_default_value_serialization.py new file mode 100644 index 000000000..a81781a0a --- /dev/null +++ b/betterproto/tests/inputs/oneof_default_value_serialization/test_oneof_default_value_serialization.py @@ -0,0 +1,38 @@ +import pytest + +import betterproto +from betterproto.tests.output_betterproto.oneof_default_value_serialization import Test + + +def test_oneof_default_value_serialization(): + """ + Serialization from message with oneof set to default -> JSON -> message should keep + default value field intact. + """ + message = Test(bool_value=False) + message2 = Test().from_json(message.to_json()) + + assert betterproto.which_one_of(message, "value_type") == betterproto.which_one_of( + message2, "value_type" + ) + + message.int64_value = 0 + message2 = Test().from_json(message.to_json()) + assert betterproto.which_one_of(message, "value_type") == betterproto.which_one_of( + message2, "value_type" + ) + + +def test_oneof_no_default_values_passed(): + """ + Serialization from message with oneof set to default -> JSON -> message should keep + default value field intact. + """ + message = Test() + message2 = Test().from_json(message.to_json()) + + assert ( + betterproto.which_one_of(message, "value_type") + == betterproto.which_one_of(message2, "value_type") + == ("", None) + ) From d9eab6d787306f9c2d355d8ba892b1629cde04b1 Mon Sep 17 00:00:00 2001 From: Brady Kieffer Date: Wed, 8 Jul 2020 17:47:26 -0400 Subject: [PATCH 02/11] Some cleanup + testing for nested messages with oneofs --- betterproto/__init__.py | 43 ++++++++--- .../oneof_default_value_serialization.proto | 20 +++++ .../test_oneof_default_value_serialization.py | 74 ++++++++++++++----- 3 files changed, 109 insertions(+), 28 deletions(-) diff --git a/betterproto/__init__.py b/betterproto/__init__.py index 7718c2d4b..b24ded083 100644 --- a/betterproto/__init__.py +++ b/betterproto/__init__.py @@ -719,6 +719,13 @@ def _postprocess_single( return value + def _include_default_value_for_oneof( + self, field_name: str, meta: FieldMetadata + ) -> bool: + return ( + meta.group is not None and self._group_current.get(meta.group) == field_name + ) + def parse(self: T, data: bytes) -> T: """ Parse the binary encoded Protobuf into this message instance. This @@ -791,10 +798,22 @@ def to_dict( cased_name = casing(field_name).rstrip("_") # type: ignore if meta.proto_type == "message": if isinstance(v, datetime): - if v != DATETIME_ZERO or include_default_values: + if ( + v != DATETIME_ZERO + or include_default_values + or self._include_default_value_for_oneof( + field_name=field_name, meta=meta + ) + ): output[cased_name] = _Timestamp.timestamp_to_json(v) elif isinstance(v, timedelta): - if v != timedelta(0) or include_default_values: + if ( + v != timedelta(0) + or include_default_values + or self._include_default_value_for_oneof( + field_name=field_name, meta=meta + ) + ): output[cased_name] = _Duration.delta_to_json(v) elif meta.wraps: if v is not None or include_default_values: @@ -804,9 +823,14 @@ def to_dict( v = [i.to_dict(casing, include_default_values) for i in v] if v or include_default_values: output[cased_name] = v - else: - if v._serialized_on_wire or include_default_values: - output[cased_name] = v.to_dict(casing, include_default_values) + elif ( + v._serialized_on_wire + or include_default_values + or self._include_default_value_for_oneof( + field_name=field_name, meta=meta + ) + ): + output[cased_name] = v.to_dict(casing, include_default_values,) elif meta.proto_type == "map": for k in v: if hasattr(v[k], "to_dict"): @@ -817,9 +841,8 @@ def to_dict( elif ( v != self._get_field_default(field_name) or include_default_values - or ( - meta.group is not None - and self._group_current.get(meta.group) == field_name + or self._include_default_value_for_oneof( + field_name=field_name, meta=meta ) ): if meta.proto_type in INT_64_TYPES: @@ -873,7 +896,9 @@ def from_dict(self: T, value: dict) -> T: elif meta.wraps: setattr(self, field_name, value[key]) else: - v.from_dict(value[key]) + v = v.from_dict(value[key]) + if v is not None: + setattr(self, field_name, v) elif meta.map_types and meta.map_types[1] == TYPE_MESSAGE: v = getattr(self, field_name) cls = self._betterproto.cls_by_field[field_name + ".value"] diff --git a/betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto b/betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto index 6ec111951..44163c70a 100644 --- a/betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto +++ b/betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto @@ -1,8 +1,28 @@ syntax = "proto3"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +message Message{ + int64 value = 1; +} + +message NestedMessage{ + int64 id = 1; + oneof value_type{ + Message wrapped_message_value = 2; + } +} + message Test{ oneof value_type { bool bool_value = 1; int64 int64_value = 2; + google.protobuf.Timestamp timestamp_value = 3; + google.protobuf.Duration duration_value = 4; + Message wrapped_message_value = 5; + NestedMessage wrapped_nested_message_value = 6; + google.protobuf.BoolValue wrapped_bool_value = 7; } } \ No newline at end of file diff --git a/betterproto/tests/inputs/oneof_default_value_serialization/test_oneof_default_value_serialization.py b/betterproto/tests/inputs/oneof_default_value_serialization/test_oneof_default_value_serialization.py index a81781a0a..de089d3bb 100644 --- a/betterproto/tests/inputs/oneof_default_value_serialization/test_oneof_default_value_serialization.py +++ b/betterproto/tests/inputs/oneof_default_value_serialization/test_oneof_default_value_serialization.py @@ -1,38 +1,74 @@ import pytest +import datetime import betterproto -from betterproto.tests.output_betterproto.oneof_default_value_serialization import Test +from betterproto.tests.output_betterproto.oneof_default_value_serialization import ( + Test, + Message, + NestedMessage, +) -def test_oneof_default_value_serialization(): +def assert_round_trip_serialization_works(message: Test) -> None: + assert betterproto.which_one_of(message, "value_type") == betterproto.which_one_of( + Test().from_json(message.to_json()), "value_type" + ) + + +def test_oneof_default_value_serialization_works_for_all_values(): """ Serialization from message with oneof set to default -> JSON -> message should keep default value field intact. """ - message = Test(bool_value=False) - message2 = Test().from_json(message.to_json()) - assert betterproto.which_one_of(message, "value_type") == betterproto.which_one_of( - message2, "value_type" - ) + test_cases = [ + Test(bool_value=False), + Test(int64_value=0), + Test( + timestamp_value=datetime.datetime( + year=1970, + month=1, + day=1, + hour=0, + minute=0, + tzinfo=datetime.timezone.utc, + ) + ), + Test(duration_value=datetime.timedelta(0)), + Test(wrapped_message_value=Message(value=0)), + # NOTE: Do NOT use betterproto.BoolValue here, it will cause JSON serialization + # errors. + # TODO: Do we want to allow use of BoolValue directly within a wrapped field or + # should we simply hard fail here? + Test(wrapped_bool_value=False), + ] + for message in test_cases: + assert_round_trip_serialization_works(message) - message.int64_value = 0 - message2 = Test().from_json(message.to_json()) - assert betterproto.which_one_of(message, "value_type") == betterproto.which_one_of( - message2, "value_type" + +def test_oneof_no_default_values_passed(): + message = Test() + assert ( + betterproto.which_one_of(message, "value_type") + == betterproto.which_one_of(Test().from_json(message.to_json()), "value_type") + == ("", None) ) -def test_oneof_no_default_values_passed(): +def test_oneof_nested_oneof_messages_are_serialized_with_defaults(): """ - Serialization from message with oneof set to default -> JSON -> message should keep - default value field intact. + Nested messages with oneofs should also be handled """ - message = Test() - message2 = Test().from_json(message.to_json()) - + message = Test( + wrapped_nested_message_value=NestedMessage( + id=0, wrapped_message_value=Message(value=0) + ) + ) assert ( betterproto.which_one_of(message, "value_type") - == betterproto.which_one_of(message2, "value_type") - == ("", None) + == betterproto.which_one_of(Test().from_json(message.to_json()), "value_type") + == ( + "wrapped_nested_message_value", + NestedMessage(id=0, wrapped_message_value=Message(value=0)), + ) ) From 4fc2f717278a62dcfe1f3eac6a641a8d12120565 Mon Sep 17 00:00:00 2001 From: Brady Kieffer Date: Wed, 8 Jul 2020 18:11:13 -0400 Subject: [PATCH 03/11] Cleanup oneof_enum test cases, they should be fixed This _should_ address: https://github.com/danielgtaylor/python-betterproto/issues/63 --- .../tests/inputs/oneof_enum/test_oneof_enum.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/betterproto/tests/inputs/oneof_enum/test_oneof_enum.py b/betterproto/tests/inputs/oneof_enum/test_oneof_enum.py index ae9d40da5..82bcb6ce3 100644 --- a/betterproto/tests/inputs/oneof_enum/test_oneof_enum.py +++ b/betterproto/tests/inputs/oneof_enum/test_oneof_enum.py @@ -9,34 +9,36 @@ from betterproto.tests.util import get_test_case_json_data -@pytest.mark.xfail def test_which_one_of_returns_enum_with_default_value(): """ returns first field when it is enum and set with default value """ message = Test() message.from_json(get_test_case_json_data("oneof_enum", "oneof_enum-enum-0.json")) - assert message.move is None + + assert message.move == Move( + x=0, y=0 + ) # Proto3 will default this as there is no null assert message.signal == Signal.PASS assert betterproto.which_one_of(message, "action") == ("signal", Signal.PASS) -@pytest.mark.xfail def test_which_one_of_returns_enum_with_non_default_value(): """ returns first field when it is enum and set with non default value """ message = Test() message.from_json(get_test_case_json_data("oneof_enum", "oneof_enum-enum-1.json")) - assert message.move is None - assert message.signal == Signal.PASS + assert message.move == Move( + x=0, y=0 + ) # Proto3 will default this as there is no null + assert message.signal == Signal.RESIGN assert betterproto.which_one_of(message, "action") == ("signal", Signal.RESIGN) -@pytest.mark.xfail def test_which_one_of_returns_second_field_when_set(): message = Test() message.from_json(get_test_case_json_data("oneof_enum")) assert message.move == Move(x=2, y=3) - assert message.signal == 0 + assert message.signal == Signal.PASS assert betterproto.which_one_of(message, "action") == ("move", Move(x=2, y=3)) From 68d58b36a696180d964a6597a83bd88c3246f9d5 Mon Sep 17 00:00:00 2001 From: Brady Kieffer Date: Wed, 8 Jul 2020 18:24:02 -0400 Subject: [PATCH 04/11] Include default value oneof fields when serializing to bytes This will cause oneof fields with default values to explicitly be sent to clients. Note that does not mean that all fields are serialized and sent to clients, just those that _could_ be null and are not. --- betterproto/__init__.py | 6 +++++- betterproto/tests/test_features.py | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/betterproto/__init__.py b/betterproto/__init__.py index b24ded083..2d00a891a 100644 --- a/betterproto/__init__.py +++ b/betterproto/__init__.py @@ -591,7 +591,11 @@ def __bytes__(self) -> bytes: serialize_empty = True if value == self._get_field_default(field_name) and not ( - selected_in_group or serialize_empty + selected_in_group + or serialize_empty + or self._include_default_value_for_oneof( + field_name=field_name, meta=meta + ) ): # Default (zero) values are not serialized. Two exceptions are # if this is the selected oneof item or if we know we have to diff --git a/betterproto/tests/test_features.py b/betterproto/tests/test_features.py index 3a4a7c703..6917aa91f 100644 --- a/betterproto/tests/test_features.py +++ b/betterproto/tests/test_features.py @@ -263,3 +263,14 @@ class TestParentMessage(betterproto.Message): "someDouble": 1.2, "someMessage": {"someOtherInt": 0}, } + + +def test_oneof_default_value_set_causes_writes_wire(): + @dataclass + class Foo(betterproto.Message): + bar: int = betterproto.int32_field(1, group="group1") + baz: str = betterproto.string_field(2, group="group1") + + assert bytes(Foo(bar=0)) != b"" + assert bytes(Foo(baz="")) == b"" # Baz is just an empty string + assert bytes(Foo()) == b"" From 72f96863f85357b8257dfbd706bd2609ec463875 Mon Sep 17 00:00:00 2001 From: Brady Kieffer Date: Thu, 9 Jul 2020 18:10:25 -0400 Subject: [PATCH 05/11] Remove assignment when populating a sub-message within a proto Also, move setattr out one indentation level --- betterproto/__init__.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/betterproto/__init__.py b/betterproto/__init__.py index 2d00a891a..46f8649be 100644 --- a/betterproto/__init__.py +++ b/betterproto/__init__.py @@ -900,9 +900,7 @@ def from_dict(self: T, value: dict) -> T: elif meta.wraps: setattr(self, field_name, value[key]) else: - v = v.from_dict(value[key]) - if v is not None: - setattr(self, field_name, v) + v.from_dict(value[key]) elif meta.map_types and meta.map_types[1] == TYPE_MESSAGE: v = getattr(self, field_name) cls = self._betterproto.cls_by_field[field_name + ".value"] @@ -927,8 +925,8 @@ def from_dict(self: T, value: dict) -> T: elif isinstance(v, str): v = enum_cls.from_string(v) - if v is not None: - setattr(self, field_name, v) + if v is not None: + setattr(self, field_name, v) return self def to_json(self, indent: Union[None, int, str] = None) -> str: From 4252a10e306d24bb42905ac97d33c4d97914e146 Mon Sep 17 00:00:00 2001 From: Brady Kieffer Date: Thu, 9 Jul 2020 18:13:05 -0400 Subject: [PATCH 06/11] Properly transform proto with empty string in oneof to bytes Also, updated tests to ensure that which_one_of picks up the set field --- betterproto/__init__.py | 21 ++++++++++----------- betterproto/tests/test_features.py | 30 +++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/betterproto/__init__.py b/betterproto/__init__.py index 46f8649be..9ec9c8ae6 100644 --- a/betterproto/__init__.py +++ b/betterproto/__init__.py @@ -580,22 +580,18 @@ def __bytes__(self) -> bytes: # Being selected in a a group means this field is the one that is # currently set in a `oneof` group, so it must be serialized even # if the value is the default zero value. - selected_in_group = False - if meta.group and self._group_current[meta.group] == field_name: - selected_in_group = True + selected_in_group =meta.group and self._group_current[meta.group] == field_name - serialize_empty = False - if isinstance(value, Message) and value._serialized_on_wire: - # Empty messages can still be sent on the wire if they were - # set (or received empty). - serialize_empty = True + # Empty messages can still be sent on the wire if they were + # set (or received empty). + serialize_empty = isinstance(value, Message) and value._serialized_on_wire + + include_default_value_for_oneof =self._include_default_value_for_oneof( field_name=field_name, meta=meta) if value == self._get_field_default(field_name) and not ( selected_in_group or serialize_empty - or self._include_default_value_for_oneof( - field_name=field_name, meta=meta - ) + or include_default_value_for_oneof ): # Default (zero) values are not serialized. Two exceptions are # if this is the selected oneof item or if we know we have to @@ -624,6 +620,9 @@ def __bytes__(self) -> bytes: sv = _serialize_single(2, meta.map_types[1], v) output += _serialize_single(meta.number, meta.proto_type, sk + sv) else: + if isinstance(value, str) and value == "" and include_default_value_for_oneof: + serialize_empty = True + output += _serialize_single( meta.number, meta.proto_type, diff --git a/betterproto/tests/test_features.py b/betterproto/tests/test_features.py index 6917aa91f..509c2fb65 100644 --- a/betterproto/tests/test_features.py +++ b/betterproto/tests/test_features.py @@ -271,6 +271,30 @@ class Foo(betterproto.Message): bar: int = betterproto.int32_field(1, group="group1") baz: str = betterproto.string_field(2, group="group1") - assert bytes(Foo(bar=0)) != b"" - assert bytes(Foo(baz="")) == b"" # Baz is just an empty string - assert bytes(Foo()) == b"" + def _round_trip_serialization(foo: Foo) -> Foo: + return Foo().parse(bytes(foo)) + + foo1 = Foo(bar=0) + foo2 = Foo(baz="") + foo3 = Foo() + + assert bytes(foo1) == b"\x08\x00" + assert ( + betterproto.which_one_of(foo1, "group1") + == betterproto.which_one_of(_round_trip_serialization(foo1), "group1") + == ("bar", 0) + ) + + assert bytes(foo2) == b"\x12\x00" # Baz is just an empty string + assert ( + betterproto.which_one_of(foo2, "group1") + == betterproto.which_one_of(_round_trip_serialization(foo2), "group1") + == ("baz", "") + ) + + assert bytes(foo3) == b"" + assert ( + betterproto.which_one_of(foo3, "group1") + == betterproto.which_one_of(_round_trip_serialization(foo3), "group1") + == ("", None) + ) From 0726f7f4591e3a5296ddd68c46c10dd9cc54d5ca Mon Sep 17 00:00:00 2001 From: Brady Kieffer Date: Thu, 9 Jul 2020 19:55:29 -0400 Subject: [PATCH 07/11] Formatting betterproto/__init__.py --- betterproto/__init__.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/betterproto/__init__.py b/betterproto/__init__.py index 9ec9c8ae6..ec856fce6 100644 --- a/betterproto/__init__.py +++ b/betterproto/__init__.py @@ -580,18 +580,20 @@ def __bytes__(self) -> bytes: # Being selected in a a group means this field is the one that is # currently set in a `oneof` group, so it must be serialized even # if the value is the default zero value. - selected_in_group =meta.group and self._group_current[meta.group] == field_name + selected_in_group = ( + meta.group and self._group_current[meta.group] == field_name + ) # Empty messages can still be sent on the wire if they were # set (or received empty). serialize_empty = isinstance(value, Message) and value._serialized_on_wire - include_default_value_for_oneof =self._include_default_value_for_oneof( field_name=field_name, meta=meta) + include_default_value_for_oneof = self._include_default_value_for_oneof( + field_name=field_name, meta=meta + ) if value == self._get_field_default(field_name) and not ( - selected_in_group - or serialize_empty - or include_default_value_for_oneof + selected_in_group or serialize_empty or include_default_value_for_oneof ): # Default (zero) values are not serialized. Two exceptions are # if this is the selected oneof item or if we know we have to @@ -620,7 +622,11 @@ def __bytes__(self) -> bytes: sv = _serialize_single(2, meta.map_types[1], v) output += _serialize_single(meta.number, meta.proto_type, sk + sv) else: - if isinstance(value, str) and value == "" and include_default_value_for_oneof: + if ( + isinstance(value, str) + and value == "" + and include_default_value_for_oneof + ): serialize_empty = True output += _serialize_single( From 4789636cb4d9d5fbee1f29ddb17e6675322febef Mon Sep 17 00:00:00 2001 From: Brady Kieffer Date: Thu, 9 Jul 2020 19:55:45 -0400 Subject: [PATCH 08/11] Adding test cases demonstrating equivalent behaviour with google impl --- .../google_impl_behavior_equivalence.proto | 13 + .../test_google_impl_behavior_equivalence.py | 55 +++ .../oneof_default_value_serialization_pb2.py | 439 ++++++++++++++++++ 3 files changed, 507 insertions(+) create mode 100644 betterproto/tests/inputs/google_impl_behavior_equivalence/google_impl_behavior_equivalence.proto create mode 100644 betterproto/tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py create mode 100644 betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization_pb2.py diff --git a/betterproto/tests/inputs/google_impl_behavior_equivalence/google_impl_behavior_equivalence.proto b/betterproto/tests/inputs/google_impl_behavior_equivalence/google_impl_behavior_equivalence.proto new file mode 100644 index 000000000..e54212c2a --- /dev/null +++ b/betterproto/tests/inputs/google_impl_behavior_equivalence/google_impl_behavior_equivalence.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +message Foo{ + int64 bar = 1; +} + +message OneOfTest{ + oneof group{ + string string = 1; + int64 integer = 2; + Foo foo = 3; + } +} diff --git a/betterproto/tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py b/betterproto/tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py new file mode 100644 index 000000000..e3c6b2aab --- /dev/null +++ b/betterproto/tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py @@ -0,0 +1,55 @@ +import pytest + +from google.protobuf import json_format +import betterproto +from betterproto.tests.output_betterproto.google_impl_behavior_equivalence import ( + OneOfTest, + Foo, +) +from betterproto.tests.output_reference.google_impl_behavior_equivalence.google_impl_behavior_equivalence_pb2 import ( + OneOfTest as ReferenceOneOfTest, + Foo as ReferenceFoo, +) + + +def test_oneof_serializes_similar_to_google_oneof(): + + tests = [ + (OneOfTest(string="abc"), ReferenceOneOfTest(string="abc")), + (OneOfTest(integer=2), ReferenceOneOfTest(integer=2)), + (OneOfTest(foo=Foo(bar=1)), ReferenceOneOfTest(foo=ReferenceFoo(bar=1))), + # Default values should also behave the same within oneofs + (OneOfTest(string=""), ReferenceOneOfTest(string="")), + (OneOfTest(integer=0), ReferenceOneOfTest(integer=0)), + (OneOfTest(foo=Foo(bar=0)), ReferenceOneOfTest(foo=ReferenceFoo(bar=0))), + ] + for message, message_reference in tests: + # NOTE: As of July 2020, MessageToJson inserts newlines in the output string so, + # just compare dicts + assert message.to_dict() == json_format.MessageToDict(message_reference) + + +def test_bytes_are_the_same_for_oneof(): + + message = OneOfTest(string="") + message_reference = ReferenceOneOfTest(string="") + + message_bytes = bytes(message) + message_reference_bytes = message_reference.SerializeToString() + + assert message_bytes == message_reference_bytes + + message2 = OneOfTest().parse(message_reference_bytes) + message_reference2 = ReferenceOneOfTest() + message_reference2.ParseFromString(message_reference_bytes) + + assert message == message2 + assert message_reference == message_reference2 + + # None of these fields were explicitly set BUT they should not actually be null + # themselves + assert isinstance(message.foo, Foo) + assert isinstance(message2.foo, Foo) + + assert isinstance(message_reference.foo, ReferenceFoo) + assert isinstance(message_reference2.foo, ReferenceFoo) diff --git a/betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization_pb2.py b/betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization_pb2.py new file mode 100644 index 000000000..fe07606c1 --- /dev/null +++ b/betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization_pb2.py @@ -0,0 +1,439 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2 +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 +from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2 + + +DESCRIPTOR = _descriptor.FileDescriptor( + name="betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto", + package="", + syntax="proto3", + serialized_options=None, + create_key=_descriptor._internal_create_key, + serialized_pb=b'\nbbetterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto"(\n\x07Message\x12\r\n\x05value\x18\x01 \x01(\x03\x12\x0e\n\x06string\x18\x02 \x01(\t"T\n\rNestedMessage\x12\n\n\x02id\x18\x01 \x01(\x03\x12)\n\x15wrapped_message_value\x18\x02 \x01(\x0b\x32\x08.MessageH\x00\x42\x0c\n\nvalue_type"\xdf\x02\n\x04Test\x12\x14\n\nbool_value\x18\x01 \x01(\x08H\x00\x12\x15\n\x0bint64_value\x18\x02 \x01(\x03H\x00\x12\x35\n\x0ftimestamp_value\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00\x12\x33\n\x0e\x64uration_value\x18\x04 \x01(\x0b\x32\x19.google.protobuf.DurationH\x00\x12)\n\x15wrapped_message_value\x18\x05 \x01(\x0b\x32\x08.MessageH\x00\x12\x36\n\x1cwrapped_nested_message_value\x18\x06 \x01(\x0b\x32\x0e.NestedMessageH\x00\x12\x38\n\x12wrapped_bool_value\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.BoolValueH\x00\x12\x13\n\tstr_value\x18\x65 \x01(\tH\x00\x42\x0c\n\nvalue_typeb\x06proto3', + dependencies=[ + google_dot_protobuf_dot_duration__pb2.DESCRIPTOR, + google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR, + google_dot_protobuf_dot_wrappers__pb2.DESCRIPTOR, + ], +) + + +_MESSAGE = _descriptor.Descriptor( + name="Message", + full_name="Message", + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name="value", + full_name="Message.value", + index=0, + number=1, + type=3, + cpp_type=2, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + ), + _descriptor.FieldDescriptor( + name="string", + full_name="Message.string", + index=1, + number=2, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=b"".decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[], + serialized_start=199, + serialized_end=239, +) + + +_NESTEDMESSAGE = _descriptor.Descriptor( + name="NestedMessage", + full_name="NestedMessage", + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name="id", + full_name="NestedMessage.id", + index=0, + number=1, + type=3, + cpp_type=2, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + ), + _descriptor.FieldDescriptor( + name="wrapped_message_value", + full_name="NestedMessage.wrapped_message_value", + index=1, + number=2, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name="value_type", + full_name="NestedMessage.value_type", + index=0, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[], + ), + ], + serialized_start=241, + serialized_end=325, +) + + +_TEST = _descriptor.Descriptor( + name="Test", + full_name="Test", + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name="bool_value", + full_name="Test.bool_value", + index=0, + number=1, + type=8, + cpp_type=7, + label=1, + has_default_value=False, + default_value=False, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + ), + _descriptor.FieldDescriptor( + name="int64_value", + full_name="Test.int64_value", + index=1, + number=2, + type=3, + cpp_type=2, + label=1, + has_default_value=False, + default_value=0, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + ), + _descriptor.FieldDescriptor( + name="timestamp_value", + full_name="Test.timestamp_value", + index=2, + number=3, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + ), + _descriptor.FieldDescriptor( + name="duration_value", + full_name="Test.duration_value", + index=3, + number=4, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + ), + _descriptor.FieldDescriptor( + name="wrapped_message_value", + full_name="Test.wrapped_message_value", + index=4, + number=5, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + ), + _descriptor.FieldDescriptor( + name="wrapped_nested_message_value", + full_name="Test.wrapped_nested_message_value", + index=5, + number=6, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + ), + _descriptor.FieldDescriptor( + name="wrapped_bool_value", + full_name="Test.wrapped_bool_value", + index=6, + number=7, + type=11, + cpp_type=10, + label=1, + has_default_value=False, + default_value=None, + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + ), + _descriptor.FieldDescriptor( + name="str_value", + full_name="Test.str_value", + index=7, + number=101, + type=9, + cpp_type=9, + label=1, + has_default_value=False, + default_value=b"".decode("utf-8"), + message_type=None, + enum_type=None, + containing_type=None, + is_extension=False, + extension_scope=None, + serialized_options=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + ), + ], + extensions=[], + nested_types=[], + enum_types=[], + serialized_options=None, + is_extendable=False, + syntax="proto3", + extension_ranges=[], + oneofs=[ + _descriptor.OneofDescriptor( + name="value_type", + full_name="Test.value_type", + index=0, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[], + ), + ], + serialized_start=328, + serialized_end=679, +) + +_NESTEDMESSAGE.fields_by_name["wrapped_message_value"].message_type = _MESSAGE +_NESTEDMESSAGE.oneofs_by_name["value_type"].fields.append( + _NESTEDMESSAGE.fields_by_name["wrapped_message_value"] +) +_NESTEDMESSAGE.fields_by_name[ + "wrapped_message_value" +].containing_oneof = _NESTEDMESSAGE.oneofs_by_name["value_type"] +_TEST.fields_by_name[ + "timestamp_value" +].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP +_TEST.fields_by_name[ + "duration_value" +].message_type = google_dot_protobuf_dot_duration__pb2._DURATION +_TEST.fields_by_name["wrapped_message_value"].message_type = _MESSAGE +_TEST.fields_by_name["wrapped_nested_message_value"].message_type = _NESTEDMESSAGE +_TEST.fields_by_name[ + "wrapped_bool_value" +].message_type = google_dot_protobuf_dot_wrappers__pb2._BOOLVALUE +_TEST.oneofs_by_name["value_type"].fields.append(_TEST.fields_by_name["bool_value"]) +_TEST.fields_by_name["bool_value"].containing_oneof = _TEST.oneofs_by_name["value_type"] +_TEST.oneofs_by_name["value_type"].fields.append(_TEST.fields_by_name["int64_value"]) +_TEST.fields_by_name["int64_value"].containing_oneof = _TEST.oneofs_by_name[ + "value_type" +] +_TEST.oneofs_by_name["value_type"].fields.append( + _TEST.fields_by_name["timestamp_value"] +) +_TEST.fields_by_name["timestamp_value"].containing_oneof = _TEST.oneofs_by_name[ + "value_type" +] +_TEST.oneofs_by_name["value_type"].fields.append(_TEST.fields_by_name["duration_value"]) +_TEST.fields_by_name["duration_value"].containing_oneof = _TEST.oneofs_by_name[ + "value_type" +] +_TEST.oneofs_by_name["value_type"].fields.append( + _TEST.fields_by_name["wrapped_message_value"] +) +_TEST.fields_by_name["wrapped_message_value"].containing_oneof = _TEST.oneofs_by_name[ + "value_type" +] +_TEST.oneofs_by_name["value_type"].fields.append( + _TEST.fields_by_name["wrapped_nested_message_value"] +) +_TEST.fields_by_name[ + "wrapped_nested_message_value" +].containing_oneof = _TEST.oneofs_by_name["value_type"] +_TEST.oneofs_by_name["value_type"].fields.append( + _TEST.fields_by_name["wrapped_bool_value"] +) +_TEST.fields_by_name["wrapped_bool_value"].containing_oneof = _TEST.oneofs_by_name[ + "value_type" +] +_TEST.oneofs_by_name["value_type"].fields.append(_TEST.fields_by_name["str_value"]) +_TEST.fields_by_name["str_value"].containing_oneof = _TEST.oneofs_by_name["value_type"] +DESCRIPTOR.message_types_by_name["Message"] = _MESSAGE +DESCRIPTOR.message_types_by_name["NestedMessage"] = _NESTEDMESSAGE +DESCRIPTOR.message_types_by_name["Test"] = _TEST +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +Message = _reflection.GeneratedProtocolMessageType( + "Message", + (_message.Message,), + { + "DESCRIPTOR": _MESSAGE, + "__module__": "betterproto.tests.inputs.oneof_default_value_serialization.oneof_default_value_serialization_pb2" + # @@protoc_insertion_point(class_scope:Message) + }, +) +_sym_db.RegisterMessage(Message) + +NestedMessage = _reflection.GeneratedProtocolMessageType( + "NestedMessage", + (_message.Message,), + { + "DESCRIPTOR": _NESTEDMESSAGE, + "__module__": "betterproto.tests.inputs.oneof_default_value_serialization.oneof_default_value_serialization_pb2" + # @@protoc_insertion_point(class_scope:NestedMessage) + }, +) +_sym_db.RegisterMessage(NestedMessage) + +Test = _reflection.GeneratedProtocolMessageType( + "Test", + (_message.Message,), + { + "DESCRIPTOR": _TEST, + "__module__": "betterproto.tests.inputs.oneof_default_value_serialization.oneof_default_value_serialization_pb2" + # @@protoc_insertion_point(class_scope:Test) + }, +) +_sym_db.RegisterMessage(Test) + + +# @@protoc_insertion_point(module_scope) From f904b5ce4a5d3686dbc9f28e833ec19feba75404 Mon Sep 17 00:00:00 2001 From: Brady Kieffer Date: Thu, 9 Jul 2020 19:56:22 -0400 Subject: [PATCH 09/11] Removing a temporary file I made locally --- .../oneof_default_value_serialization_pb2.py | 439 ------------------ 1 file changed, 439 deletions(-) delete mode 100644 betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization_pb2.py diff --git a/betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization_pb2.py b/betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization_pb2.py deleted file mode 100644 index fe07606c1..000000000 --- a/betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization_pb2.py +++ /dev/null @@ -1,439 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database - -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import duration_pb2 as google_dot_protobuf_dot_duration__pb2 -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -from google.protobuf import wrappers_pb2 as google_dot_protobuf_dot_wrappers__pb2 - - -DESCRIPTOR = _descriptor.FileDescriptor( - name="betterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto", - package="", - syntax="proto3", - serialized_options=None, - create_key=_descriptor._internal_create_key, - serialized_pb=b'\nbbetterproto/tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto"(\n\x07Message\x12\r\n\x05value\x18\x01 \x01(\x03\x12\x0e\n\x06string\x18\x02 \x01(\t"T\n\rNestedMessage\x12\n\n\x02id\x18\x01 \x01(\x03\x12)\n\x15wrapped_message_value\x18\x02 \x01(\x0b\x32\x08.MessageH\x00\x42\x0c\n\nvalue_type"\xdf\x02\n\x04Test\x12\x14\n\nbool_value\x18\x01 \x01(\x08H\x00\x12\x15\n\x0bint64_value\x18\x02 \x01(\x03H\x00\x12\x35\n\x0ftimestamp_value\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampH\x00\x12\x33\n\x0e\x64uration_value\x18\x04 \x01(\x0b\x32\x19.google.protobuf.DurationH\x00\x12)\n\x15wrapped_message_value\x18\x05 \x01(\x0b\x32\x08.MessageH\x00\x12\x36\n\x1cwrapped_nested_message_value\x18\x06 \x01(\x0b\x32\x0e.NestedMessageH\x00\x12\x38\n\x12wrapped_bool_value\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.BoolValueH\x00\x12\x13\n\tstr_value\x18\x65 \x01(\tH\x00\x42\x0c\n\nvalue_typeb\x06proto3', - dependencies=[ - google_dot_protobuf_dot_duration__pb2.DESCRIPTOR, - google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR, - google_dot_protobuf_dot_wrappers__pb2.DESCRIPTOR, - ], -) - - -_MESSAGE = _descriptor.Descriptor( - name="Message", - full_name="Message", - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name="value", - full_name="Message.value", - index=0, - number=1, - type=3, - cpp_type=2, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="string", - full_name="Message.string", - index=1, - number=2, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=b"".decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[], - serialized_start=199, - serialized_end=239, -) - - -_NESTEDMESSAGE = _descriptor.Descriptor( - name="NestedMessage", - full_name="NestedMessage", - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name="id", - full_name="NestedMessage.id", - index=0, - number=1, - type=3, - cpp_type=2, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="wrapped_message_value", - full_name="NestedMessage.wrapped_message_value", - index=1, - number=2, - type=11, - cpp_type=10, - label=1, - has_default_value=False, - default_value=None, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name="value_type", - full_name="NestedMessage.value_type", - index=0, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[], - ), - ], - serialized_start=241, - serialized_end=325, -) - - -_TEST = _descriptor.Descriptor( - name="Test", - full_name="Test", - filename=None, - file=DESCRIPTOR, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[ - _descriptor.FieldDescriptor( - name="bool_value", - full_name="Test.bool_value", - index=0, - number=1, - type=8, - cpp_type=7, - label=1, - has_default_value=False, - default_value=False, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="int64_value", - full_name="Test.int64_value", - index=1, - number=2, - type=3, - cpp_type=2, - label=1, - has_default_value=False, - default_value=0, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="timestamp_value", - full_name="Test.timestamp_value", - index=2, - number=3, - type=11, - cpp_type=10, - label=1, - has_default_value=False, - default_value=None, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="duration_value", - full_name="Test.duration_value", - index=3, - number=4, - type=11, - cpp_type=10, - label=1, - has_default_value=False, - default_value=None, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="wrapped_message_value", - full_name="Test.wrapped_message_value", - index=4, - number=5, - type=11, - cpp_type=10, - label=1, - has_default_value=False, - default_value=None, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="wrapped_nested_message_value", - full_name="Test.wrapped_nested_message_value", - index=5, - number=6, - type=11, - cpp_type=10, - label=1, - has_default_value=False, - default_value=None, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="wrapped_bool_value", - full_name="Test.wrapped_bool_value", - index=6, - number=7, - type=11, - cpp_type=10, - label=1, - has_default_value=False, - default_value=None, - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - _descriptor.FieldDescriptor( - name="str_value", - full_name="Test.str_value", - index=7, - number=101, - type=9, - cpp_type=9, - label=1, - has_default_value=False, - default_value=b"".decode("utf-8"), - message_type=None, - enum_type=None, - containing_type=None, - is_extension=False, - extension_scope=None, - serialized_options=None, - file=DESCRIPTOR, - create_key=_descriptor._internal_create_key, - ), - ], - extensions=[], - nested_types=[], - enum_types=[], - serialized_options=None, - is_extendable=False, - syntax="proto3", - extension_ranges=[], - oneofs=[ - _descriptor.OneofDescriptor( - name="value_type", - full_name="Test.value_type", - index=0, - containing_type=None, - create_key=_descriptor._internal_create_key, - fields=[], - ), - ], - serialized_start=328, - serialized_end=679, -) - -_NESTEDMESSAGE.fields_by_name["wrapped_message_value"].message_type = _MESSAGE -_NESTEDMESSAGE.oneofs_by_name["value_type"].fields.append( - _NESTEDMESSAGE.fields_by_name["wrapped_message_value"] -) -_NESTEDMESSAGE.fields_by_name[ - "wrapped_message_value" -].containing_oneof = _NESTEDMESSAGE.oneofs_by_name["value_type"] -_TEST.fields_by_name[ - "timestamp_value" -].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP -_TEST.fields_by_name[ - "duration_value" -].message_type = google_dot_protobuf_dot_duration__pb2._DURATION -_TEST.fields_by_name["wrapped_message_value"].message_type = _MESSAGE -_TEST.fields_by_name["wrapped_nested_message_value"].message_type = _NESTEDMESSAGE -_TEST.fields_by_name[ - "wrapped_bool_value" -].message_type = google_dot_protobuf_dot_wrappers__pb2._BOOLVALUE -_TEST.oneofs_by_name["value_type"].fields.append(_TEST.fields_by_name["bool_value"]) -_TEST.fields_by_name["bool_value"].containing_oneof = _TEST.oneofs_by_name["value_type"] -_TEST.oneofs_by_name["value_type"].fields.append(_TEST.fields_by_name["int64_value"]) -_TEST.fields_by_name["int64_value"].containing_oneof = _TEST.oneofs_by_name[ - "value_type" -] -_TEST.oneofs_by_name["value_type"].fields.append( - _TEST.fields_by_name["timestamp_value"] -) -_TEST.fields_by_name["timestamp_value"].containing_oneof = _TEST.oneofs_by_name[ - "value_type" -] -_TEST.oneofs_by_name["value_type"].fields.append(_TEST.fields_by_name["duration_value"]) -_TEST.fields_by_name["duration_value"].containing_oneof = _TEST.oneofs_by_name[ - "value_type" -] -_TEST.oneofs_by_name["value_type"].fields.append( - _TEST.fields_by_name["wrapped_message_value"] -) -_TEST.fields_by_name["wrapped_message_value"].containing_oneof = _TEST.oneofs_by_name[ - "value_type" -] -_TEST.oneofs_by_name["value_type"].fields.append( - _TEST.fields_by_name["wrapped_nested_message_value"] -) -_TEST.fields_by_name[ - "wrapped_nested_message_value" -].containing_oneof = _TEST.oneofs_by_name["value_type"] -_TEST.oneofs_by_name["value_type"].fields.append( - _TEST.fields_by_name["wrapped_bool_value"] -) -_TEST.fields_by_name["wrapped_bool_value"].containing_oneof = _TEST.oneofs_by_name[ - "value_type" -] -_TEST.oneofs_by_name["value_type"].fields.append(_TEST.fields_by_name["str_value"]) -_TEST.fields_by_name["str_value"].containing_oneof = _TEST.oneofs_by_name["value_type"] -DESCRIPTOR.message_types_by_name["Message"] = _MESSAGE -DESCRIPTOR.message_types_by_name["NestedMessage"] = _NESTEDMESSAGE -DESCRIPTOR.message_types_by_name["Test"] = _TEST -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -Message = _reflection.GeneratedProtocolMessageType( - "Message", - (_message.Message,), - { - "DESCRIPTOR": _MESSAGE, - "__module__": "betterproto.tests.inputs.oneof_default_value_serialization.oneof_default_value_serialization_pb2" - # @@protoc_insertion_point(class_scope:Message) - }, -) -_sym_db.RegisterMessage(Message) - -NestedMessage = _reflection.GeneratedProtocolMessageType( - "NestedMessage", - (_message.Message,), - { - "DESCRIPTOR": _NESTEDMESSAGE, - "__module__": "betterproto.tests.inputs.oneof_default_value_serialization.oneof_default_value_serialization_pb2" - # @@protoc_insertion_point(class_scope:NestedMessage) - }, -) -_sym_db.RegisterMessage(NestedMessage) - -Test = _reflection.GeneratedProtocolMessageType( - "Test", - (_message.Message,), - { - "DESCRIPTOR": _TEST, - "__module__": "betterproto.tests.inputs.oneof_default_value_serialization.oneof_default_value_serialization_pb2" - # @@protoc_insertion_point(class_scope:Test) - }, -) -_sym_db.RegisterMessage(Test) - - -# @@protoc_insertion_point(module_scope) From 2e86a44f655191fd578665a12210f280c5e0fc1e Mon Sep 17 00:00:00 2001 From: Brady Kieffer Date: Thu, 9 Jul 2020 20:03:49 -0400 Subject: [PATCH 10/11] Adding some clarifying comments --- betterproto/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/betterproto/__init__.py b/betterproto/__init__.py index ec856fce6..6b6e5c307 100644 --- a/betterproto/__init__.py +++ b/betterproto/__init__.py @@ -622,6 +622,10 @@ def __bytes__(self) -> bytes: sv = _serialize_single(2, meta.map_types[1], v) output += _serialize_single(meta.number, meta.proto_type, sk + sv) else: + # If we have an empty string and we're including the default value for + # a oneof, make sure we serialize it. This ensures that the byte string + # output isn't simply an empty string. This also ensures that round trip + # serialization will keep `which_one_of` calls consistent. if ( isinstance(value, str) and value == "" @@ -905,6 +909,8 @@ def from_dict(self: T, value: dict) -> T: elif meta.wraps: setattr(self, field_name, value[key]) else: + # NOTE: `from_dict` mutates the underlying message, so no + # assignment here is necessary. v.from_dict(value[key]) elif meta.map_types and meta.map_types[1] == TYPE_MESSAGE: v = getattr(self, field_name) From 8d3fa9e7b71afd2ab7e563ca5e9cafa612c5298b Mon Sep 17 00:00:00 2001 From: Brady Kieffer Date: Thu, 9 Jul 2020 20:46:36 -0400 Subject: [PATCH 11/11] Fixing tests for python38 --- .../google_impl_behavior_equivalence.proto | 2 +- .../test_google_impl_behavior_equivalence.py | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/betterproto/tests/inputs/google_impl_behavior_equivalence/google_impl_behavior_equivalence.proto b/betterproto/tests/inputs/google_impl_behavior_equivalence/google_impl_behavior_equivalence.proto index e54212c2a..31b6bd3e8 100644 --- a/betterproto/tests/inputs/google_impl_behavior_equivalence/google_impl_behavior_equivalence.proto +++ b/betterproto/tests/inputs/google_impl_behavior_equivalence/google_impl_behavior_equivalence.proto @@ -4,7 +4,7 @@ message Foo{ int64 bar = 1; } -message OneOfTest{ +message Test{ oneof group{ string string = 1; int64 integer = 2; diff --git a/betterproto/tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py b/betterproto/tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py index e3c6b2aab..78b64fd3e 100644 --- a/betterproto/tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py +++ b/betterproto/tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py @@ -3,11 +3,11 @@ from google.protobuf import json_format import betterproto from betterproto.tests.output_betterproto.google_impl_behavior_equivalence import ( - OneOfTest, + Test, Foo, ) from betterproto.tests.output_reference.google_impl_behavior_equivalence.google_impl_behavior_equivalence_pb2 import ( - OneOfTest as ReferenceOneOfTest, + Test as ReferenceTest, Foo as ReferenceFoo, ) @@ -15,13 +15,13 @@ def test_oneof_serializes_similar_to_google_oneof(): tests = [ - (OneOfTest(string="abc"), ReferenceOneOfTest(string="abc")), - (OneOfTest(integer=2), ReferenceOneOfTest(integer=2)), - (OneOfTest(foo=Foo(bar=1)), ReferenceOneOfTest(foo=ReferenceFoo(bar=1))), + (Test(string="abc"), ReferenceTest(string="abc")), + (Test(integer=2), ReferenceTest(integer=2)), + (Test(foo=Foo(bar=1)), ReferenceTest(foo=ReferenceFoo(bar=1))), # Default values should also behave the same within oneofs - (OneOfTest(string=""), ReferenceOneOfTest(string="")), - (OneOfTest(integer=0), ReferenceOneOfTest(integer=0)), - (OneOfTest(foo=Foo(bar=0)), ReferenceOneOfTest(foo=ReferenceFoo(bar=0))), + (Test(string=""), ReferenceTest(string="")), + (Test(integer=0), ReferenceTest(integer=0)), + (Test(foo=Foo(bar=0)), ReferenceTest(foo=ReferenceFoo(bar=0))), ] for message, message_reference in tests: # NOTE: As of July 2020, MessageToJson inserts newlines in the output string so, @@ -31,16 +31,16 @@ def test_oneof_serializes_similar_to_google_oneof(): def test_bytes_are_the_same_for_oneof(): - message = OneOfTest(string="") - message_reference = ReferenceOneOfTest(string="") + message = Test(string="") + message_reference = ReferenceTest(string="") message_bytes = bytes(message) message_reference_bytes = message_reference.SerializeToString() assert message_bytes == message_reference_bytes - message2 = OneOfTest().parse(message_reference_bytes) - message_reference2 = ReferenceOneOfTest() + message2 = Test().parse(message_reference_bytes) + message_reference2 = ReferenceTest() message_reference2.ParseFromString(message_reference_bytes) assert message == message2