* :class:`slice` objects are now hashable, allowing them to be used as dict keys and
set items. (Contributed by Will Bradshaw and Furkan Onder in :gh:`101264`.)
+* Exceptions raised in a typeobject's ``__set_name__`` method are no longer
+ wrapped by a :exc:`RuntimeError`. Context information is added to the
+ exception as a :pep:`678` note. (Contributed by Irit Katriel in :gh:`77757`.)
+
New Modules
===========
PyAPI_FUNC(Py_ssize_t) _Py_UTF8_Edit_Cost(PyObject *str_a, PyObject *str_b,
Py_ssize_t max_cost);
+void _PyErr_FormatNote(const char *format, ...);
+
#ifdef __cplusplus
}
#endif
def test_reuse_different_names(self):
"""Disallow this case because decorated function a would not be cached."""
- with self.assertRaises(RuntimeError) as ctx:
+ with self.assertRaises(TypeError) as ctx:
class ReusedCachedProperty:
@py_functools.cached_property
def a(self):
b = a
self.assertEqual(
- str(ctx.exception.__context__),
+ str(ctx.exception),
str(TypeError("Cannot assign the same cached_property to two different names ('a' and 'b')."))
)
def __set_name__(self, owner, name):
1/0
- with self.assertRaises(RuntimeError) as cm:
+ with self.assertRaises(ZeroDivisionError) as cm:
class NotGoingToWork:
attr = Descriptor()
- exc = cm.exception
- self.assertRegex(str(exc), r'\bNotGoingToWork\b')
- self.assertRegex(str(exc), r'\battr\b')
- self.assertRegex(str(exc), r'\bDescriptor\b')
- self.assertIsInstance(exc.__cause__, ZeroDivisionError)
+ notes = cm.exception.__notes__
+ self.assertRegex(str(notes), r'\bNotGoingToWork\b')
+ self.assertRegex(str(notes), r'\battr\b')
+ self.assertRegex(str(notes), r'\bDescriptor\b')
def test_set_name_wrong(self):
class Descriptor:
def __set_name__(self):
pass
- with self.assertRaises(RuntimeError) as cm:
+ with self.assertRaises(TypeError) as cm:
class NotGoingToWork:
attr = Descriptor()
- exc = cm.exception
- self.assertRegex(str(exc), r'\bNotGoingToWork\b')
- self.assertRegex(str(exc), r'\battr\b')
- self.assertRegex(str(exc), r'\bDescriptor\b')
- self.assertIsInstance(exc.__cause__, TypeError)
+ notes = cm.exception.__notes__
+ self.assertRegex(str(notes), r'\bNotGoingToWork\b')
+ self.assertRegex(str(notes), r'\battr\b')
+ self.assertRegex(str(notes), r'\bDescriptor\b')
def test_set_name_lookup(self):
resolved = []
--- /dev/null
+Exceptions raised in a typeobject's ``__set_name__`` method are no longer
+wrapped by a :exc:`RuntimeError`. Context information is added to the
+exception as a :pep:`678` note.
Py_DECREF(set_name);
if (res == NULL) {
- _PyErr_FormatFromCause(PyExc_RuntimeError,
+ _PyErr_FormatNote(
"Error calling __set_name__ on '%.100s' instance %R "
"in '%.100s'",
Py_TYPE(value)->tp_name, key, type->tp_name);
goto error;
}
- Py_DECREF(res);
+ else {
+ Py_DECREF(res);
+ }
}
Py_DECREF(names_to_set);
#include "Python.h"
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_interp.h" // PyInterpreterState.codec_search_path
+#include "pycore_pyerrors.h" // _PyErr_FormatNote()
#include "pycore_pystate.h" // _PyInterpreterState_GET()
#include "pycore_ucnhash.h" // _PyUnicode_Name_CAPI
#include <ctype.h>
return codec_getstreamcodec(encoding, stream, errors, 3);
}
-static void
-add_note_to_codec_error(const char *operation,
- const char *encoding)
-{
- PyObject *exc = PyErr_GetRaisedException();
- if (exc == NULL) {
- return;
- }
- PyObject *note = PyUnicode_FromFormat("%s with '%s' codec failed",
- operation, encoding);
- if (note == NULL) {
- _PyErr_ChainExceptions1(exc);
- return;
- }
- int res = _PyException_AddNote(exc, note);
- Py_DECREF(note);
- if (res < 0) {
- _PyErr_ChainExceptions1(exc);
- return;
- }
- PyErr_SetRaisedException(exc);
-}
-
/* Encode an object (e.g. a Unicode object) using the given encoding
and return the resulting encoded object (usually a Python string).
result = PyObject_Call(encoder, args, NULL);
if (result == NULL) {
- add_note_to_codec_error("encoding", encoding);
+ _PyErr_FormatNote("%s with '%s' codec failed", "encoding", encoding);
goto onError;
}
result = PyObject_Call(decoder, args, NULL);
if (result == NULL) {
- add_note_to_codec_error("decoding", encoding);
+ _PyErr_FormatNote("%s with '%s' codec failed", "decoding", encoding);
goto onError;
}
if (!PyTuple_Check(result) ||
}
+/* Adds a note to the current exception (if any) */
+void
+_PyErr_FormatNote(const char *format, ...)
+{
+ PyObject *exc = PyErr_GetRaisedException();
+ if (exc == NULL) {
+ return;
+ }
+ va_list vargs;
+ va_start(vargs, format);
+ PyObject *note = PyUnicode_FromFormatV(format, vargs);
+ va_end(vargs);
+ if (note == NULL) {
+ goto error;
+ }
+ int res = _PyException_AddNote(exc, note);
+ Py_DECREF(note);
+ if (res < 0) {
+ goto error;
+ }
+ PyErr_SetRaisedException(exc);
+ return;
+error:
+ _PyErr_ChainExceptions1(exc);
+}
+
+
PyObject *
PyErr_NewException(const char *name, PyObject *base, PyObject *dict)
{