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

Stage 2: move ExceptionGroup formatting to traceback.py #3

Closed
wants to merge 73 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
5655a3e
first attempt at ExceptionGroup/TracebackGroup
iritkatriel Oct 23, 2020
7c9b194
implemented traceback group in c - added map field to traceback so it…
iritkatriel Oct 24, 2020
030818d
add output of exception_group.py
iritkatriel Oct 24, 2020
3d01e30
Move ExceptionGroup to types; add asyncio.TaskGroup
1st1 Oct 25, 2020
f9ccf22
remove add_exc from ExceptionGroup
iritkatriel Oct 28, 2020
404ab68
pure-python, immutable ExceptionGroup
iritkatriel Oct 28, 2020
3995d2f
revert c changes
iritkatriel Oct 28, 2020
6c78891
fixed merging of nested TBGs. Added __iter__ on EG. removed frame fro…
iritkatriel Oct 28, 2020
c0c2833
review comments from Guido. Also fixed split, and activated it in the…
iritkatriel Oct 28, 2020
3ccb921
move ExceptionGroup and TracebackGroup from types to their own script…
iritkatriel Oct 28, 2020
3b4254c
added Lib/exception_group.py
iritkatriel Oct 28, 2020
79995cd
refreshed output
iritkatriel Oct 28, 2020
90700b7
added unit tests checking the structure of a simple and nested EG
iritkatriel Oct 28, 2020
3257e5b
assertExceptionMatchesTemplate instad of assertExceptionsMatch to com…
iritkatriel Oct 29, 2020
e95fd95
added simple split tests
iritkatriel Oct 29, 2020
f729a56
added split tests for nexted exception groups
iritkatriel Oct 29, 2020
f16f601
deleted the old test script and output
iritkatriel Oct 29, 2020
a4b31ed
added ExceptionGroupCatcher and tests for simple (swallowing) handler
iritkatriel Oct 29, 2020
166415d
tidy up tests
iritkatriel Oct 29, 2020
a177e97
added tests for catch with a handler that raises new exceptions (not …
iritkatriel Oct 29, 2020
6d78291
tidy up tests
iritkatriel Oct 29, 2020
8ff067a
handle case where EG is created with an exception with no traceback (…
iritkatriel Oct 29, 2020
e22bd01
added ExceptionGroup.extract_traceback to get the traceback of a sing…
iritkatriel Oct 29, 2020
2a6673c
added traceback checks to the Cacther tests. Fixed bug in Catcher's m…
iritkatriel Oct 30, 2020
ddf4677
derive template from the exception in the tests
iritkatriel Oct 30, 2020
4d66d91
added ExceptionGroup.subgroup() and use it in Catcher to preserve str…
iritkatriel Oct 30, 2020
e7bb3be
added more interesting catch tests. removed empty nested EGs from spl…
iritkatriel Oct 30, 2020
9b671ab
revert change to Lib\types.py
iritkatriel Oct 30, 2020
618e251
update Lib/asyncio/taskgroup.py and tg1 with new location of EG
iritkatriel Oct 30, 2020
791ff8d
_split_on_condition --> project
iritkatriel Oct 30, 2020
f6c1442
use subgroup to simplify extract_traceback
iritkatriel Oct 31, 2020
c78848b
don't create a new EG if to_add is empty
iritkatriel Oct 31, 2020
1ec4bce
tb_next_map keyed on id(exc). Do we need anything else? (array of tbs…
iritkatriel Oct 31, 2020
4d63a8a
Guido's review comments (easy ones done, complex ones noted as TODO)
iritkatriel Nov 1, 2020
040ebcf
replace __len__ by is_empty
iritkatriel Nov 1, 2020
286f4ad
make complement EG optional in project()
iritkatriel Nov 1, 2020
742f8fa
fix TracebackGroup constructor for nested EGs
iritkatriel Nov 2, 2020
e21b368
rewrote construction tests, fixed TraceBackGroup init
iritkatriel Nov 2, 2020
91af84d
rewrote split tests
iritkatriel Nov 2, 2020
0001e28
make tests more explicit
iritkatriel Nov 2, 2020
b11a262
call super.__init__ from ExceptionGroup.__init__ and give it the message
iritkatriel Nov 2, 2020
d9929ff
fix test name
iritkatriel Nov 2, 2020
d4e44cf
Change handler API - return True for naked raise or raise some EG
iritkatriel Nov 2, 2020
2d96ffa
In catcher tests, compare caught exception's traceback to that of the…
iritkatriel Nov 2, 2020
3581143
reduce boilerplate in tests
iritkatriel Nov 3, 2020
c243e94
use traceback.py types in ExceptionGroup.render/format/extract
iritkatriel Nov 3, 2020
7b0b2b4
added test for format and render
iritkatriel Nov 4, 2020
100bd46
traceback.TracebackException is awesome
iritkatriel Nov 4, 2020
1ddacce
Delete tg1.py
iritkatriel Nov 5, 2020
1faf80a
move extract_traceback to test script
iritkatriel Nov 5, 2020
025b432
pep8 formatting
iritkatriel Nov 11, 2020
2fd59e7
reduce repetition in tests
iritkatriel Nov 11, 2020
0d57f35
move ExceptionGroup formatting to traceback.py
iritkatriel Nov 20, 2020
4cae22a
Update Lib/traceback.py
iritkatriel Nov 22, 2020
8d6a2a9
Update Lib/traceback.py
iritkatriel Nov 22, 2020
f593579
Update Lib/traceback.py
iritkatriel Nov 22, 2020
0276874
Update Lib/traceback.py
iritkatriel Nov 22, 2020
2e5cf25
Update Lib/traceback.py
iritkatriel Nov 22, 2020
e14f9bb
Guido's review comments (part1)
iritkatriel Nov 22, 2020
bc38881
move message to be first arg of ExceptionGroup()
iritkatriel Nov 23, 2020
5218614
more of Guido's review comments
iritkatriel Nov 23, 2020
942ac86
add indent_level field to TracebackException
iritkatriel Nov 23, 2020
51405d2
ExceptionGroup takes exceptions as *args instead of list
iritkatriel Nov 23, 2020
46117d3
make indent 3 instead of 4
iritkatriel Nov 23, 2020
7a3df2a
remove tb arg from ExceptionGroup
iritkatriel Nov 23, 2020
3d25ae7
break up large test function, remove some of the tests that should not
iritkatriel Nov 24, 2020
dd23f00
TracebackExceptionGroup is only for ExceptionGroups, and simper struc…
iritkatriel Dec 2, 2020
e36d9b9
revert unintended changes to other tests
iritkatriel Dec 7, 2020
22a566f
add missing newline at end of file
iritkatriel Dec 7, 2020
0a6800c
minor test tidyup
iritkatriel Dec 8, 2020
701cd5d
ExceptionGroup.project() now (1) checks the ExceptionGroup itself (an…
iritkatriel Dec 8, 2020
26394d8
remove the TracebackGroup class - we don't need it
iritkatriel Dec 11, 2020
3e3c03f
remove TracebackGroup import in test
iritkatriel Dec 13, 2020
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
177 changes: 177 additions & 0 deletions Lib/exception_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@

import sys
import textwrap
import traceback


class ExceptionGroup(BaseException):
def __init__(self, message, *excs):
""" Construct a new ExceptionGroup

message: The exception Group's error message
excs: sequence of exceptions
"""
assert message is None or isinstance(message, str)
assert all(isinstance(e, BaseException) for e in excs)

self.message = message
self.excs = excs
super().__init__(self.message)

@staticmethod
def project(exc, condition, with_complement=False):
""" Split an ExceptionGroup based on an exception predicate

returns a new ExceptionGroup, match, of the exceptions of exc
for which condition returns true. If with_complement is true,
returns another ExceptionGroup for the exception for which
condition returns false. Note that condition is checked for
exc and nested ExceptionGroups as well, and if it returns true
then the whole ExceptionGroup is considered to be matched.

match and rest have the same nested structure as exc, but empty
sub-exceptions are not included. They have the same message,
__traceback__, __cause__ and __context__ fields as exc.

condition: BaseException --> Boolean
with_complement: Bool If True, construct also an EG of the non-matches
"""

if condition(exc):
return exc, None
elif not isinstance(exc, ExceptionGroup):
return None, exc if with_complement else None
else:
# recurse into ExceptionGroup
match_exc = rest_exc = None
match = []
rest = [] if with_complement else None
for e in exc.excs:
e_match, e_rest = ExceptionGroup.project(
e, condition, with_complement=with_complement)

if e_match is not None:
match.append(e_match)
if with_complement and e_rest is not None:
rest.append(e_rest)

def copy_metadata(src, target):
target.__traceback__ = src.__traceback__
target.__context__ = src.__context__
target.__cause__ = src.__cause__

if match:
match_exc = ExceptionGroup(exc.message, *match)
copy_metadata(exc, match_exc)
if with_complement and rest:
rest_exc = ExceptionGroup(exc.message, *rest)
copy_metadata(exc, rest_exc)
return match_exc, rest_exc

def split(self, type):
""" Split an ExceptionGroup to extract exceptions of type E

type: An exception type
"""
iritkatriel marked this conversation as resolved.
Show resolved Hide resolved
return self.project(
self, lambda e: isinstance(e, type), with_complement=True)

def subgroup(self, keep):
""" Split an ExceptionGroup to extract only exceptions in keep

keep: List[BaseException]
"""
match, _ = self.project(self, lambda e: e in keep)
return match

def __iter__(self):
''' iterate over the individual exceptions (flattens the tree) '''
for e in self.excs:
if isinstance(e, ExceptionGroup):
for e_ in e:
yield e_
else:
yield e

def __repr__(self):
return f"ExceptionGroup({self.message}, {self.excs})"

@staticmethod
def catch(types, handler):
return ExceptionGroupCatcher(types, handler)


class ExceptionGroupCatcher:
""" Based on trio.MultiErrorCatcher """

def __init__(self, types, handler):
""" Context manager to catch and handle ExceptionGroups

types: the exception types that this handler is interested in
handler: a function that takes an ExceptionGroup of the
matched type and does something with them

Any rest exceptions are raised at the end as another
exception group
"""
self.types = types
self.handler = handler

def __enter__(self):
pass

def __exit__(self, etype, exc, tb):
if exc is not None and isinstance(exc, ExceptionGroup):
match, rest = exc.split(self.types)

if match is None:
# Let the interpreter reraise the exception
return False

naked_raise = False
handler_excs = None
try:
naked_raise = self.handler(match)
except (Exception, ExceptionGroup) as e:
handler_excs = e

if naked_raise or handler_excs is match:
# handler reraised all of the matched exceptions.
# reraise exc as is.
return False

if handler_excs is None:
if rest is None:
# handled and swallowed all exceptions
# do not raise anything.
return True
else:
# raise the rest exceptions
to_raise = rest
elif rest is None:
to_raise = handler_excs # raise what handler returned
else:
# Merge handler's exceptions with rest
# to_keep: EG subgroup of exc with only those to reraise
# (either not matched or reraised by handler)
to_keep = exc.subgroup(
list(rest) + [e for e in handler_excs if e in match])
# to_add: new exceptions raised by handler
to_add = handler_excs.subgroup(
[e for e in handler_excs if e not in match])
if to_add is not None:
to_raise = ExceptionGroup(exc.message, to_keep, to_add)
else:
to_raise = to_keep

# When we raise to_raise, Python will unconditionally blow
# away its __context__ attribute and replace it with the original
# exc we caught. So after we raise it, we have to pause it while
# it's in flight to put the correct __context__ back.
old_context = to_raise.__context__
try:
raise to_raise
finally:
_, value, _ = sys.exc_info()
assert value is to_raise
value.__context__ = old_context
Loading