Add support for recursive messages (#130)
Changes message initialization (`__post_init__`) so that default values are no longer eagerly created to prevent infinite recursion when initializing recursive messages. As a result, `PLACEHOLDER` will be present in the message for any uninitialized fields. So, an implementation of `__get_attribute__` is added that checks for `PLACEHOLDER` and lazily creates and stores default field values. And, because `PLACEHOLDER` values don't compare equal with zero values, a custom implementation of `__eq__` is provided, and the code generation template is updated so that messages generate with `@dataclass(eq=False)`. Also add new Message __repr__ implementation that skips PLACEHOLDER values and orders keys by number from the proto. Co-authored-by: Christopher Chambers <chris@peanutcode.com> Co-authored-by: nat <n@natn.me> Co-authored-by: James <50501825+Gobot1234@users.noreply.github.com>
This commit is contained in:
12
tests/inputs/recursivemessage/recursivemessage.json
Normal file
12
tests/inputs/recursivemessage/recursivemessage.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "Zues",
|
||||
"child": {
|
||||
"name": "Hercules"
|
||||
},
|
||||
"intermediate": {
|
||||
"child": {
|
||||
"name": "Douglas Adams"
|
||||
},
|
||||
"number": 42
|
||||
}
|
||||
}
|
13
tests/inputs/recursivemessage/recursivemessage.proto
Normal file
13
tests/inputs/recursivemessage/recursivemessage.proto
Normal file
@@ -0,0 +1,13 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message Test {
|
||||
string name = 1;
|
||||
Test child = 2;
|
||||
Intermediate intermediate = 3;
|
||||
}
|
||||
|
||||
|
||||
message Intermediate {
|
||||
int32 number = 1;
|
||||
Test child = 2;
|
||||
}
|
@@ -317,3 +317,51 @@ def test_oneof_default_value_set_causes_writes_wire():
|
||||
== betterproto.which_one_of(_round_trip_serialization(foo3), "group1")
|
||||
== ("", None)
|
||||
)
|
||||
|
||||
|
||||
def test_recursive_message():
|
||||
from tests.output_betterproto.recursivemessage import Test as RecursiveMessage
|
||||
|
||||
msg = RecursiveMessage()
|
||||
|
||||
assert msg.child == RecursiveMessage()
|
||||
|
||||
# Lazily-created zero-value children must not affect equality.
|
||||
assert msg == RecursiveMessage()
|
||||
|
||||
# Lazily-created zero-value children must not affect serialization.
|
||||
assert bytes(msg) == b""
|
||||
|
||||
|
||||
def test_recursive_message_defaults():
|
||||
from tests.output_betterproto.recursivemessage import (
|
||||
Test as RecursiveMessage,
|
||||
Intermediate,
|
||||
)
|
||||
|
||||
msg = RecursiveMessage(name="bob", intermediate=Intermediate(42))
|
||||
|
||||
# set values are as expected
|
||||
assert msg == RecursiveMessage(name="bob", intermediate=Intermediate(42))
|
||||
|
||||
# lazy initialized works modifies the message
|
||||
assert msg != RecursiveMessage(
|
||||
name="bob", intermediate=Intermediate(42), child=RecursiveMessage(name="jude")
|
||||
)
|
||||
msg.child.child.name = "jude"
|
||||
assert msg == RecursiveMessage(
|
||||
name="bob",
|
||||
intermediate=Intermediate(42),
|
||||
child=RecursiveMessage(child=RecursiveMessage(name="jude")),
|
||||
)
|
||||
|
||||
# lazily initialization recurses as needed
|
||||
assert msg.child.child.child.child.child.child.child == RecursiveMessage()
|
||||
assert msg.intermediate.child.intermediate == Intermediate()
|
||||
|
||||
|
||||
def test_message_repr():
|
||||
from tests.output_betterproto.recursivemessage import Test
|
||||
|
||||
assert repr(Test(name="Loki")) == "Test(name='Loki')"
|
||||
assert repr(Test(child=Test(), name="Loki")) == "Test(name='Loki', child=Test())"
|
||||
|
Reference in New Issue
Block a user