|
| 1 | +Migrating Guide |
| 2 | +=============== |
| 3 | + |
| 4 | +Google's protocolbuffers |
| 5 | +------------------------ |
| 6 | + |
| 7 | +betterproto has a mostly 1 to 1 drop in replacement for Google's protocolbuffers (after |
| 8 | +regenerating your protobufs of course) although there are some minor differences. |
| 9 | + |
| 10 | +.. note:: |
| 11 | + |
| 12 | + betterproto implements the same basic methods including: |
| 13 | + |
| 14 | + - :meth:`betterproto.Message.FromString` |
| 15 | + - :meth:`betterproto.Message.SerializeToString` |
| 16 | + |
| 17 | + for compatibility purposes, however it is important to note that these are |
| 18 | + effectively aliases for :meth:`betterproto.Message.parse` and |
| 19 | + :meth:`betterproto.Message.__bytes__` respectively. |
| 20 | + |
| 21 | + |
| 22 | +Determining if a message was sent |
| 23 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 24 | + |
| 25 | +Sometimes it is useful to be able to determine whether a message has been sent on |
| 26 | +the wire. This is how the Google wrapper types work to let you know whether a value is |
| 27 | +unset (set as the default/zero value), or set as something else, for example. |
| 28 | + |
| 29 | +Use ``betterproto.serialized_on_wire(message)`` to determine if it was sent. This is |
| 30 | +a little bit different from the official Google generated Python code, and it lives |
| 31 | +outside the generated ``Message`` class to prevent name clashes. Note that it only |
| 32 | +supports Proto 3 and thus can only be used to check if ``Message`` fields are set. |
| 33 | +You cannot check if a scalar was sent on the wire. |
| 34 | + |
| 35 | +.. code-block:: python |
| 36 | +
|
| 37 | + # Old way (official Google Protobuf package) |
| 38 | + >>> mymessage.HasField('myfield') |
| 39 | + True |
| 40 | +
|
| 41 | + # New way (this project) |
| 42 | + >>> betterproto.serialized_on_wire(mymessage.myfield) |
| 43 | + True |
| 44 | +
|
| 45 | +
|
| 46 | +One-of Support |
| 47 | +~~~~~~~~~~~~~~ |
| 48 | + |
| 49 | +Protobuf supports grouping fields in a oneof clause. Only one of the fields in the group |
| 50 | +may be set at a given time. For example, given the proto: |
| 51 | + |
| 52 | +.. code-block:: proto |
| 53 | +
|
| 54 | + syntax = "proto3"; |
| 55 | +
|
| 56 | + message Test { |
| 57 | + oneof foo { |
| 58 | + bool on = 1; |
| 59 | + int32 count = 2; |
| 60 | + string name = 3; |
| 61 | + } |
| 62 | + } |
| 63 | +
|
| 64 | +You can use ``betterproto.which_one_of(message, group_name)`` to determine which of the |
| 65 | +fields was set. It returns a tuple of the field name and value, or a blank string and |
| 66 | +``None`` if unset. Again this is a little different than the official Google code |
| 67 | +generator: |
| 68 | + |
| 69 | +.. code-block:: python |
| 70 | +
|
| 71 | + # Old way (official Google protobuf package) |
| 72 | + >>> message.WhichOneof("group") |
| 73 | + "foo" |
| 74 | +
|
| 75 | + # New way (this project) |
| 76 | + >>> betterproto.which_one_of(message, "group") |
| 77 | + ("foo", "foo's value") |
| 78 | +
|
| 79 | +
|
| 80 | +Well-Known Google Types |
| 81 | +~~~~~~~~~~~~~~~~~~~~~~~ |
| 82 | + |
| 83 | +Google provides several well-known message types like a timestamp, duration, and several |
| 84 | +wrappers used to provide optional zero value support. Each of these has a special JSON |
| 85 | +representation and is handled a little differently from normal messages. The Python |
| 86 | +mapping for these is as follows: |
| 87 | + |
| 88 | ++-------------------------------+-----------------------------------------------+--------------------------+ |
| 89 | +| ``Google Message`` | ``Python Type`` | ``Default`` | |
| 90 | ++===============================+===============================================+==========================+ |
| 91 | +| ``google.protobuf.duration`` | :class:`datetime.timedelta` | ``0`` | |
| 92 | ++-------------------------------+-----------------------------------------------+--------------------------+ |
| 93 | +| ``google.protobuf.timestamp`` | ``Timezone-aware`` :class:`datetime.datetime` | ``1970-01-01T00:00:00Z`` | |
| 94 | ++-------------------------------+-----------------------------------------------+--------------------------+ |
| 95 | +| ``google.protobuf.*Value`` | ``Optional[...]``/``None`` | ``None`` | |
| 96 | ++-------------------------------+-----------------------------------------------+--------------------------+ |
| 97 | +| ``google.protobuf.*`` | ``betterproto.lib.google.protobuf.*`` | ``None`` | |
| 98 | ++-------------------------------+-----------------------------------------------+--------------------------+ |
| 99 | + |
| 100 | + |
| 101 | +For the wrapper types, the Python type corresponds to the wrapped type, e.g. |
| 102 | +``google.protobuf.BoolValue`` becomes ``Optional[bool]`` while |
| 103 | +``google.protobuf.Int32Value`` becomes ``Optional[int]``. All of the optional values |
| 104 | +default to None, so don't forget to check for that possible state. |
| 105 | + |
| 106 | +Given: |
| 107 | + |
| 108 | +.. code-block:: proto |
| 109 | +
|
| 110 | + syntax = "proto3"; |
| 111 | +
|
| 112 | + import "google/protobuf/duration.proto"; |
| 113 | + import "google/protobuf/timestamp.proto"; |
| 114 | + import "google/protobuf/wrappers.proto"; |
| 115 | +
|
| 116 | + message Test { |
| 117 | + google.protobuf.BoolValue maybe = 1; |
| 118 | + google.protobuf.Timestamp ts = 2; |
| 119 | + google.protobuf.Duration duration = 3; |
| 120 | + } |
| 121 | +
|
| 122 | +You can use it as such: |
| 123 | + |
| 124 | +.. code-block:: python |
| 125 | +
|
| 126 | + >>> t = Test().from_dict({"maybe": True, "ts": "2019-01-01T12:00:00Z", "duration": "1.200s"}) |
| 127 | + >>> t |
| 128 | + Test(maybe=True, ts=datetime.datetime(2019, 1, 1, 12, 0, tzinfo=datetime.timezone.utc), duration=datetime.timedelta(seconds=1, microseconds=200000)) |
| 129 | +
|
| 130 | + >>> t.ts - t.duration |
| 131 | + datetime.datetime(2019, 1, 1, 11, 59, 58, 800000, tzinfo=datetime.timezone.utc) |
| 132 | +
|
| 133 | + >>> t.ts.isoformat() |
| 134 | + '2019-01-01T12:00:00+00:00' |
| 135 | +
|
| 136 | + >>> t.maybe = None |
| 137 | + >>> t.to_dict() |
| 138 | + {'ts': '2019-01-01T12:00:00Z', 'duration': '1.200s'} |
| 139 | +
|
| 140 | +
|
| 141 | +[1.2.5] to [2.0.0b1] |
| 142 | +-------------------- |
| 143 | + |
| 144 | +Updated package structures |
| 145 | +~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 146 | + |
| 147 | +Generated code now strictly follows the *package structure* of the ``.proto`` files. |
| 148 | +Consequently ``.proto`` files without a package will be combined in a single |
| 149 | +``__init__.py`` file. To avoid overwriting existing ``__init__.py`` files, its best |
| 150 | +to compile into a dedicated subdirectory. |
| 151 | + |
| 152 | +Upgrading: |
| 153 | + |
| 154 | +- Remove your previously compiled ``.py`` files. |
| 155 | +- Create a new *empty* directory, e.g. ``generated`` or ``lib/generated/proto`` etc. |
| 156 | +- Regenerate your python files into this directory |
| 157 | +- Update import statements, e.g. ``import ExampleMessage from generated`` |
0 commit comments