|
| 1 | +# Messages |
| 2 | + |
| 3 | +A protobuf message is represented by a class that inherit from the `betterproto2.Message` abstract class. |
| 4 | + |
| 5 | +## Field presence |
| 6 | + |
| 7 | +The [documentation](https://protobuf.dev/programming-guides/field_presence/) of protobuf defines field presence as "the |
| 8 | +notion of whether a protobuf field has a value". The presence of a field can be tracked in two ways: |
| 9 | + |
| 10 | + - **Implicit presence.** It is not possible to know if the field was set to its default value or if it was simply |
| 11 | + omitted. When the field is omitted, it is set to its default value automatically (`0` for an `int`, `""` for a |
| 12 | + string, ...) |
| 13 | + - **Explicit presence.** It is possible to know if the field was set to its default value or if it was |
| 14 | + omitted. In Python, these fields are marked as optional. They are set to `None` when omitted. |
| 15 | + |
| 16 | +The [documentation](https://protobuf.dev/programming-guides/field_presence/#presence-in-proto3-apis) of protobuf shows |
| 17 | +when field presence is explicitly tracked. |
| 18 | + |
| 19 | +For example, given the following `proto` file: |
| 20 | + |
| 21 | +```proto |
| 22 | +syntax = "proto3"; |
| 23 | +
|
| 24 | +message Message { |
| 25 | + int32 x = 1; |
| 26 | + optional int32 y = 2; |
| 27 | +} |
| 28 | +``` |
| 29 | + |
| 30 | +We can see that the default values are not the same: |
| 31 | + |
| 32 | +```python |
| 33 | +>>> msg = Message() |
| 34 | +>>> print(msg.x) |
| 35 | +0 |
| 36 | +>>> print(msg.y) |
| 37 | +None |
| 38 | +``` |
| 39 | + |
| 40 | +!!! warning |
| 41 | + When a field is a message, its presence is always tracked explicitly even if it is not marked as optional. Marking a |
| 42 | + message field as optional has no effect: the default value of such a field is always `None`, not an empty message. |
| 43 | + |
| 44 | +## Oneof support |
| 45 | + |
| 46 | +Protobuf supports grouping fields in a `oneof` clause: at most one of the fields in the group can be set at the same |
| 47 | +time. Let's use the following `proto`: |
| 48 | + |
| 49 | +```proto |
| 50 | +syntax = "proto3"; |
| 51 | +
|
| 52 | +message Test { |
| 53 | + oneof group { |
| 54 | + bool a = 1; |
| 55 | + int32 b = 2; |
| 56 | + string c = 3; |
| 57 | + } |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +The `betterproto2.which_one_of` function allows finding which one of the fields of the `oneof` group is set. The |
| 62 | +function returns the name of the field that is set, and the value of the field. |
| 63 | + |
| 64 | +```python |
| 65 | +>>> betterproto2.which_one_of(Message(a=True), group_name="group") |
| 66 | +('a', True) |
| 67 | +>>> betterproto2.which_one_of(Message(), group_name="group") |
| 68 | +('', None) |
| 69 | +``` |
| 70 | + |
| 71 | +On Python 3.10 and later, it is also possible to use a `match` statement to find which item in a `oneof` group is active. |
| 72 | + |
| 73 | +```python |
| 74 | +>>> def find(m: Message) -> str: |
| 75 | +... match m: |
| 76 | +... case Message(a=bool(value)): |
| 77 | +... return f"a is set to {value}" |
| 78 | +... case Message(b=int(value)): |
| 79 | +... return f"b is set to {value}" |
| 80 | +... return "No field set" |
| 81 | +... |
| 82 | +>>> find(Message(a=True)) |
| 83 | +'a is set to True' |
| 84 | +>>> find(Message(b=12)) |
| 85 | +'b is set to 12' |
| 86 | +>>> find(Message()) |
| 87 | +'No field set' |
| 88 | +``` |
0 commit comments