Visitar URL original
gh-89770: Implement PEP-678 - Exception notes by iritkatriel · Pull Request #31317 · python/cpython · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1555087
PEP-678: exception notes are set by add_note(). __notes__ holds a tup…
iritkatriel Feb 13, 2022
6a38688
clear notes with del e.__notes__, remove replace arg and None note op…
iritkatriel Feb 23, 2022
7bab63e
do not create new tuple when __notes__ is accessed
iritkatriel Mar 2, 2022
396b5f1
Revert "do not create new tuple when __notes__ is accessed"
iritkatriel Mar 16, 2022
f518aa5
add __notes__ attribute in add_note. Traceback ignores it if it's not…
iritkatriel Mar 16, 2022
0438933
add in add_note a check that __notes__ is a list. Add the test. tweak…
iritkatriel Mar 16, 2022
dcc93ef
TypeError in add_note if __notes__ is not a list
iritkatriel Mar 16, 2022
614378e
if __notes__ is not sequence, print repr(__notes__). If note is not a…
iritkatriel Mar 17, 2022
f240e71
simplify traceback code (no need to special case note which is a string)
iritkatriel Mar 21, 2022
6786dbd
shallow copy the notes in split(), if it's a sequence
iritkatriel Mar 22, 2022
bdd4e2a
split() ignores notes if they are not a sequence
iritkatriel Mar 23, 2022
711e804
typo in doc
iritkatriel Mar 28, 2022
e147f52
fix typo in test
iritkatriel Apr 12, 2022
622ca51
Merge remote-tracking branch 'upstream/main' into pep-678
iritkatriel Apr 12, 2022
404f80d
📜🤖 Added by blurb_it.
blurb-it[bot] Apr 12, 2022
c014ad8
update test_traceback
iritkatriel Apr 12, 2022
47bfcdc
Merge branch 'main' into pep-678
iritkatriel Apr 12, 2022
2ae22e4
update whatnew
iritkatriel Apr 12, 2022
95be670
METH_VARARGS --> METH_O
iritkatriel Apr 14, 2022
520efd1
Merge branch 'main' into pep-678
iritkatriel Apr 14, 2022
a14e915
add_note no longer has a replace kwarg
iritkatriel Apr 14, 2022
991e982
finish converting add_note to METH_O
iritkatriel Apr 14, 2022
602b4c4
fix whitespace
iritkatriel Apr 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add __notes__ attribute in add_note. Traceback ignores it if it's not…
… a sequence of strings
  • Loading branch information
iritkatriel committed Mar 16, 2022
commit f518aa5f67e0ca6466868cf71175c40548cedb86
4 changes: 3 additions & 1 deletion Lib/test/test_exception_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,9 @@ def leaves(exc):
self.assertIs(eg.__cause__, part.__cause__)
self.assertIs(eg.__context__, part.__context__)
self.assertIs(eg.__traceback__, part.__traceback__)
self.assertEqual(eg.__notes__, part.__notes__)
self.assertEqual(
getattr(eg, '__notes__', None),
getattr(part, '__notes__', None))

def tbs_for_leaf(leaf, eg):
for e, tbs in leaf_generator(eg):
Expand Down
18 changes: 8 additions & 10 deletions Lib/test/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -541,27 +541,25 @@ def testAttributes(self):
def test_notes(self):
for e in [BaseException(1), Exception(2), ValueError(3)]:
with self.subTest(e=e):
self.assertEqual(e.__notes__, ())
self.assertFalse(hasattr(e, '__notes__'))
e.add_note("My Note")
self.assertEqual(e.__notes__, ("My Note",))
self.assertEqual(e.__notes__, ["My Note"])

with self.assertRaises(TypeError):
e.add_note(42)
self.assertEqual(e.__notes__, ("My Note",))
self.assertEqual(e.__notes__, ["My Note"])

e.add_note("Your Note")
self.assertEqual(e.__notes__, ("My Note", "Your Note"))

with self.assertRaises(AttributeError):
e.__notes__ = ("NewNote",)
self.assertEqual(e.__notes__, ("My Note", "Your Note"))
self.assertEqual(e.__notes__, ["My Note", "Your Note"])

del e.__notes__
self.assertEqual(e.__notes__, ())
self.assertFalse(hasattr(e, '__notes__'))

e.add_note("Our Note")
self.assertEqual(e.__notes__, ("Our Note",))
self.assertEqual(e.__notes__, ["Our Note"])

e.__notes__ = 42
self.assertEqual(e.__notes__, 42)
Comment thread
gvanrossum marked this conversation as resolved.

def testWithTraceback(self):
try:
Expand Down
8 changes: 8 additions & 0 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -1340,6 +1340,14 @@ def test_exception_with_note(self):
del e.__notes__
self.assertEqual(self.get_report(e), vanilla)

# non-sequence __notes__ is ignored
e.__notes__ = 42
self.assertEqual(self.get_report(e), vanilla)

# non-string items in the __notes__ sequence are ignored
e.__notes__ = [42, 'Final Note']
self.assertEqual(self.get_report(e), vanilla + 'Final Note\n')

def test_exception_with_note_with_multiple_notes(self):
e = ValueError(42)
vanilla = self.get_report(e)
Expand Down
9 changes: 5 additions & 4 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Extract, format and print information about Python stack traces."""

import collections
import collections.abc
import itertools
import linecache
import sys
Expand Down Expand Up @@ -689,7 +689,7 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
# Capture now to permit freeing resources: only complication is in the
# unofficial API _format_final_exc_line
self._str = _some_str(exc_value)
self.__notes__ = exc_value.__notes__ if exc_value else None
self.__notes__ = getattr(exc_value, '__notes__', None)

if exc_type and issubclass(exc_type, SyntaxError):
# Handle SyntaxError's specially
Expand Down Expand Up @@ -822,9 +822,10 @@ def format_exception_only(self):
yield _format_final_exc_line(stype, self._str)
else:
yield from self._format_syntax_error(stype)
if self.__notes__ is not None:
if isinstance(self.__notes__, collections.abc.Sequence):
for note in self.__notes__:
yield from [l + '\n' for l in note.split('\n')]
if isinstance(note, str):
yield from [l + '\n' for l in note.split('\n')]

def _format_syntax_error(self, stype):
"""Format SyntaxError exceptions (internal helper)."""
Expand Down
63 changes: 27 additions & 36 deletions Objects/exceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -194,25 +194,34 @@ _PyBaseExceptionObject_cast(PyObject *exc)
}

static PyObject *
BaseException_add_note(PyObject *self_, PyObject *args, PyObject *kwds)
BaseException_add_note(PyObject *self, PyObject *args, PyObject *kwds)
{
PyBaseExceptionObject *self = _PyBaseExceptionObject_cast(self_);
PyObject *note = NULL;

if (!PyArg_ParseTuple(args, "U:add_note", &note)) {
return NULL;
}

if (self->notes == NULL) {
self->notes = PyList_New(0);
if (self->notes == NULL) {
if (!PyObject_HasAttr(self, &_Py_ID(__notes__))) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not use PyObject_HasAttr. It is broken by design.

PyObject *new_notes = PyList_New(0);
if (new_notes == NULL) {
return NULL;
}
if (PyObject_SetAttr(self, &_Py_ID(__notes__), new_notes) < 0) {
Py_DECREF(new_notes);
return NULL;
}
Py_DECREF(new_notes);
}
PyObject *notes = PyObject_GetAttr(self, &_Py_ID(__notes__));
if (notes == NULL) {
return NULL;
}
assert(PyUnicode_CheckExact(note));
if (PyList_Append(self->notes, note) < 0) {
if (PyList_Append(notes, note) < 0) {
Py_DECREF(notes);
Comment thread
gvanrossum marked this conversation as resolved.
return NULL;
}
Py_DECREF(notes);
Py_RETURN_NONE;
}

Expand Down Expand Up @@ -255,31 +264,6 @@ BaseException_set_args(PyBaseExceptionObject *self, PyObject *val, void *Py_UNUS
return 0;
}

static PyObject *
BaseException_get_notes(PyBaseExceptionObject *self, void *Py_UNUSED(ignored))
{
if (self->notes == NULL) {
return PyTuple_New(0);
}
return PySequence_Tuple(self->notes);
}

static int
BaseException_set_notes(PyBaseExceptionObject *self, PyObject *note,
void *Py_UNUSED(ignored))
{
if (note == NULL) {
Py_CLEAR(self->notes);
}
else {
PyErr_SetString(
PyExc_AttributeError,
"Cannot assign a value to __notes__. Use add_note().");
return -1;
}
return 0;
}

static PyObject *
BaseException_get_tb(PyBaseExceptionObject *self, void *Py_UNUSED(ignored))
{
Expand Down Expand Up @@ -370,7 +354,6 @@ BaseException_set_cause(PyObject *self, PyObject *arg, void *Py_UNUSED(ignored))
static PyGetSetDef BaseException_getset[] = {
{"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict},
{"args", (getter)BaseException_get_args, (setter)BaseException_set_args},
{"__notes__", (getter)BaseException_get_notes, (setter)BaseException_set_notes},
{"__traceback__", (getter)BaseException_get_tb, (setter)BaseException_set_tb},
{"__context__", BaseException_get_context,
BaseException_set_context, PyDoc_STR("exception context")},
Expand Down Expand Up @@ -930,9 +913,17 @@ exceptiongroup_subset(
PyException_SetContext(eg, PyException_GetContext(orig));
PyException_SetCause(eg, PyException_GetCause(orig));

PyObject *notes = _PyBaseExceptionObject_cast(orig)->notes;
Py_XINCREF(notes);
_PyBaseExceptionObject_cast(eg)->notes = notes;
if (PyObject_HasAttr(orig, &_Py_ID(__notes__))) {
PyObject *notes = PyObject_GetAttr(orig, &_Py_ID(__notes__));
if (notes == NULL) {
goto error;
}
if (PyObject_SetAttr(eg, &_Py_ID(__notes__), notes) < 0) {
Py_DECREF(notes);
goto error;
}
Py_DECREF(notes);
}

*result = eg;
return 0;
Expand Down
46 changes: 27 additions & 19 deletions Python/pythonrun.c
Original file line number Diff line number Diff line change
Expand Up @@ -1137,40 +1137,48 @@ print_exception_notes(struct exception_print_context *ctx, PyObject *value)
return 0;
}

if (!PyObject_HasAttr(value, &_Py_ID(__notes__))) {
return 0;
}
PyObject *notes = PyObject_GetAttr(value, &_Py_ID(__notes__));
if (notes == NULL) {
return -1;
}
if (!PyTuple_Check(notes)) {
if (!PySequence_Check(notes)) {
Py_DECREF(notes);
return 0;
}
Py_ssize_t num_notes = PyTuple_GET_SIZE(notes);
Py_ssize_t num_notes = PySequence_Length(notes);
PyObject *lines = NULL;
for (Py_ssize_t ni = 0; ni < num_notes; ni++) {
PyObject *note = PyTuple_GET_ITEM(notes, ni);
assert(PyUnicode_Check(note));
lines = PyUnicode_Splitlines(note, 1);
if (lines == NULL) {
Py_DECREF(notes);
return -1;
}

Py_ssize_t n = PyList_GET_SIZE(lines);
for (Py_ssize_t i = 0; i < n; i++) {
PyObject *line = PyList_GET_ITEM(lines, i);
assert(PyUnicode_Check(line));
if (write_indented_margin(ctx, f) < 0) {
PyObject *note = PySequence_GetItem(notes, ni);
if (PyUnicode_Check(note)) {
lines = PyUnicode_Splitlines(note, 1);
Py_DECREF(note);
if (lines == NULL) {
goto error;
}
if (PyFile_WriteObject(line, f, Py_PRINT_RAW) < 0) {

Py_ssize_t n = PyList_GET_SIZE(lines);
for (Py_ssize_t i = 0; i < n; i++) {
PyObject *line = PyList_GET_ITEM(lines, i);
assert(PyUnicode_Check(line));
if (write_indented_margin(ctx, f) < 0) {
goto error;
}
if (PyFile_WriteObject(line, f, Py_PRINT_RAW) < 0) {
goto error;
}
}
if (PyFile_WriteString("\n", f) < 0) {
goto error;
}
Py_CLEAR(lines);
}
if (PyFile_WriteString("\n", f) < 0) {
goto error;
else {
/* Ignore notes which are not strings */
Py_DECREF(note);
}
Py_CLEAR(lines);
}

Py_DECREF(notes);
Expand Down