Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PEP 678: new API for add_note() #4

Merged
merged 2 commits into from
Apr 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ Version history

This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.

**1.0.0rc4**

- Update `PEP 678`_ support to use ``.add_note()`` and ``__notes__`` (PR by Zac Hatfield-Dodds)

**1.0.0rc3**

- Added message about the number of sub-exceptions

**1.0.0rc2**

- Display and copy ``__note__`` (`PEP 678`_) if available (PR by Zac Hatfield-Dodds)
- Display and copy ``__note__`` (draft `PEP 678`_) if available (PR by Zac Hatfield-Dodds)

.. _PEP 678: https://www.python.org/dev/peps/pep-0678/

Expand Down
14 changes: 12 additions & 2 deletions src/exceptiongroup/_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,15 @@ def __init__(self, __message: str, __exceptions: Sequence[EBase], *args: Any):
super().__init__(__message, __exceptions, *args)
self._message = __message
self._exceptions = __exceptions
self.__note__ = None

def add_note(self, note: str):
if not isinstance(note, str):
raise TypeError(
f"Expected a string, got note={note!r} (type {type(note).__name__})"
)
if not hasattr(self, "__notes__"):
self.__notes__ = []
self.__notes__.append(note)

@property
def message(self) -> str:
Expand Down Expand Up @@ -151,7 +159,9 @@ def split(

def derive(self: T, __excs: Sequence[EBase]) -> T:
eg = BaseExceptionGroup(self.message, __excs)
eg.__note__ = self.__note__
if hasattr(self, "__notes__"):
# Create a new list so that add_note() only affects one exceptiongroup
eg.__notes__ = list(self.__notes__)
return eg

def __str__(self) -> str:
Expand Down
18 changes: 13 additions & 5 deletions src/exceptiongroup/_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ def traceback_exception_init(
_seen=_seen,
**kwargs,
)
self.__note__ = getattr(exc_value, "__note__", None) if exc_value else None

seen_was_none = _seen is None

Expand Down Expand Up @@ -79,6 +78,7 @@ def traceback_exception_init(
self.msg = exc_value.message
else:
self.exceptions = None
self.__notes__ = getattr(exc_value, "__notes__", ())


class _ExceptionPrintContext:
Expand Down Expand Up @@ -135,8 +135,12 @@ def traceback_exception_format(self, *, chain=True, _ctx=None):
yield from _ctx.emit("Traceback (most recent call last):\n")
yield from _ctx.emit(exc.stack.format())
yield from _ctx.emit(exc.format_exception_only())
if isinstance(exc.__note__, str):
yield from _ctx.emit(line + "\n" for line in exc.__note__.split("\n"))
for note in exc.__notes__:
try:
msg = str(note)
except BaseException:
msg = "<note str() failed>"
yield from _ctx.emit(msg + "\n")
elif _ctx.exception_group_depth > max_group_depth:
# exception group, but depth exceeds limit
yield from _ctx.emit(f"... (max_group_depth is {max_group_depth})\n")
Expand All @@ -154,8 +158,12 @@ def traceback_exception_format(self, *, chain=True, _ctx=None):
yield from _ctx.emit(exc.stack.format())

yield from _ctx.emit(exc.format_exception_only())
if isinstance(exc.__note__, str):
yield from _ctx.emit(line + "\n" for line in exc.__note__.split("\n"))
for note in exc.__notes__:
try:
msg = str(note)
except BaseException:
msg = "<note str() failed>"
yield from _ctx.emit(msg + "\n")
num_excs = len(exc.exceptions)
if num_excs <= max_group_width:
n = num_excs
Expand Down
15 changes: 9 additions & 6 deletions tests/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,13 @@ def test_fields_are_readonly(self):
with self.assertRaises(AttributeError):
eg.exceptions = [OSError("xyz")]

def test_note_exists_and_is_string_or_none(self):
def test_notes_is_list_of_strings_if_it_exists(self):
eg = create_simple_eg()

note = "This is a happy note for the exception group"
self.assertIs(eg.__note__, None)
eg.__note__ = note
self.assertIs(eg.__note__, note)
self.assertFalse(hasattr(eg, "__notes__"))
eg.add_note(note)
self.assertEqual(eg.__notes__, [note])


class ExceptionGroupTestBase(unittest.TestCase):
Expand Down Expand Up @@ -497,7 +497,10 @@ def leaves(exc):
self.assertIs(eg.__cause__, part.__cause__)
self.assertIs(eg.__context__, part.__context__)
self.assertIs(eg.__traceback__, part.__traceback__)
self.assertIs(eg.__note__, part.__note__)
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 Expand Up @@ -561,7 +564,7 @@ def level3(i):
try:
nested_group()
except ExceptionGroup as e:
e.__note__ = f"the note: {id(e)}"
e.add_note(f"the note: {id(e)}")
eg = e

eg_template = [
Expand Down
4 changes: 2 additions & 2 deletions tests/test_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ def test_formatting(capsys):
try:
raise RuntimeError("bar")
except RuntimeError as exc:
exc.__note__ = "Note from bar handler"
exc.__notes__ = ["Note from bar handler"]
exceptions.append(exc)

try:
raise ExceptionGroup("test message", exceptions)
except ExceptionGroup as exc:
exc.__note__ = "Displays notes attached to the group too"
exc.add_note("Displays notes attached to the group too")
sys.excepthook(type(exc), exc, exc.__traceback__)

lineno = test_formatting.__code__.co_firstlineno
Expand Down