Skip to content
This repository was archived by the owner on Nov 2, 2022. It is now read-only.

Commit 6a0a18b

Browse files
authored
Merge pull request #3 from WindSoilder/master
Add docstring for split function and catch function, also try to make ExceptionGroup can work for now :)
2 parents 91a286f + eef4d66 commit 6a0a18b

File tree

4 files changed

+158
-6
lines changed

4 files changed

+158
-6
lines changed

exceptiongroup/__init__.py

+11
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def __init__(self, message, exceptions, sources):
3333
raise TypeError(
3434
"Expected an exception object, not {!r}".format(exc)
3535
)
36+
self.message = message
3637
self.sources = list(sources)
3738
if len(self.sources) != len(self.exceptions):
3839
raise ValueError(
@@ -41,6 +42,16 @@ def __init__(self, message, exceptions, sources):
4142
)
4243
)
4344

45+
# copy.copy doesn't work for ExceptionGroup, because BaseException have
46+
# rewrite __reduce_ex__ method. We need to add __copy__ method to
47+
# make it can be copied.
48+
def __copy__(self):
49+
new_group = self.__class__(self.message, self.exceptions, self.sources)
50+
new_group.__traceback__ = self.__traceback__
51+
new_group.__context__ = self.__context__
52+
new_group.__cause__ = self.__cause__
53+
return new_group
54+
4455

4556
from . import _monkeypatch
4657
from ._tools import split, catch

exceptiongroup/_tests/test_exceptiongroup.py

-2
This file was deleted.

exceptiongroup/_tests/test_tools.py

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
from exceptiongroup import ExceptionGroup, split
2+
3+
4+
def raise_error(err):
5+
raise err
6+
7+
8+
def raise_error_from_another(out_err, another_err):
9+
# use try..except approache so out_error have meaningful
10+
# __context__, __cause__ attribute.
11+
try:
12+
raise another_err
13+
except Exception as e:
14+
raise out_err from e
15+
16+
17+
def test_split_for_none_exception():
18+
matched, unmatched = split(RuntimeError, None)
19+
assert matched is None
20+
assert unmatched is None
21+
22+
23+
def test_split_when_all_exception_matched():
24+
group = ExceptionGroup(
25+
"Many Errors",
26+
[RuntimeError("Runtime Error1"), RuntimeError("Runtime Error2")],
27+
["Runtime Error1", "Runtime Error2"]
28+
)
29+
matched, unmatched = split(RuntimeError, group)
30+
assert matched is group
31+
assert unmatched is None
32+
33+
34+
def test_split_when_all_exception_unmatched():
35+
group = ExceptionGroup(
36+
"Many Errors",
37+
[RuntimeError("Runtime Error1"), RuntimeError("Runtime Error2")],
38+
["Runtime Error1", "Runtime Error2"]
39+
)
40+
matched, unmatched = split(ValueError, group)
41+
assert matched is None
42+
assert unmatched is group
43+
44+
45+
def test_split_when_contains_matched_and_unmatched():
46+
error1 = RuntimeError("Runtime Error1")
47+
error2 = ValueError("Value Error2")
48+
group = ExceptionGroup(
49+
"Many Errors",
50+
[error1, error2],
51+
['Runtime Error1', 'Value Error2']
52+
)
53+
matched, unmatched = split(RuntimeError, group)
54+
assert isinstance(matched, ExceptionGroup)
55+
assert isinstance(unmatched, ExceptionGroup)
56+
assert matched.exceptions == [error1]
57+
assert matched.message == "Many Errors"
58+
assert matched.sources == ['Runtime Error1']
59+
assert unmatched.exceptions == [error2]
60+
assert unmatched.message == "Many Errors"
61+
assert unmatched.sources == ['Value Error2']
62+
63+
64+
def test_split_with_predicate():
65+
def _match(err):
66+
return str(err) != 'skip'
67+
68+
error1 = RuntimeError("skip")
69+
error2 = RuntimeError("Runtime Error")
70+
group = ExceptionGroup(
71+
"Many Errors",
72+
[error1, error2],
73+
['skip', 'Runtime Error']
74+
)
75+
matched, unmatched = split(RuntimeError, group, match=_match)
76+
assert matched.exceptions == [error2]
77+
assert unmatched.exceptions == [error1]
78+
79+
80+
def test_split_with_single_exception():
81+
err = RuntimeError("Error")
82+
matched, unmatched = split(RuntimeError, err)
83+
assert matched is err
84+
assert unmatched is None
85+
86+
matched, unmatched = split(ValueError, err)
87+
assert matched is None
88+
assert unmatched is err
89+
90+
91+
def test_split_and_check_attributes_same():
92+
try:
93+
raise_error(RuntimeError("RuntimeError"))
94+
except Exception as e:
95+
run_error = e
96+
97+
try:
98+
raise_error(ValueError("ValueError"))
99+
except Exception as e:
100+
val_error = e
101+
102+
group = ExceptionGroup(
103+
"ErrorGroup", [run_error, val_error], ["RuntimeError", "ValueError"]
104+
)
105+
# go and check __traceback__, __cause__ attributes
106+
try:
107+
raise_error_from_another(group, RuntimeError("Cause"))
108+
except BaseException as e:
109+
new_group = e
110+
111+
matched, unmatched = split(RuntimeError, group)
112+
assert matched.__traceback__ is new_group.__traceback__
113+
assert matched.__cause__ is new_group.__cause__
114+
assert matched.__context__ is new_group.__context__
115+
assert unmatched.__traceback__ is new_group.__traceback__
116+
assert unmatched.__cause__ is new_group.__cause__
117+
assert unmatched.__context__ is new_group.__context__

exceptiongroup/_tools.py

+30-4
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,22 @@
22
# Core primitives for working with ExceptionGroups
33
################################################################
44

5+
import copy
56
from . import ExceptionGroup
67

8+
79
def split(exc_type, exc, *, match=None):
10+
""" splits the exception into one half (matched) representing all the parts of
11+
the exception that match the predicate, and another half (not matched)
12+
representing all the parts that don't match.
13+
14+
Args:
15+
exc_type (type of exception): The exception type we use to split.
16+
exc (BaseException): Exception object we want to split.
17+
match (None or func): predicate function to restict the split process,
18+
if the argument is not None, only exceptions with match(exception)
19+
will go into matched part.
20+
"""
821
if exc is None:
922
return None, None
1023
elif isinstance(exc, ExceptionGroup):
@@ -13,9 +26,9 @@ def split(exc_type, exc, *, match=None):
1326
rests = []
1427
rest_notes = []
1528
for subexc, note in zip(exc.exceptions, exc.sources):
16-
match, rest = ExceptionGroup.split(exc_type, subexc, match=match)
17-
if match is not None:
18-
matches.append(match)
29+
matched, rest = split(exc_type, subexc, match=match)
30+
if matched is not None:
31+
matches.append(matched)
1932
match_notes.append(note)
2033
if rest is not None:
2134
rests.append(rest)
@@ -128,5 +141,18 @@ def __exit__(self, etype, exc, tb):
128141
exceptiongroup_catch_exc.__context__ = saved_context
129142

130143

131-
def catch(cls, exc_type, handler, match=None):
144+
def catch(exc_type, handler, match=None):
145+
"""Return a context manager that catches and re-throws exception.
146+
after running :meth:`handle` on them.
147+
148+
Args:
149+
exc_type: An exception type or A tuple of exception type that need
150+
to be handled by ``handler``. Exceptions which doesn't belong to
151+
exc_type or doesn't match the predicate will not be handled by
152+
``handler``.
153+
handler: the handler to handle exception which match exc_type and
154+
predicate.
155+
match: when the match is not None, ``handler`` will only handle when
156+
match(exc) is True
157+
"""
132158
return Catcher(exc_type, handler, match)

0 commit comments

Comments
 (0)