Skip to content

Commit b785396

Browse files
bpo-29998: Pickling and copying ImportError now preserves name and path (python#1010)
attributes.
1 parent b879fe8 commit b785396

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
@@ -1126,6 +1127,25 @@ def test_non_str_argument(self):
11261127
exc = ImportError(arg)
11271128
self.assertEqual(str(arg), str(exc))
11281129

1130+
def test_copy_pickle(self):
1131+
for kwargs in (dict(),
1132+
dict(name='somename'),
1133+
dict(path='somepath'),
1134+
dict(name='somename', path='somepath')):
1135+
orig = ImportError('test', **kwargs)
1136+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1137+
exc = pickle.loads(pickle.dumps(orig, proto))
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+
for c in copy.copy, copy.deepcopy:
1143+
exc = c(orig)
1144+
self.assertEqual(exc.args, ('test',))
1145+
self.assertEqual(exc.msg, 'test')
1146+
self.assertEqual(exc.name, orig.name)
1147+
self.assertEqual(exc.path, orig.path)
1148+
11291149

11301150
if __name__ == '__main__':
11311151
unittest.main()

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,9 @@ Extension Modules
307307
Library
308308
-------
309309

310+
- bpo-29998: Pickling and copying ImportError now preserves name and path
311+
attributes.
312+
310313
- bpo-29962: Add math.remainder operation, implementing remainder
311314
as specified in IEEE 754.
312315

Objects/exceptions.c

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,53 @@ ImportError_str(PyImportErrorObject *self)
682682
}
683683
}
684684

685+
static PyObject *
686+
ImportError_getstate(PyImportErrorObject *self)
687+
{
688+
PyObject *dict = ((PyBaseExceptionObject *)self)->dict;
689+
if (self->name || self->path) {
690+
_Py_IDENTIFIER(name);
691+
_Py_IDENTIFIER(path);
692+
dict = dict ? PyDict_Copy(dict) : PyDict_New();
693+
if (dict == NULL)
694+
return NULL;
695+
if (self->name && _PyDict_SetItemId(dict, &PyId_name, self->name) < 0) {
696+
Py_DECREF(dict);
697+
return NULL;
698+
}
699+
if (self->path && _PyDict_SetItemId(dict, &PyId_path, self->path) < 0) {
700+
Py_DECREF(dict);
701+
return NULL;
702+
}
703+
return dict;
704+
}
705+
else if (dict) {
706+
Py_INCREF(dict);
707+
return dict;
708+
}
709+
else {
710+
Py_RETURN_NONE;
711+
}
712+
}
713+
714+
/* Pickling support */
715+
static PyObject *
716+
ImportError_reduce(PyImportErrorObject *self)
717+
{
718+
PyObject *res;
719+
PyObject *args;
720+
PyObject *state = ImportError_getstate(self);
721+
if (state == NULL)
722+
return NULL;
723+
args = ((PyBaseExceptionObject *)self)->args;
724+
if (state == Py_None)
725+
res = PyTuple_Pack(2, Py_TYPE(self), args);
726+
else
727+
res = PyTuple_Pack(3, Py_TYPE(self), args, state);
728+
Py_DECREF(state);
729+
return res;
730+
}
731+
685732
static PyMemberDef ImportError_members[] = {
686733
{"msg", T_OBJECT, offsetof(PyImportErrorObject, msg), 0,
687734
PyDoc_STR("exception message")},
@@ -693,6 +740,7 @@ static PyMemberDef ImportError_members[] = {
693740
};
694741

695742
static PyMethodDef ImportError_methods[] = {
743+
{"__reduce__", (PyCFunction)ImportError_reduce, METH_NOARGS},
696744
{NULL}
697745
};
698746

0 commit comments

Comments
 (0)