forked from danielgtaylor/python-betterproto
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Serialize default values in oneofs when calling to_dict() or to_json() (
danielgtaylor#110) * 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. * Some cleanup + testing for nested messages with oneofs * Cleanup oneof_enum test cases, they should be fixed This _should_ address: danielgtaylor#63 * 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. * Remove assignment when populating a sub-message within a proto Also, move setattr out one indentation level * Properly transform proto with empty string in oneof to bytes Also, updated tests to ensure that which_one_of picks up the set field * Formatting betterproto/__init__.py * Adding test cases demonstrating equivalent behaviour with google impl * Removing a temporary file I made locally * Adding some clarifying comments * Fixing tests for python38
- Loading branch information
1 parent
56e7b98
commit eda8035
Showing
7 changed files
with
277 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
tests/inputs/google_impl_behavior_equivalence/google_impl_behavior_equivalence.proto
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
syntax = "proto3"; | ||
|
||
message Foo{ | ||
int64 bar = 1; | ||
} | ||
|
||
message Test{ | ||
oneof group{ | ||
string string = 1; | ||
int64 integer = 2; | ||
Foo foo = 3; | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
tests/inputs/google_impl_behavior_equivalence/test_google_impl_behavior_equivalence.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import pytest | ||
|
||
from google.protobuf import json_format | ||
import betterproto | ||
from tests.output_betterproto.google_impl_behavior_equivalence import ( | ||
Test, | ||
Foo, | ||
) | ||
from tests.output_reference.google_impl_behavior_equivalence.google_impl_behavior_equivalence_pb2 import ( | ||
Test as ReferenceTest, | ||
Foo as ReferenceFoo, | ||
) | ||
|
||
|
||
def test_oneof_serializes_similar_to_google_oneof(): | ||
|
||
tests = [ | ||
(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 | ||
(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, | ||
# just compare dicts | ||
assert message.to_dict() == json_format.MessageToDict(message_reference) | ||
|
||
|
||
def test_bytes_are_the_same_for_oneof(): | ||
|
||
message = Test(string="") | ||
message_reference = ReferenceTest(string="") | ||
|
||
message_bytes = bytes(message) | ||
message_reference_bytes = message_reference.SerializeToString() | ||
|
||
assert message_bytes == message_reference_bytes | ||
|
||
message2 = Test().parse(message_reference_bytes) | ||
message_reference2 = ReferenceTest() | ||
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) |
28 changes: 28 additions & 0 deletions
28
tests/inputs/oneof_default_value_serialization/oneof_default_value_serialization.proto
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +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; | ||
} | ||
} |
74 changes: 74 additions & 0 deletions
74
tests/inputs/oneof_default_value_serialization/test_oneof_default_value_serialization.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import pytest | ||
import datetime | ||
|
||
import betterproto | ||
from tests.output_betterproto.oneof_default_value_serialization import ( | ||
Test, | ||
Message, | ||
NestedMessage, | ||
) | ||
|
||
|
||
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. | ||
""" | ||
|
||
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) | ||
|
||
|
||
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_nested_oneof_messages_are_serialized_with_defaults(): | ||
""" | ||
Nested messages with oneofs should also be handled | ||
""" | ||
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(Test().from_json(message.to_json()), "value_type") | ||
== ( | ||
"wrapped_nested_message_value", | ||
NestedMessage(id=0, wrapped_message_value=Message(value=0)), | ||
) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.