Skip to content
This repository was archived by the owner on Feb 23, 2026. It is now read-only.

Commit 2b7be35

Browse files
authored
fix: add pickling support to proto messages (#280)
FBO of frameworks such as Apache Beam, which use them for sharing state between trusted hosts. Closes #260.
1 parent 437fb9f commit 2b7be35

3 files changed

Lines changed: 65 additions & 2 deletions

File tree

docs/messages.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,11 @@ already allows construction from mapping types.
246246
247247
.. note::
248248

249-
Protobuf messages **CANNOT** be safely pickled or unpickled. This has serious consequences for programs that use multiprocessing or write messages to files.
250-
The preferred mechanism for serializing proto messages is :meth:`~.Message.serialize`.
249+
Although Python's pickling protocol has known issues when used with
250+
untrusted collaborators, some frameworks do use it for communication
251+
between trusted hosts. To support such frameworks, protobuf messages
252+
**can** be pickled and unpickled, although the preferred mechanism for
253+
serializing proto messages is :meth:`~.Message.serialize`.
251254

252255
Multiprocessing example:
253256

proto/message.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,15 @@ def __setattr__(self, key, value):
642642
if pb_value is not None:
643643
self._pb.MergeFrom(self._meta.pb(**{key: pb_value}))
644644

645+
def __getstate__(self):
646+
"""Serialize for pickling."""
647+
return self._pb.SerializeToString()
648+
649+
def __setstate__(self, value):
650+
"""Deserialization for pickling."""
651+
new_pb = self._meta.pb().FromString(value)
652+
super().__setattr__("_pb", new_pb)
653+
645654

646655
class _MessageInfo:
647656
"""Metadata about a message.

tests/test_message_pickling.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright 2018 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import itertools
16+
import pickle
17+
18+
import pytest
19+
20+
import proto
21+
22+
23+
class Squid(proto.Message):
24+
# Test primitives, enums, and repeated fields.
25+
class Chromatophore(proto.Message):
26+
class Color(proto.Enum):
27+
UNKNOWN = 0
28+
RED = 1
29+
BROWN = 2
30+
WHITE = 3
31+
BLUE = 4
32+
33+
color = proto.Field(Color, number=1)
34+
35+
mass_kg = proto.Field(proto.INT32, number=1)
36+
chromatophores = proto.RepeatedField(Chromatophore, number=2)
37+
38+
39+
def test_pickling():
40+
41+
s = Squid(mass_kg=20)
42+
colors = ["RED", "BROWN", "WHITE", "BLUE"]
43+
s.chromatophores = [
44+
{"color": c} for c in itertools.islice(itertools.cycle(colors), 10)
45+
]
46+
47+
pickled = pickle.dumps(s)
48+
49+
unpickled = pickle.loads(pickled)
50+
51+
assert unpickled == s

0 commit comments

Comments
 (0)