From c4f82543082863717d343404f609b0874b719a93 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 8 Dec 2021 06:48:30 -0500 Subject: [PATCH 1/2] fix: add pickling support to proto messages FBO of frameworks such as Apache Beam, which use them for sharing state between trusted hosts. Closes #260. --- docs/messages.rst | 7 +++-- proto/message.py | 10 +++++++ tests/test_message_pickling.py | 51 ++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 tests/test_message_pickling.py diff --git a/docs/messages.rst b/docs/messages.rst index c723a0d0..e033473c 100644 --- a/docs/messages.rst +++ b/docs/messages.rst @@ -246,8 +246,11 @@ already allows construction from mapping types. .. note:: - Protobuf messages **CANNOT** be safely pickled or unpickled. This has serious consequences for programs that use multiprocessing or write messages to files. - The preferred mechanism for serializing proto messages is :meth:`~.Message.serialize`. + Although Python's pickling protocol has known issues when used with + untrusted collaborators, some frameworks do use it for communication + between trusted hosts. To support such frameworks, protobuf messages + **can** be pickled and unpickled, although the preferred mechanism for + serializing proto messages is :meth:`~.Message.serialize`. Multiprocessing example: diff --git a/proto/message.py b/proto/message.py index d7a61dcb..a4931a89 100644 --- a/proto/message.py +++ b/proto/message.py @@ -642,6 +642,16 @@ def __setattr__(self, key, value): if pb_value is not None: self._pb.MergeFrom(self._meta.pb(**{key: pb_value})) + def __getstate__(self): + """Serialize for pickling.""" + return self._pb.SerializeToString() + + def __setstate__(self, value): + """Deserialization for pickling.""" + new_pb = self._meta.pb().FromString(value) + super().__setattr__("_pb", new_pb) + + class _MessageInfo: """Metadata about a message. diff --git a/tests/test_message_pickling.py b/tests/test_message_pickling.py new file mode 100644 index 00000000..dd97403c --- /dev/null +++ b/tests/test_message_pickling.py @@ -0,0 +1,51 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import itertools +import pickle + +import pytest + +import proto + + +class Squid(proto.Message): + # Test primitives, enums, and repeated fields. + class Chromatophore(proto.Message): + class Color(proto.Enum): + UNKNOWN = 0 + RED = 1 + BROWN = 2 + WHITE = 3 + BLUE = 4 + + color = proto.Field(Color, number=1) + + mass_kg = proto.Field(proto.INT32, number=1) + chromatophores = proto.RepeatedField(Chromatophore, number=2) + + +def test_pickling(): + + s = Squid(mass_kg=20) + colors = ["RED", "BROWN", "WHITE", "BLUE"] + s.chromatophores = [ + {"color": c} for c in itertools.islice(itertools.cycle(colors), 10) + ] + + pickled = pickle.dumps(s) + + unpickled = pickle.loads(pickled) + + assert unpickled == s From 1a6c7424f4eeb0887fd5382ce6eed909300f5875 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Wed, 8 Dec 2021 06:59:41 -0500 Subject: [PATCH 2/2] ci: blacken --- proto/message.py | 1 - 1 file changed, 1 deletion(-) diff --git a/proto/message.py b/proto/message.py index a4931a89..97ee3814 100644 --- a/proto/message.py +++ b/proto/message.py @@ -652,7 +652,6 @@ def __setstate__(self, value): super().__setattr__("_pb", new_pb) - class _MessageInfo: """Metadata about a message.