Skip to content

Commit e63f8f2

Browse files
bpo-29998: Pickling and copying ImportError now preserves name and path (#1010) (#1043)
attributes. (cherry picked from commit b785396)
1 parent fa25f16 commit e63f8f2

File tree

3 files changed

+71
-0
lines changed

3 files changed

+71
-0
lines changed

Lib/test/test_exceptions.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Python test set -- part 5, built-in exceptions
22

3+
import copy
34
import os
45
import sys
56
import unittest
@@ -1120,6 +1121,25 @@ def test_non_str_argument(self):
11201121
exc = ImportError(arg)
11211122
self.assertEqual(str(arg), str(exc))
11221123

1124+
def test_copy_pickle(self):
1125+
for kwargs in (dict(),
1126+
dict(name='somename'),
1127+
dict(path='somepath'),
1128+
dict(name='somename', path='somepath')):
1129+
orig = ImportError('test', **kwargs)
1130+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1131+
exc = pickle.loads(pickle.dumps(orig, proto))
1132+
self.assertEqual(exc.args, ('test',))
1133+
self.assertEqual(exc.msg, 'test')
1134+
self.assertEqual(exc.name, orig.name)
1135+
self.assertEqual(exc.path, orig.path)
1136+
for c in copy.copy, copy.deepcopy:
1137+
exc = c(orig)
1138+
self.assertEqual(exc.args, ('test',))
1139+
self.assertEqual(exc.msg, 'test')
1140+
self.assertEqual(exc.name, orig.name)
1141+
self.assertEqual(exc.path, orig.path)
1142+
11231143

11241144
if __name__ == '__main__':
11251145
unittest.main()

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ Extension Modules
4949
Library
5050
-------
5151

52+
- bpo-29998: Pickling and copying ImportError now preserves name and path
53+
attributes.
54+
5255
- bpo-29942: Fix a crash in itertools.chain.from_iterable when encountering
5356
long runs of empty iterables.
5457

Objects/exceptions.c

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,53 @@ ImportError_str(PyImportErrorObject *self)
692692
}
693693
}
694694

695+
static PyObject *
696+
ImportError_getstate(PyImportErrorObject *self)
697+
{
698+
PyObject *dict = ((PyBaseExceptionObject *)self)->dict;
699+
if (self->name || self->path) {
700+
_Py_IDENTIFIER(name);
701+
_Py_IDENTIFIER(path);
702+
dict = dict ? PyDict_Copy(dict) : PyDict_New();
703+
if (dict == NULL)
704+
return NULL;
705+
if (self->name && _PyDict_SetItemId(dict, &PyId_name, self->name) < 0) {
706+
Py_DECREF(dict);
707+
return NULL;
708+
}
709+
if (self->path && _PyDict_SetItemId(dict, &PyId_path, self->path) < 0) {
710+
Py_DECREF(dict);
711+
return NULL;
712+
}
713+
return dict;
714+
}
715+
else if (dict) {
716+
Py_INCREF(dict);
717+
return dict;
718+
}
719+
else {
720+
Py_RETURN_NONE;
721+
}
722+
}
723+
724+
/* Pickling support */
725+
static PyObject *
726+
ImportError_reduce(PyImportErrorObject *self)
727+
{
728+
PyObject *res;
729+
PyObject *args;
730+
PyObject *state = ImportError_getstate(self);
731+
if (state == NULL)
732+
return NULL;
733+
args = ((PyBaseExceptionObject *)self)->args;
734+
if (state == Py_None)
735+
res = PyTuple_Pack(2, Py_TYPE(self), args);
736+
else
737+
res = PyTuple_Pack(3, Py_TYPE(self), args, state);
738+
Py_DECREF(state);
739+
return res;
740+
}
741+
695742
static PyMemberDef ImportError_members[] = {
696743
{"msg", T_OBJECT, offsetof(PyImportErrorObject, msg), 0,
697744
PyDoc_STR("exception message")},
@@ -703,6 +750,7 @@ static PyMemberDef ImportError_members[] = {
703750
};
704751

705752
static PyMethodDef ImportError_methods[] = {
753+
{"__reduce__", (PyCFunction)ImportError_reduce, METH_NOARGS},
706754
{NULL}
707755
};
708756

0 commit comments

Comments
 (0)