summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorIrit Katriel <1055913+iritkatriel@users.noreply.github.com>2021-12-03 22:01:15 (GMT)
committerGitHub <noreply@github.com>2021-12-03 22:01:15 (GMT)
commit5bb7ef2768be5979b306e4c7552862b1746c251d (patch)
tree2c5d4d96a1e5bba8d0d918cc413d882f10308e63 /Lib
parentd9301703fb1086cafbd730c17e3d450a192485d6 (diff)
downloadcpython-5bb7ef2768be5979b306e4c7552862b1746c251d.zip
cpython-5bb7ef2768be5979b306e4c7552862b1746c251d.tar.gz
cpython-5bb7ef2768be5979b306e4c7552862b1746c251d.tar.bz2
bpo-45607: Make it possible to enrich exception displays via setting their __note__ field (GH-29880)
Diffstat (limited to 'Lib')
-rw-r--r--Lib/test/test_exceptions.py21
-rw-r--r--Lib/test/test_sys.py8
-rw-r--r--Lib/test/test_traceback.py69
-rw-r--r--Lib/traceback.py4
4 files changed, 98 insertions, 4 deletions
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index c666004..e4b7b8f 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -516,6 +516,27 @@ class ExceptionTests(unittest.TestCase):
'pickled "%r", attribute "%s' %
(e, checkArgName))
+ def test_note(self):
+ for e in [BaseException(1), Exception(2), ValueError(3)]:
+ with self.subTest(e=e):
+ self.assertIsNone(e.__note__)
+ e.__note__ = "My Note"
+ self.assertEqual(e.__note__, "My Note")
+
+ with self.assertRaises(TypeError):
+ e.__note__ = 42
+ self.assertEqual(e.__note__, "My Note")
+
+ e.__note__ = "Your Note"
+ self.assertEqual(e.__note__, "Your Note")
+
+ with self.assertRaises(TypeError):
+ del e.__note__
+ self.assertEqual(e.__note__, "Your Note")
+
+ e.__note__ = None
+ self.assertIsNone(e.__note__)
+
def testWithTraceback(self):
try:
raise IndexError(4)
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index db8d008..2b1ba24 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -1298,13 +1298,13 @@ class SizeofTest(unittest.TestCase):
class C(object): pass
check(C.__dict__, size('P'))
# BaseException
- check(BaseException(), size('5Pb'))
+ check(BaseException(), size('6Pb'))
# UnicodeEncodeError
- check(UnicodeEncodeError("", "", 0, 0, ""), size('5Pb 2P2nP'))
+ check(UnicodeEncodeError("", "", 0, 0, ""), size('6Pb 2P2nP'))
# UnicodeDecodeError
- check(UnicodeDecodeError("", b"", 0, 0, ""), size('5Pb 2P2nP'))
+ check(UnicodeDecodeError("", b"", 0, 0, ""), size('6Pb 2P2nP'))
# UnicodeTranslateError
- check(UnicodeTranslateError("", 0, 1, ""), size('5Pb 2P2nP'))
+ check(UnicodeTranslateError("", 0, 1, ""), size('6Pb 2P2nP'))
# ellipses
check(Ellipsis, size(''))
# EncodingMap
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index cde35f5..a458b21 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -1224,6 +1224,22 @@ class BaseExceptionReportingTests:
exp = "\n".join(expected)
self.assertEqual(exp, err)
+ def test_exception_with_note(self):
+ e = ValueError(42)
+ vanilla = self.get_report(e)
+
+ e.__note__ = 'My Note'
+ self.assertEqual(self.get_report(e), vanilla + 'My Note\n')
+
+ e.__note__ = ''
+ self.assertEqual(self.get_report(e), vanilla + '\n')
+
+ e.__note__ = 'Your Note'
+ self.assertEqual(self.get_report(e), vanilla + 'Your Note\n')
+
+ e.__note__ = None
+ self.assertEqual(self.get_report(e), vanilla)
+
def test_exception_qualname(self):
class A:
class B:
@@ -1566,6 +1582,59 @@ class BaseExceptionReportingTests:
report = self.get_report(exc)
self.assertEqual(report, expected)
+ def test_exception_group_with_notes(self):
+ def exc():
+ try:
+ excs = []
+ for msg in ['bad value', 'terrible value']:
+ try:
+ raise ValueError(msg)
+ except ValueError as e:
+ e.__note__ = f'the {msg}'
+ excs.append(e)
+ raise ExceptionGroup("nested", excs)
+ except ExceptionGroup as e:
+ e.__note__ = ('>> Multi line note\n'
+ '>> Because I am such\n'
+ '>> an important exception.\n'
+ '>> empty lines work too\n'
+ '\n'
+ '(that was an empty line)')
+ raise
+
+ expected = (f' + Exception Group Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {self.callable_line}, in get_exception\n'
+ f' | exception_or_callable()\n'
+ f' | ^^^^^^^^^^^^^^^^^^^^^^^\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 9}, in exc\n'
+ f' | raise ExceptionGroup("nested", excs)\n'
+ f' | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n'
+ f' | ExceptionGroup: nested\n'
+ f' | >> Multi line note\n'
+ f' | >> Because I am such\n'
+ f' | >> an important exception.\n'
+ f' | >> empty lines work too\n'
+ f' | \n'
+ f' | (that was an empty line)\n'
+ f' +-+---------------- 1 ----------------\n'
+ f' | Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
+ f' | raise ValueError(msg)\n'
+ f' | ^^^^^^^^^^^^^^^^^^^^^\n'
+ f' | ValueError: bad value\n'
+ f' | the bad value\n'
+ f' +---------------- 2 ----------------\n'
+ f' | Traceback (most recent call last):\n'
+ f' | File "{__file__}", line {exc.__code__.co_firstlineno + 5}, in exc\n'
+ f' | raise ValueError(msg)\n'
+ f' | ^^^^^^^^^^^^^^^^^^^^^\n'
+ f' | ValueError: terrible value\n'
+ f' | the terrible value\n'
+ f' +------------------------------------\n')
+
+ report = self.get_report(exc)
+ self.assertEqual(report, expected)
+
class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
#
diff --git a/Lib/traceback.py b/Lib/traceback.py
index 77f8590..b244750 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -685,6 +685,8 @@ class TracebackException:
# Capture now to permit freeing resources: only complication is in the
# unofficial API _format_final_exc_line
self._str = _some_str(exc_value)
+ self.__note__ = exc_value.__note__ if exc_value else None
+
if exc_type and issubclass(exc_type, SyntaxError):
# Handle SyntaxError's specially
self.filename = exc_value.filename
@@ -816,6 +818,8 @@ class TracebackException:
yield _format_final_exc_line(stype, self._str)
else:
yield from self._format_syntax_error(stype)
+ if self.__note__ is not None:
+ yield from [l + '\n' for l in self.__note__.split('\n')]
def _format_syntax_error(self, stype):
"""Format SyntaxError exceptions (internal helper)."""