Skip to content

Commit 93c1d96

Browse files
authored
fix pickle support for MultiError (#2648)
* fix pickle support for MultiError
1 parent 16b6b5e commit 93c1d96

File tree

2 files changed

+42
-2
lines changed

2 files changed

+42
-2
lines changed

trio/_core/_multierror.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,13 @@ def __new__(cls, exceptions, *, _collapse=True):
213213

214214
return super().__new__(cls, "multiple tasks failed", exceptions)
215215

216+
def __reduce__(self):
217+
return (
218+
self.__new__,
219+
(self.__class__, list(self.exceptions)),
220+
{"collapse": self.collapse},
221+
)
222+
216223
def __str__(self):
217224
return ", ".join(repr(exc) for exc in self.exceptions)
218225

trio/_core/_tests/test_multierror.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import gc
2-
import logging
32
import os
43
import subprocess
54
from pathlib import Path
5+
import pickle
6+
import warnings
67

78
import pytest
89

910
from traceback import (
1011
extract_tb,
1112
print_exception,
12-
format_exception,
1313
)
1414
from traceback import _cause_message # type: ignore
1515
import sys
@@ -555,3 +555,36 @@ def test_apport_excepthook_monkeypatch_interaction():
555555
["--- 1 ---", "KeyError", "--- 2 ---", "ValueError"],
556556
stdout,
557557
)
558+
559+
560+
@pytest.mark.parametrize("protocol", range(0, pickle.HIGHEST_PROTOCOL + 1))
561+
def test_pickle_multierror(protocol) -> None:
562+
# use trio.MultiError to make sure that pickle works through the deprecation layer
563+
import trio
564+
565+
my_except = ZeroDivisionError()
566+
567+
try:
568+
1 / 0
569+
except ZeroDivisionError as e:
570+
my_except = e
571+
572+
# MultiError will collapse into different classes depending on the errors
573+
for cls, errors in (
574+
(ZeroDivisionError, [my_except]),
575+
(NonBaseMultiError, [my_except, ValueError()]),
576+
(MultiError, [BaseException(), my_except]),
577+
):
578+
with warnings.catch_warnings():
579+
warnings.simplefilter("ignore", TrioDeprecationWarning)
580+
me = trio.MultiError(errors) # type: ignore[attr-defined]
581+
dump = pickle.dumps(me, protocol=protocol)
582+
load = pickle.loads(dump)
583+
assert repr(me) == repr(load)
584+
assert me.__class__ == load.__class__ == cls
585+
586+
assert me.__dict__.keys() == load.__dict__.keys()
587+
for me_val, load_val in zip(me.__dict__.values(), load.__dict__.values()):
588+
# tracebacks etc are not preserved through pickling for the default
589+
# exceptions, so we only check that the repr stays the same
590+
assert repr(me_val) == repr(load_val)

0 commit comments

Comments
 (0)