diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 6cfbd099660..f547eb20701 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -303,6 +303,46 @@ class ExceptionTests(unittest.TestCase): 'pickled "%r", attribute "%s"' % (e, checkArgName)) + + def testDeprecatedMessageAttribute(self): + # Accessing BaseException.message and relying on its value set by + # BaseException.__init__ triggers a deprecation warning. + exc = BaseException("foo") + with warnings.catch_warnings(record=True) as w: + self.assertEquals(exc.message, "foo") + self.assertEquals(len(w), 1) + self.assertEquals(w[0].category, DeprecationWarning) + self.assertEquals( + str(w[0].message), + "BaseException.message has been deprecated as of Python 2.6") + + + def testRegularMessageAttribute(self): + # Accessing BaseException.message after explicitly setting a value + # for it does not trigger a deprecation warning. + exc = BaseException("foo") + exc.message = "bar" + with warnings.catch_warnings(record=True) as w: + self.assertEquals(exc.message, "bar") + self.assertEquals(len(w), 0) + # Deleting the message is supported, too. + del exc.message + with self.assertRaises(AttributeError): + exc.message + + def testPickleMessageAttribute(self): + # Pickling with message attribute must work, as well. + e = Exception("foo") + f = Exception("foo") + f.message = "bar" + for p in pickle, cPickle: + ep = p.loads(p.dumps(e)) + with warnings.catch_warnings(): + ignore_message_warning() + self.assertEqual(ep.message, "foo") + fp = p.loads(p.dumps(f)) + self.assertEqual(fp.message, "bar") + def testSlicing(self): # Test that you can slice an exception directly instead of requiring # going through the 'args' attribute. diff --git a/Misc/NEWS b/Misc/NEWS index 555d96ea8f3..72aa48e1083 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,9 @@ What's New in Python 2.7 alpha 1 Core and Builtins ----------------- +- Issue #6844: Do not emit DeprecationWarnings when accessing a "message" + attribute on exceptions that was set explicitly. + - Issue #6846: Fix bug where bytearray.pop() returns negative integers. - classmethod no longer checks if its argument is callable. diff --git a/Objects/exceptions.c b/Objects/exceptions.c index fbdc781162e..0a39a6bee95 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -300,30 +300,51 @@ BaseException_set_args(PyBaseExceptionObject *self, PyObject *val) static PyObject * BaseException_get_message(PyBaseExceptionObject *self) { - int ret; - ret = PyErr_WarnEx(PyExc_DeprecationWarning, - "BaseException.message has been deprecated as " - "of Python 2.6", 1); - if (ret < 0) - return NULL; + PyObject *msg; + + /* if "message" is in self->dict, accessing a user-set message attribute */ + if (self->dict && + (msg = PyDict_GetItemString(self->dict, "message"))) { + Py_INCREF(msg); + return msg; + } - Py_INCREF(self->message); - return self->message; + if (self->message == NULL) { + PyErr_SetString(PyExc_AttributeError, "message attribute was deleted"); + return NULL; + } + + /* accessing the deprecated "builtin" message attribute of Exception */ + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "BaseException.message has been deprecated as " + "of Python 2.6", 1) < 0) + return NULL; + + Py_INCREF(self->message); + return self->message; } static int BaseException_set_message(PyBaseExceptionObject *self, PyObject *val) { - int ret; - ret = PyErr_WarnEx(PyExc_DeprecationWarning, - "BaseException.message has been deprecated as " - "of Python 2.6", 1); - if (ret < 0) - return -1; - Py_INCREF(val); - Py_DECREF(self->message); - self->message = val; - return 0; + /* if val is NULL, delete the message attribute */ + if (val == NULL) { + if (self->dict && PyDict_GetItemString(self->dict, "message")) { + if (PyDict_DelItemString(self->dict, "message") < 0) + return -1; + } + Py_XDECREF(self->message); + self->message = NULL; + return 0; + } + + /* else set it in __dict__, but may need to create the dict first */ + if (self->dict == NULL) { + self->dict = PyDict_New(); + if (!self->dict) + return -1; + } + return PyDict_SetItemString(self->dict, "message", val); } static PyGetSetDef BaseException_getset[] = {