mirror of
https://github.com/python/cpython.git
synced 2025-10-20 05:41:23 +00:00
bpo-45292: [PEP 654] add the ExceptionGroup and BaseExceptionGroup classes (GH-28569)
This commit is contained in:
parent
4bc5473a42
commit
f30ad65dbf
16 changed files with 1366 additions and 4 deletions
|
@ -189,6 +189,7 @@ var,PyExc_ArithmeticError,3.2,
|
||||||
var,PyExc_AssertionError,3.2,
|
var,PyExc_AssertionError,3.2,
|
||||||
var,PyExc_AttributeError,3.2,
|
var,PyExc_AttributeError,3.2,
|
||||||
var,PyExc_BaseException,3.2,
|
var,PyExc_BaseException,3.2,
|
||||||
|
var,PyExc_BaseExceptionGroup,3.11,
|
||||||
var,PyExc_BlockingIOError,3.7,
|
var,PyExc_BlockingIOError,3.7,
|
||||||
var,PyExc_BrokenPipeError,3.7,
|
var,PyExc_BrokenPipeError,3.7,
|
||||||
var,PyExc_BufferError,3.2,
|
var,PyExc_BufferError,3.2,
|
||||||
|
|
|
@ -14,6 +14,12 @@ typedef struct {
|
||||||
PyException_HEAD
|
PyException_HEAD
|
||||||
} PyBaseExceptionObject;
|
} PyBaseExceptionObject;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyException_HEAD
|
||||||
|
PyObject *msg;
|
||||||
|
PyObject *excs;
|
||||||
|
} PyBaseExceptionGroupObject;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyException_HEAD
|
PyException_HEAD
|
||||||
PyObject *msg;
|
PyObject *msg;
|
||||||
|
|
|
@ -205,6 +205,8 @@ struct _Py_exc_state {
|
||||||
PyObject *errnomap;
|
PyObject *errnomap;
|
||||||
PyBaseExceptionObject *memerrors_freelist;
|
PyBaseExceptionObject *memerrors_freelist;
|
||||||
int memerrors_numfree;
|
int memerrors_numfree;
|
||||||
|
// The ExceptionGroup type
|
||||||
|
PyObject *PyExc_ExceptionGroup;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -93,6 +93,7 @@ extern void _PyAsyncGen_Fini(PyInterpreterState *interp);
|
||||||
extern int _PySignal_Init(int install_signal_handlers);
|
extern int _PySignal_Init(int install_signal_handlers);
|
||||||
extern void _PySignal_Fini(void);
|
extern void _PySignal_Fini(void);
|
||||||
|
|
||||||
|
extern void _PyExc_ClearExceptionGroupType(PyInterpreterState *interp);
|
||||||
extern void _PyExc_Fini(PyInterpreterState *interp);
|
extern void _PyExc_Fini(PyInterpreterState *interp);
|
||||||
extern void _PyImport_Fini(void);
|
extern void _PyImport_Fini(void);
|
||||||
extern void _PyImport_Fini2(void);
|
extern void _PyImport_Fini2(void);
|
||||||
|
|
|
@ -60,11 +60,14 @@ PyAPI_FUNC(const char *) PyExceptionClass_Name(PyObject *);
|
||||||
|
|
||||||
#define PyExceptionInstance_Class(x) ((PyObject*)Py_TYPE(x))
|
#define PyExceptionInstance_Class(x) ((PyObject*)Py_TYPE(x))
|
||||||
|
|
||||||
|
#define _PyBaseExceptionGroup_Check(x) \
|
||||||
|
PyObject_TypeCheck(x, (PyTypeObject *)PyExc_BaseExceptionGroup)
|
||||||
|
|
||||||
/* Predefined exceptions */
|
/* Predefined exceptions */
|
||||||
|
|
||||||
PyAPI_DATA(PyObject *) PyExc_BaseException;
|
PyAPI_DATA(PyObject *) PyExc_BaseException;
|
||||||
PyAPI_DATA(PyObject *) PyExc_Exception;
|
PyAPI_DATA(PyObject *) PyExc_Exception;
|
||||||
|
PyAPI_DATA(PyObject *) PyExc_BaseExceptionGroup;
|
||||||
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03050000
|
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03050000
|
||||||
PyAPI_DATA(PyObject *) PyExc_StopAsyncIteration;
|
PyAPI_DATA(PyObject *) PyExc_StopAsyncIteration;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -2,7 +2,9 @@ BaseException
|
||||||
├── SystemExit
|
├── SystemExit
|
||||||
├── KeyboardInterrupt
|
├── KeyboardInterrupt
|
||||||
├── GeneratorExit
|
├── GeneratorExit
|
||||||
|
├── BaseExceptionGroup
|
||||||
└── Exception
|
└── Exception
|
||||||
|
├── ExceptionGroup [BaseExceptionGroup]
|
||||||
├── StopIteration
|
├── StopIteration
|
||||||
├── StopAsyncIteration
|
├── StopAsyncIteration
|
||||||
├── ArithmeticError
|
├── ArithmeticError
|
||||||
|
|
|
@ -4032,7 +4032,11 @@ order (MRO) for bases """
|
||||||
for tp in builtin_types:
|
for tp in builtin_types:
|
||||||
object.__getattribute__(tp, "__bases__")
|
object.__getattribute__(tp, "__bases__")
|
||||||
if tp is not object:
|
if tp is not object:
|
||||||
self.assertEqual(len(tp.__bases__), 1, tp)
|
if tp is ExceptionGroup:
|
||||||
|
num_bases = 2
|
||||||
|
else:
|
||||||
|
num_bases = 1
|
||||||
|
self.assertEqual(len(tp.__bases__), num_bases, tp)
|
||||||
|
|
||||||
class L(list):
|
class L(list):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -668,7 +668,7 @@ plain ol' Python and is guaranteed to be available.
|
||||||
|
|
||||||
>>> import builtins
|
>>> import builtins
|
||||||
>>> tests = doctest.DocTestFinder().find(builtins)
|
>>> tests = doctest.DocTestFinder().find(builtins)
|
||||||
>>> 820 < len(tests) < 840 # approximate number of objects with docstrings
|
>>> 825 < len(tests) < 845 # approximate number of objects with docstrings
|
||||||
True
|
True
|
||||||
>>> real_tests = [t for t in tests if len(t.examples) > 0]
|
>>> real_tests = [t for t in tests if len(t.examples) > 0]
|
||||||
>>> len(real_tests) # objects that actually have doctests
|
>>> len(real_tests) # objects that actually have doctests
|
||||||
|
|
808
Lib/test/test_exception_group.py
Normal file
808
Lib/test/test_exception_group.py
Normal file
|
@ -0,0 +1,808 @@
|
||||||
|
import collections.abc
|
||||||
|
import traceback
|
||||||
|
import types
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class TestExceptionGroupTypeHierarchy(unittest.TestCase):
|
||||||
|
def test_exception_group_types(self):
|
||||||
|
self.assertTrue(issubclass(ExceptionGroup, Exception))
|
||||||
|
self.assertTrue(issubclass(ExceptionGroup, BaseExceptionGroup))
|
||||||
|
self.assertTrue(issubclass(BaseExceptionGroup, BaseException))
|
||||||
|
|
||||||
|
def test_exception_is_not_generic_type(self):
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
Exception[OSError]
|
||||||
|
|
||||||
|
def test_exception_group_is_generic_type(self):
|
||||||
|
E = OSError
|
||||||
|
self.assertIsInstance(ExceptionGroup[E], types.GenericAlias)
|
||||||
|
self.assertIsInstance(BaseExceptionGroup[E], types.GenericAlias)
|
||||||
|
|
||||||
|
|
||||||
|
class BadConstructorArgs(unittest.TestCase):
|
||||||
|
def test_bad_EG_construction__too_many_args(self):
|
||||||
|
MSG = 'function takes exactly 2 arguments'
|
||||||
|
with self.assertRaisesRegex(TypeError, MSG):
|
||||||
|
ExceptionGroup('no errors')
|
||||||
|
with self.assertRaisesRegex(TypeError, MSG):
|
||||||
|
ExceptionGroup([ValueError('no msg')])
|
||||||
|
with self.assertRaisesRegex(TypeError, MSG):
|
||||||
|
ExceptionGroup('eg', [ValueError('too')], [TypeError('many')])
|
||||||
|
|
||||||
|
def test_bad_EG_construction__bad_message(self):
|
||||||
|
MSG = 'argument 1 must be str, not '
|
||||||
|
with self.assertRaisesRegex(TypeError, MSG):
|
||||||
|
ExceptionGroup(ValueError(12), SyntaxError('bad syntax'))
|
||||||
|
with self.assertRaisesRegex(TypeError, MSG):
|
||||||
|
ExceptionGroup(None, [ValueError(12)])
|
||||||
|
|
||||||
|
def test_bad_EG_construction__bad_excs_sequence(self):
|
||||||
|
MSG = 'second argument \(exceptions\) must be a sequence'
|
||||||
|
with self.assertRaisesRegex(TypeError, MSG):
|
||||||
|
ExceptionGroup('errors not sequence', {ValueError(42)})
|
||||||
|
with self.assertRaisesRegex(TypeError, MSG):
|
||||||
|
ExceptionGroup("eg", None)
|
||||||
|
|
||||||
|
MSG = 'second argument \(exceptions\) must be a non-empty sequence'
|
||||||
|
with self.assertRaisesRegex(ValueError, MSG):
|
||||||
|
ExceptionGroup("eg", [])
|
||||||
|
|
||||||
|
def test_bad_EG_construction__nested_non_exceptions(self):
|
||||||
|
MSG = ('Item [0-9]+ of second argument \(exceptions\)'
|
||||||
|
' is not an exception')
|
||||||
|
with self.assertRaisesRegex(ValueError, MSG):
|
||||||
|
ExceptionGroup('expect instance, not type', [OSError]);
|
||||||
|
with self.assertRaisesRegex(ValueError, MSG):
|
||||||
|
ExceptionGroup('bad error', ["not an exception"])
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceCreation(unittest.TestCase):
|
||||||
|
def test_EG_wraps_Exceptions__creates_EG(self):
|
||||||
|
excs = [ValueError(1), TypeError(2)]
|
||||||
|
self.assertIs(
|
||||||
|
type(ExceptionGroup("eg", excs)),
|
||||||
|
ExceptionGroup)
|
||||||
|
|
||||||
|
def test_BEG_wraps_Exceptions__creates_EG(self):
|
||||||
|
excs = [ValueError(1), TypeError(2)]
|
||||||
|
self.assertIs(
|
||||||
|
type(BaseExceptionGroup("beg", excs)),
|
||||||
|
ExceptionGroup)
|
||||||
|
|
||||||
|
def test_EG_wraps_BaseException__raises_TypeError(self):
|
||||||
|
MSG= "Cannot nest BaseExceptions in an ExceptionGroup"
|
||||||
|
with self.assertRaisesRegex(TypeError, MSG):
|
||||||
|
eg = ExceptionGroup("eg", [ValueError(1), KeyboardInterrupt(2)])
|
||||||
|
|
||||||
|
def test_BEG_wraps_BaseException__creates_BEG(self):
|
||||||
|
beg = BaseExceptionGroup("beg", [ValueError(1), KeyboardInterrupt(2)])
|
||||||
|
self.assertIs(type(beg), BaseExceptionGroup)
|
||||||
|
|
||||||
|
def test_EG_subclass_wraps_anything(self):
|
||||||
|
class MyEG(ExceptionGroup):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertIs(
|
||||||
|
type(MyEG("eg", [ValueError(12), TypeError(42)])),
|
||||||
|
MyEG)
|
||||||
|
self.assertIs(
|
||||||
|
type(MyEG("eg", [ValueError(12), KeyboardInterrupt(42)])),
|
||||||
|
MyEG)
|
||||||
|
|
||||||
|
def test_BEG_subclass_wraps_anything(self):
|
||||||
|
class MyBEG(BaseExceptionGroup):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertIs(
|
||||||
|
type(MyBEG("eg", [ValueError(12), TypeError(42)])),
|
||||||
|
MyBEG)
|
||||||
|
self.assertIs(
|
||||||
|
type(MyBEG("eg", [ValueError(12), KeyboardInterrupt(42)])),
|
||||||
|
MyBEG)
|
||||||
|
|
||||||
|
|
||||||
|
def create_simple_eg():
|
||||||
|
excs = []
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
raise MemoryError("context and cause for ValueError(1)")
|
||||||
|
except MemoryError as e:
|
||||||
|
raise ValueError(1) from e
|
||||||
|
except ValueError as e:
|
||||||
|
excs.append(e)
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
raise OSError("context for TypeError")
|
||||||
|
except OSError as e:
|
||||||
|
raise TypeError(int)
|
||||||
|
except TypeError as e:
|
||||||
|
excs.append(e)
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
raise ImportError("context for ValueError(2)")
|
||||||
|
except ImportError as e:
|
||||||
|
raise ValueError(2)
|
||||||
|
except ValueError as e:
|
||||||
|
excs.append(e)
|
||||||
|
|
||||||
|
try:
|
||||||
|
raise ExceptionGroup('simple eg', excs)
|
||||||
|
except ExceptionGroup as e:
|
||||||
|
return e
|
||||||
|
|
||||||
|
|
||||||
|
class ExceptionGroupFields(unittest.TestCase):
|
||||||
|
def test_basics_ExceptionGroup_fields(self):
|
||||||
|
eg = create_simple_eg()
|
||||||
|
|
||||||
|
# check msg
|
||||||
|
self.assertEqual(eg.message, 'simple eg')
|
||||||
|
self.assertEqual(eg.args[0], 'simple eg')
|
||||||
|
|
||||||
|
# check cause and context
|
||||||
|
self.assertIsInstance(eg.exceptions[0], ValueError)
|
||||||
|
self.assertIsInstance(eg.exceptions[0].__cause__, MemoryError)
|
||||||
|
self.assertIsInstance(eg.exceptions[0].__context__, MemoryError)
|
||||||
|
self.assertIsInstance(eg.exceptions[1], TypeError)
|
||||||
|
self.assertIsNone(eg.exceptions[1].__cause__)
|
||||||
|
self.assertIsInstance(eg.exceptions[1].__context__, OSError)
|
||||||
|
self.assertIsInstance(eg.exceptions[2], ValueError)
|
||||||
|
self.assertIsNone(eg.exceptions[2].__cause__)
|
||||||
|
self.assertIsInstance(eg.exceptions[2].__context__, ImportError)
|
||||||
|
|
||||||
|
# check tracebacks
|
||||||
|
line0 = create_simple_eg.__code__.co_firstlineno
|
||||||
|
tb_linenos = [line0 + 27,
|
||||||
|
[line0 + 6, line0 + 14, line0 + 22]]
|
||||||
|
self.assertEqual(eg.__traceback__.tb_lineno, tb_linenos[0])
|
||||||
|
self.assertIsNone(eg.__traceback__.tb_next)
|
||||||
|
for i in range(3):
|
||||||
|
tb = eg.exceptions[i].__traceback__
|
||||||
|
self.assertIsNone(tb.tb_next)
|
||||||
|
self.assertEqual(tb.tb_lineno, tb_linenos[1][i])
|
||||||
|
|
||||||
|
def test_fields_are_readonly(self):
|
||||||
|
eg = ExceptionGroup('eg', [TypeError(1), OSError(2)])
|
||||||
|
|
||||||
|
self.assertEqual(type(eg.exceptions), tuple)
|
||||||
|
|
||||||
|
eg.message
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
eg.message = "new msg"
|
||||||
|
|
||||||
|
eg.exceptions
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
eg.exceptions = [OSError('xyz')]
|
||||||
|
|
||||||
|
|
||||||
|
class ExceptionGroupTestBase(unittest.TestCase):
|
||||||
|
def assertMatchesTemplate(self, exc, exc_type, template):
|
||||||
|
""" Assert that the exception matches the template
|
||||||
|
|
||||||
|
A template describes the shape of exc. If exc is a
|
||||||
|
leaf exception (i.e., not an exception group) then
|
||||||
|
template is an exception instance that has the
|
||||||
|
expected type and args value of exc. If exc is an
|
||||||
|
exception group, then template is a list of the
|
||||||
|
templates of its nested exceptions.
|
||||||
|
"""
|
||||||
|
if exc_type is not None:
|
||||||
|
self.assertIs(type(exc), exc_type)
|
||||||
|
|
||||||
|
if isinstance(exc, BaseExceptionGroup):
|
||||||
|
self.assertIsInstance(template, collections.abc.Sequence)
|
||||||
|
self.assertEqual(len(exc.exceptions), len(template))
|
||||||
|
for e, t in zip(exc.exceptions, template):
|
||||||
|
self.assertMatchesTemplate(e, None, t)
|
||||||
|
else:
|
||||||
|
self.assertIsInstance(template, BaseException)
|
||||||
|
self.assertEqual(type(exc), type(template))
|
||||||
|
self.assertEqual(exc.args, template.args)
|
||||||
|
|
||||||
|
|
||||||
|
class ExceptionGroupSubgroupTests(ExceptionGroupTestBase):
|
||||||
|
def setUp(self):
|
||||||
|
self.eg = create_simple_eg()
|
||||||
|
self.eg_template = [ValueError(1), TypeError(int), ValueError(2)]
|
||||||
|
|
||||||
|
def test_basics_subgroup_split__bad_arg_type(self):
|
||||||
|
bad_args = ["bad arg",
|
||||||
|
OSError('instance not type'),
|
||||||
|
[OSError('instance not type')],]
|
||||||
|
for arg in bad_args:
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
self.eg.subgroup(arg)
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
self.eg.split(arg)
|
||||||
|
|
||||||
|
def test_basics_subgroup_by_type__passthrough(self):
|
||||||
|
eg = self.eg
|
||||||
|
self.assertIs(eg, eg.subgroup(BaseException))
|
||||||
|
self.assertIs(eg, eg.subgroup(Exception))
|
||||||
|
self.assertIs(eg, eg.subgroup(BaseExceptionGroup))
|
||||||
|
self.assertIs(eg, eg.subgroup(ExceptionGroup))
|
||||||
|
|
||||||
|
def test_basics_subgroup_by_type__no_match(self):
|
||||||
|
self.assertIsNone(self.eg.subgroup(OSError))
|
||||||
|
|
||||||
|
def test_basics_subgroup_by_type__match(self):
|
||||||
|
eg = self.eg
|
||||||
|
testcases = [
|
||||||
|
# (match_type, result_template)
|
||||||
|
(ValueError, [ValueError(1), ValueError(2)]),
|
||||||
|
(TypeError, [TypeError(int)]),
|
||||||
|
((ValueError, TypeError), self.eg_template)]
|
||||||
|
|
||||||
|
for match_type, template in testcases:
|
||||||
|
with self.subTest(match=match_type):
|
||||||
|
subeg = eg.subgroup(match_type)
|
||||||
|
self.assertEqual(subeg.message, eg.message)
|
||||||
|
self.assertMatchesTemplate(subeg, ExceptionGroup, template)
|
||||||
|
|
||||||
|
def test_basics_subgroup_by_predicate__passthrough(self):
|
||||||
|
self.assertIs(self.eg, self.eg.subgroup(lambda e: True))
|
||||||
|
|
||||||
|
def test_basics_subgroup_by_predicate__no_match(self):
|
||||||
|
self.assertIsNone(self.eg.subgroup(lambda e: False))
|
||||||
|
|
||||||
|
def test_basics_subgroup_by_predicate__match(self):
|
||||||
|
eg = self.eg
|
||||||
|
testcases = [
|
||||||
|
# (match_type, result_template)
|
||||||
|
(ValueError, [ValueError(1), ValueError(2)]),
|
||||||
|
(TypeError, [TypeError(int)]),
|
||||||
|
((ValueError, TypeError), self.eg_template)]
|
||||||
|
|
||||||
|
for match_type, template in testcases:
|
||||||
|
subeg = eg.subgroup(lambda e: isinstance(e, match_type))
|
||||||
|
self.assertEqual(subeg.message, eg.message)
|
||||||
|
self.assertMatchesTemplate(subeg, ExceptionGroup, template)
|
||||||
|
|
||||||
|
|
||||||
|
class ExceptionGroupSplitTests(ExceptionGroupTestBase):
|
||||||
|
def setUp(self):
|
||||||
|
self.eg = create_simple_eg()
|
||||||
|
self.eg_template = [ValueError(1), TypeError(int), ValueError(2)]
|
||||||
|
|
||||||
|
def test_basics_split_by_type__passthrough(self):
|
||||||
|
for E in [BaseException, Exception,
|
||||||
|
BaseExceptionGroup, ExceptionGroup]:
|
||||||
|
match, rest = self.eg.split(E)
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
match, ExceptionGroup, self.eg_template)
|
||||||
|
self.assertIsNone(rest)
|
||||||
|
|
||||||
|
def test_basics_split_by_type__no_match(self):
|
||||||
|
match, rest = self.eg.split(OSError)
|
||||||
|
self.assertIsNone(match)
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
rest, ExceptionGroup, self.eg_template)
|
||||||
|
|
||||||
|
def test_basics_split_by_type__match(self):
|
||||||
|
eg = self.eg
|
||||||
|
VE = ValueError
|
||||||
|
TE = TypeError
|
||||||
|
testcases = [
|
||||||
|
# (matcher, match_template, rest_template)
|
||||||
|
(VE, [VE(1), VE(2)], [TE(int)]),
|
||||||
|
(TE, [TE(int)], [VE(1), VE(2)]),
|
||||||
|
((VE, TE), self.eg_template, None),
|
||||||
|
((OSError, VE), [VE(1), VE(2)], [TE(int)]),
|
||||||
|
]
|
||||||
|
|
||||||
|
for match_type, match_template, rest_template in testcases:
|
||||||
|
match, rest = eg.split(match_type)
|
||||||
|
self.assertEqual(match.message, eg.message)
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
match, ExceptionGroup, match_template)
|
||||||
|
if rest_template is not None:
|
||||||
|
self.assertEqual(rest.message, eg.message)
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
rest, ExceptionGroup, rest_template)
|
||||||
|
else:
|
||||||
|
self.assertIsNone(rest)
|
||||||
|
|
||||||
|
def test_basics_split_by_predicate__passthrough(self):
|
||||||
|
match, rest = self.eg.split(lambda e: True)
|
||||||
|
self.assertMatchesTemplate(match, ExceptionGroup, self.eg_template)
|
||||||
|
self.assertIsNone(rest)
|
||||||
|
|
||||||
|
def test_basics_split_by_predicate__no_match(self):
|
||||||
|
match, rest = self.eg.split(lambda e: False)
|
||||||
|
self.assertIsNone(match)
|
||||||
|
self.assertMatchesTemplate(rest, ExceptionGroup, self.eg_template)
|
||||||
|
|
||||||
|
def test_basics_split_by_predicate__match(self):
|
||||||
|
eg = self.eg
|
||||||
|
VE = ValueError
|
||||||
|
TE = TypeError
|
||||||
|
testcases = [
|
||||||
|
# (matcher, match_template, rest_template)
|
||||||
|
(VE, [VE(1), VE(2)], [TE(int)]),
|
||||||
|
(TE, [TE(int)], [VE(1), VE(2)]),
|
||||||
|
((VE, TE), self.eg_template, None),
|
||||||
|
]
|
||||||
|
|
||||||
|
for match_type, match_template, rest_template in testcases:
|
||||||
|
match, rest = eg.split(lambda e: isinstance(e, match_type))
|
||||||
|
self.assertEqual(match.message, eg.message)
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
match, ExceptionGroup, match_template)
|
||||||
|
if rest_template is not None:
|
||||||
|
self.assertEqual(rest.message, eg.message)
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
rest, ExceptionGroup, rest_template)
|
||||||
|
|
||||||
|
|
||||||
|
class DeepRecursionInSplitAndSubgroup(unittest.TestCase):
|
||||||
|
def make_deep_eg(self):
|
||||||
|
e = TypeError(1)
|
||||||
|
for i in range(2000):
|
||||||
|
e = ExceptionGroup('eg', [e])
|
||||||
|
return e
|
||||||
|
|
||||||
|
def test_deep_split(self):
|
||||||
|
e = self.make_deep_eg()
|
||||||
|
with self.assertRaises(RecursionError):
|
||||||
|
e.split(TypeError)
|
||||||
|
|
||||||
|
def test_deep_subgroup(self):
|
||||||
|
e = self.make_deep_eg()
|
||||||
|
with self.assertRaises(RecursionError):
|
||||||
|
e.subgroup(TypeError)
|
||||||
|
|
||||||
|
|
||||||
|
def leaf_generator(exc, tbs=None):
|
||||||
|
if tbs is None:
|
||||||
|
tbs = []
|
||||||
|
tbs.append(exc.__traceback__)
|
||||||
|
if isinstance(exc, BaseExceptionGroup):
|
||||||
|
for e in exc.exceptions:
|
||||||
|
yield from leaf_generator(e, tbs)
|
||||||
|
else:
|
||||||
|
# exc is a leaf exception and its traceback
|
||||||
|
# is the concatenation of the traceback
|
||||||
|
# segments in tbs
|
||||||
|
yield exc, tbs
|
||||||
|
tbs.pop()
|
||||||
|
|
||||||
|
|
||||||
|
class LeafGeneratorTest(unittest.TestCase):
|
||||||
|
# The leaf_generator is mentioned in PEP 654 as a suggestion
|
||||||
|
# on how to iterate over leaf nodes of an EG. Is is also
|
||||||
|
# used below as a test utility. So we test it here.
|
||||||
|
|
||||||
|
def test_leaf_generator(self):
|
||||||
|
eg = create_simple_eg()
|
||||||
|
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
[e for e, _ in leaf_generator(eg)],
|
||||||
|
eg.exceptions)
|
||||||
|
|
||||||
|
for e, tbs in leaf_generator(eg):
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
tbs, [eg.__traceback__, e.__traceback__])
|
||||||
|
|
||||||
|
|
||||||
|
def create_nested_eg():
|
||||||
|
excs = []
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
raise TypeError(bytes)
|
||||||
|
except TypeError as e:
|
||||||
|
raise ExceptionGroup("nested", [e])
|
||||||
|
except ExceptionGroup as e:
|
||||||
|
excs.append(e)
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
raise MemoryError('out of memory')
|
||||||
|
except MemoryError as e:
|
||||||
|
raise ValueError(1) from e
|
||||||
|
except ValueError as e:
|
||||||
|
excs.append(e)
|
||||||
|
|
||||||
|
try:
|
||||||
|
raise ExceptionGroup("root", excs)
|
||||||
|
except ExceptionGroup as eg:
|
||||||
|
return eg
|
||||||
|
|
||||||
|
|
||||||
|
class NestedExceptionGroupBasicsTest(ExceptionGroupTestBase):
|
||||||
|
def test_nested_group_matches_template(self):
|
||||||
|
eg = create_nested_eg()
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
eg,
|
||||||
|
ExceptionGroup,
|
||||||
|
[[TypeError(bytes)], ValueError(1)])
|
||||||
|
|
||||||
|
def test_nested_group_chaining(self):
|
||||||
|
eg = create_nested_eg()
|
||||||
|
self.assertIsInstance(eg.exceptions[1].__context__, MemoryError)
|
||||||
|
self.assertIsInstance(eg.exceptions[1].__cause__, MemoryError)
|
||||||
|
self.assertIsInstance(eg.exceptions[0].__context__, TypeError)
|
||||||
|
|
||||||
|
def test_nested_exception_group_tracebacks(self):
|
||||||
|
eg = create_nested_eg()
|
||||||
|
|
||||||
|
line0 = create_nested_eg.__code__.co_firstlineno
|
||||||
|
for (tb, expected) in [
|
||||||
|
(eg.__traceback__, line0 + 19),
|
||||||
|
(eg.exceptions[0].__traceback__, line0 + 6),
|
||||||
|
(eg.exceptions[1].__traceback__, line0 + 14),
|
||||||
|
(eg.exceptions[0].exceptions[0].__traceback__, line0 + 4),
|
||||||
|
]:
|
||||||
|
self.assertEqual(tb.tb_lineno, expected)
|
||||||
|
self.assertIsNone(tb.tb_next)
|
||||||
|
|
||||||
|
def test_iteration_full_tracebacks(self):
|
||||||
|
eg = create_nested_eg()
|
||||||
|
# check that iteration over leaves
|
||||||
|
# produces the expected tracebacks
|
||||||
|
self.assertEqual(len(list(leaf_generator(eg))), 2)
|
||||||
|
|
||||||
|
line0 = create_nested_eg.__code__.co_firstlineno
|
||||||
|
expected_tbs = [ [line0 + 19, line0 + 6, line0 + 4],
|
||||||
|
[line0 + 19, line0 + 14]]
|
||||||
|
|
||||||
|
for (i, (_, tbs)) in enumerate(leaf_generator(eg)):
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
[tb.tb_lineno for tb in tbs],
|
||||||
|
expected_tbs[i])
|
||||||
|
|
||||||
|
|
||||||
|
class ExceptionGroupSplitTestBase(ExceptionGroupTestBase):
|
||||||
|
|
||||||
|
def split_exception_group(self, eg, types):
|
||||||
|
""" Split an EG and do some sanity checks on the result """
|
||||||
|
self.assertIsInstance(eg, BaseExceptionGroup)
|
||||||
|
|
||||||
|
match, rest = eg.split(types)
|
||||||
|
sg = eg.subgroup(types)
|
||||||
|
|
||||||
|
if match is not None:
|
||||||
|
self.assertIsInstance(match, BaseExceptionGroup)
|
||||||
|
for e,_ in leaf_generator(match):
|
||||||
|
self.assertIsInstance(e, types)
|
||||||
|
|
||||||
|
self.assertIsNotNone(sg)
|
||||||
|
self.assertIsInstance(sg, BaseExceptionGroup)
|
||||||
|
for e,_ in leaf_generator(sg):
|
||||||
|
self.assertIsInstance(e, types)
|
||||||
|
|
||||||
|
if rest is not None:
|
||||||
|
self.assertIsInstance(rest, BaseExceptionGroup)
|
||||||
|
|
||||||
|
def leaves(exc):
|
||||||
|
return [] if exc is None else [e for e,_ in leaf_generator(exc)]
|
||||||
|
|
||||||
|
# match and subgroup have the same leaves
|
||||||
|
self.assertSequenceEqual(leaves(match), leaves(sg))
|
||||||
|
|
||||||
|
match_leaves = leaves(match)
|
||||||
|
rest_leaves = leaves(rest)
|
||||||
|
# each leaf exception of eg is in exactly one of match and rest
|
||||||
|
self.assertEqual(
|
||||||
|
len(leaves(eg)),
|
||||||
|
len(leaves(match)) + len(leaves(rest)))
|
||||||
|
|
||||||
|
for e in leaves(eg):
|
||||||
|
self.assertNotEqual(
|
||||||
|
match and e in match_leaves,
|
||||||
|
rest and e in rest_leaves)
|
||||||
|
|
||||||
|
# message, cause and context equal to eg
|
||||||
|
for part in [match, rest, sg]:
|
||||||
|
if part is not None:
|
||||||
|
self.assertEqual(eg.message, part.message)
|
||||||
|
self.assertIs(eg.__cause__, part.__cause__)
|
||||||
|
self.assertIs(eg.__context__, part.__context__)
|
||||||
|
self.assertIs(eg.__traceback__, part.__traceback__)
|
||||||
|
|
||||||
|
def tbs_for_leaf(leaf, eg):
|
||||||
|
for e, tbs in leaf_generator(eg):
|
||||||
|
if e is leaf:
|
||||||
|
return tbs
|
||||||
|
|
||||||
|
def tb_linenos(tbs):
|
||||||
|
return [tb.tb_lineno for tb in tbs if tb]
|
||||||
|
|
||||||
|
# full tracebacks match
|
||||||
|
for part in [match, rest, sg]:
|
||||||
|
for e in leaves(part):
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
tb_linenos(tbs_for_leaf(e, eg)),
|
||||||
|
tb_linenos(tbs_for_leaf(e, part)))
|
||||||
|
|
||||||
|
return match, rest
|
||||||
|
|
||||||
|
|
||||||
|
class NestedExceptionGroupSplitTest(ExceptionGroupSplitTestBase):
|
||||||
|
|
||||||
|
def test_split_by_type(self):
|
||||||
|
class MyExceptionGroup(ExceptionGroup):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def raiseVE(v):
|
||||||
|
raise ValueError(v)
|
||||||
|
|
||||||
|
def raiseTE(t):
|
||||||
|
raise TypeError(t)
|
||||||
|
|
||||||
|
def nested_group():
|
||||||
|
def level1(i):
|
||||||
|
excs = []
|
||||||
|
for f, arg in [(raiseVE, i), (raiseTE, int), (raiseVE, i+1)]:
|
||||||
|
try:
|
||||||
|
f(arg)
|
||||||
|
except Exception as e:
|
||||||
|
excs.append(e)
|
||||||
|
raise ExceptionGroup('msg1', excs)
|
||||||
|
|
||||||
|
def level2(i):
|
||||||
|
excs = []
|
||||||
|
for f, arg in [(level1, i), (level1, i+1), (raiseVE, i+2)]:
|
||||||
|
try:
|
||||||
|
f(arg)
|
||||||
|
except Exception as e:
|
||||||
|
excs.append(e)
|
||||||
|
raise MyExceptionGroup('msg2', excs)
|
||||||
|
|
||||||
|
def level3(i):
|
||||||
|
excs = []
|
||||||
|
for f, arg in [(level2, i+1), (raiseVE, i+2)]:
|
||||||
|
try:
|
||||||
|
f(arg)
|
||||||
|
except Exception as e:
|
||||||
|
excs.append(e)
|
||||||
|
raise ExceptionGroup('msg3', excs)
|
||||||
|
|
||||||
|
level3(5)
|
||||||
|
|
||||||
|
try:
|
||||||
|
nested_group()
|
||||||
|
except ExceptionGroup as e:
|
||||||
|
eg = e
|
||||||
|
|
||||||
|
eg_template = [
|
||||||
|
[
|
||||||
|
[ValueError(6), TypeError(int), ValueError(7)],
|
||||||
|
[ValueError(7), TypeError(int), ValueError(8)],
|
||||||
|
ValueError(8),
|
||||||
|
],
|
||||||
|
ValueError(7)]
|
||||||
|
|
||||||
|
valueErrors_template = [
|
||||||
|
[
|
||||||
|
[ValueError(6), ValueError(7)],
|
||||||
|
[ValueError(7), ValueError(8)],
|
||||||
|
ValueError(8),
|
||||||
|
],
|
||||||
|
ValueError(7)]
|
||||||
|
|
||||||
|
typeErrors_template = [[[TypeError(int)], [TypeError(int)]]]
|
||||||
|
|
||||||
|
self.assertMatchesTemplate(eg, ExceptionGroup, eg_template)
|
||||||
|
|
||||||
|
# Match Nothing
|
||||||
|
match, rest = self.split_exception_group(eg, SyntaxError)
|
||||||
|
self.assertIsNone(match)
|
||||||
|
self.assertMatchesTemplate(rest, ExceptionGroup, eg_template)
|
||||||
|
|
||||||
|
# Match Everything
|
||||||
|
match, rest = self.split_exception_group(eg, BaseException)
|
||||||
|
self.assertMatchesTemplate(match, ExceptionGroup, eg_template)
|
||||||
|
self.assertIsNone(rest)
|
||||||
|
match, rest = self.split_exception_group(eg, (ValueError, TypeError))
|
||||||
|
self.assertMatchesTemplate(match, ExceptionGroup, eg_template)
|
||||||
|
self.assertIsNone(rest)
|
||||||
|
|
||||||
|
# Match ValueErrors
|
||||||
|
match, rest = self.split_exception_group(eg, ValueError)
|
||||||
|
self.assertMatchesTemplate(match, ExceptionGroup, valueErrors_template)
|
||||||
|
self.assertMatchesTemplate(rest, ExceptionGroup, typeErrors_template)
|
||||||
|
|
||||||
|
# Match TypeErrors
|
||||||
|
match, rest = self.split_exception_group(eg, (TypeError, SyntaxError))
|
||||||
|
self.assertMatchesTemplate(match, ExceptionGroup, typeErrors_template)
|
||||||
|
self.assertMatchesTemplate(rest, ExceptionGroup, valueErrors_template)
|
||||||
|
|
||||||
|
# Match ExceptionGroup
|
||||||
|
match, rest = eg.split(ExceptionGroup)
|
||||||
|
self.assertIs(match, eg)
|
||||||
|
self.assertIsNone(rest)
|
||||||
|
|
||||||
|
# Match MyExceptionGroup (ExceptionGroup subclass)
|
||||||
|
match, rest = eg.split(MyExceptionGroup)
|
||||||
|
self.assertMatchesTemplate(match, ExceptionGroup, [eg_template[0]])
|
||||||
|
self.assertMatchesTemplate(rest, ExceptionGroup, [eg_template[1]])
|
||||||
|
|
||||||
|
def test_split_BaseExceptionGroup(self):
|
||||||
|
def exc(ex):
|
||||||
|
try:
|
||||||
|
raise ex
|
||||||
|
except BaseException as e:
|
||||||
|
return e
|
||||||
|
|
||||||
|
try:
|
||||||
|
raise BaseExceptionGroup(
|
||||||
|
"beg", [exc(ValueError(1)), exc(KeyboardInterrupt(2))])
|
||||||
|
except BaseExceptionGroup as e:
|
||||||
|
beg = e
|
||||||
|
|
||||||
|
# Match Nothing
|
||||||
|
match, rest = self.split_exception_group(beg, TypeError)
|
||||||
|
self.assertIsNone(match)
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
rest, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)])
|
||||||
|
|
||||||
|
# Match Everything
|
||||||
|
match, rest = self.split_exception_group(
|
||||||
|
beg, (ValueError, KeyboardInterrupt))
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
match, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)])
|
||||||
|
self.assertIsNone(rest)
|
||||||
|
|
||||||
|
# Match ValueErrors
|
||||||
|
match, rest = self.split_exception_group(beg, ValueError)
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
match, ExceptionGroup, [ValueError(1)])
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
rest, BaseExceptionGroup, [KeyboardInterrupt(2)])
|
||||||
|
|
||||||
|
# Match KeyboardInterrupts
|
||||||
|
match, rest = self.split_exception_group(beg, KeyboardInterrupt)
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
match, BaseExceptionGroup, [KeyboardInterrupt(2)])
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
rest, ExceptionGroup, [ValueError(1)])
|
||||||
|
|
||||||
|
|
||||||
|
class NestedExceptionGroupSubclassSplitTest(ExceptionGroupSplitTestBase):
|
||||||
|
|
||||||
|
def test_split_ExceptionGroup_subclass_no_derive_no_new_override(self):
|
||||||
|
class EG(ExceptionGroup):
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
raise TypeError(2)
|
||||||
|
except TypeError as te:
|
||||||
|
raise EG("nested", [te])
|
||||||
|
except EG as nested:
|
||||||
|
try:
|
||||||
|
raise ValueError(1)
|
||||||
|
except ValueError as ve:
|
||||||
|
raise EG("eg", [ve, nested])
|
||||||
|
except EG as e:
|
||||||
|
eg = e
|
||||||
|
|
||||||
|
self.assertMatchesTemplate(eg, EG, [ValueError(1), [TypeError(2)]])
|
||||||
|
|
||||||
|
# Match Nothing
|
||||||
|
match, rest = self.split_exception_group(eg, OSError)
|
||||||
|
self.assertIsNone(match)
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
rest, ExceptionGroup, [ValueError(1), [TypeError(2)]])
|
||||||
|
|
||||||
|
# Match Everything
|
||||||
|
match, rest = self.split_exception_group(eg, (ValueError, TypeError))
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
match, ExceptionGroup, [ValueError(1), [TypeError(2)]])
|
||||||
|
self.assertIsNone(rest)
|
||||||
|
|
||||||
|
# Match ValueErrors
|
||||||
|
match, rest = self.split_exception_group(eg, ValueError)
|
||||||
|
self.assertMatchesTemplate(match, ExceptionGroup, [ValueError(1)])
|
||||||
|
self.assertMatchesTemplate(rest, ExceptionGroup, [[TypeError(2)]])
|
||||||
|
|
||||||
|
# Match TypeErrors
|
||||||
|
match, rest = self.split_exception_group(eg, TypeError)
|
||||||
|
self.assertMatchesTemplate(match, ExceptionGroup, [[TypeError(2)]])
|
||||||
|
self.assertMatchesTemplate(rest, ExceptionGroup, [ValueError(1)])
|
||||||
|
|
||||||
|
def test_split_BaseExceptionGroup_subclass_no_derive_new_override(self):
|
||||||
|
class EG(BaseExceptionGroup):
|
||||||
|
def __new__(cls, message, excs, unused):
|
||||||
|
# The "unused" arg is here to show that split() doesn't call
|
||||||
|
# the actual class constructor from the default derive()
|
||||||
|
# implementation (it would fail on unused arg if so because
|
||||||
|
# it assumes the BaseExceptionGroup.__new__ signature).
|
||||||
|
return super().__new__(cls, message, excs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
raise EG("eg", [ValueError(1), KeyboardInterrupt(2)], "unused")
|
||||||
|
except EG as e:
|
||||||
|
eg = e
|
||||||
|
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
eg, EG, [ValueError(1), KeyboardInterrupt(2)])
|
||||||
|
|
||||||
|
# Match Nothing
|
||||||
|
match, rest = self.split_exception_group(eg, OSError)
|
||||||
|
self.assertIsNone(match)
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
rest, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)])
|
||||||
|
|
||||||
|
# Match Everything
|
||||||
|
match, rest = self.split_exception_group(
|
||||||
|
eg, (ValueError, KeyboardInterrupt))
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
match, BaseExceptionGroup, [ValueError(1), KeyboardInterrupt(2)])
|
||||||
|
self.assertIsNone(rest)
|
||||||
|
|
||||||
|
# Match ValueErrors
|
||||||
|
match, rest = self.split_exception_group(eg, ValueError)
|
||||||
|
self.assertMatchesTemplate(match, ExceptionGroup, [ValueError(1)])
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
rest, BaseExceptionGroup, [KeyboardInterrupt(2)])
|
||||||
|
|
||||||
|
# Match KeyboardInterrupt
|
||||||
|
match, rest = self.split_exception_group(eg, KeyboardInterrupt)
|
||||||
|
self.assertMatchesTemplate(
|
||||||
|
match, BaseExceptionGroup, [KeyboardInterrupt(2)])
|
||||||
|
self.assertMatchesTemplate(rest, ExceptionGroup, [ValueError(1)])
|
||||||
|
|
||||||
|
def test_split_ExceptionGroup_subclass_derive_and_new_overrides(self):
|
||||||
|
class EG(ExceptionGroup):
|
||||||
|
def __new__(cls, message, excs, code):
|
||||||
|
obj = super().__new__(cls, message, excs)
|
||||||
|
obj.code = code
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def derive(self, excs):
|
||||||
|
return EG(self.message, excs, self.code)
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
raise TypeError(2)
|
||||||
|
except TypeError as te:
|
||||||
|
raise EG("nested", [te], 101)
|
||||||
|
except EG as nested:
|
||||||
|
try:
|
||||||
|
raise ValueError(1)
|
||||||
|
except ValueError as ve:
|
||||||
|
raise EG("eg", [ve, nested], 42)
|
||||||
|
except EG as e:
|
||||||
|
eg = e
|
||||||
|
|
||||||
|
self.assertMatchesTemplate(eg, EG, [ValueError(1), [TypeError(2)]])
|
||||||
|
|
||||||
|
# Match Nothing
|
||||||
|
match, rest = self.split_exception_group(eg, OSError)
|
||||||
|
self.assertIsNone(match)
|
||||||
|
self.assertMatchesTemplate(rest, EG, [ValueError(1), [TypeError(2)]])
|
||||||
|
self.assertEqual(rest.code, 42)
|
||||||
|
self.assertEqual(rest.exceptions[1].code, 101)
|
||||||
|
|
||||||
|
# Match Everything
|
||||||
|
match, rest = self.split_exception_group(eg, (ValueError, TypeError))
|
||||||
|
self.assertMatchesTemplate(match, EG, [ValueError(1), [TypeError(2)]])
|
||||||
|
self.assertEqual(match.code, 42)
|
||||||
|
self.assertEqual(match.exceptions[1].code, 101)
|
||||||
|
self.assertIsNone(rest)
|
||||||
|
|
||||||
|
# Match ValueErrors
|
||||||
|
match, rest = self.split_exception_group(eg, ValueError)
|
||||||
|
self.assertMatchesTemplate(match, EG, [ValueError(1)])
|
||||||
|
self.assertEqual(match.code, 42)
|
||||||
|
self.assertMatchesTemplate(rest, EG, [[TypeError(2)]])
|
||||||
|
self.assertEqual(rest.code, 42)
|
||||||
|
self.assertEqual(rest.exceptions[0].code, 101)
|
||||||
|
|
||||||
|
# Match TypeErrors
|
||||||
|
match, rest = self.split_exception_group(eg, TypeError)
|
||||||
|
self.assertMatchesTemplate(match, EG, [[TypeError(2)]])
|
||||||
|
self.assertEqual(match.code, 42)
|
||||||
|
self.assertEqual(match.exceptions[0].code, 101)
|
||||||
|
self.assertMatchesTemplate(rest, EG, [ValueError(1)])
|
||||||
|
self.assertEqual(rest.code, 42)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
|
@ -489,7 +489,9 @@ class CompatPickleTests(unittest.TestCase):
|
||||||
ResourceWarning,
|
ResourceWarning,
|
||||||
StopAsyncIteration,
|
StopAsyncIteration,
|
||||||
RecursionError,
|
RecursionError,
|
||||||
EncodingWarning):
|
EncodingWarning,
|
||||||
|
BaseExceptionGroup,
|
||||||
|
ExceptionGroup):
|
||||||
continue
|
continue
|
||||||
if exc is not OSError and issubclass(exc, OSError):
|
if exc is not OSError and issubclass(exc, OSError):
|
||||||
self.assertEqual(reverse_mapping('builtins', name),
|
self.assertEqual(reverse_mapping('builtins', name),
|
||||||
|
|
|
@ -198,6 +198,7 @@ SYMBOL_NAMES = (
|
||||||
"PyExc_AssertionError",
|
"PyExc_AssertionError",
|
||||||
"PyExc_AttributeError",
|
"PyExc_AttributeError",
|
||||||
"PyExc_BaseException",
|
"PyExc_BaseException",
|
||||||
|
"PyExc_BaseExceptionGroup",
|
||||||
"PyExc_BlockingIOError",
|
"PyExc_BlockingIOError",
|
||||||
"PyExc_BrokenPipeError",
|
"PyExc_BrokenPipeError",
|
||||||
"PyExc_BufferError",
|
"PyExc_BufferError",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Implement :pep:`654` Add :class:`ExceptionGroup` and :class:`BaseExceptionGroup`.
|
|
@ -619,6 +619,8 @@ data PyExc_AttributeError
|
||||||
added 3.2
|
added 3.2
|
||||||
data PyExc_BaseException
|
data PyExc_BaseException
|
||||||
added 3.2
|
added 3.2
|
||||||
|
data PyExc_BaseExceptionGroup
|
||||||
|
added 3.11
|
||||||
data PyExc_BufferError
|
data PyExc_BufferError
|
||||||
added 3.2
|
added 3.2
|
||||||
data PyExc_BytesWarning
|
data PyExc_BytesWarning
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#define PY_SSIZE_T_CLEAN
|
#define PY_SSIZE_T_CLEAN
|
||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include "pycore_initconfig.h"
|
#include "pycore_initconfig.h"
|
||||||
#include "pycore_object.h"
|
#include "pycore_object.h"
|
||||||
#include "structmember.h" // PyMemberDef
|
#include "structmember.h" // PyMemberDef
|
||||||
|
@ -540,7 +541,7 @@ StopIteration_clear(PyStopIterationObject *self)
|
||||||
static void
|
static void
|
||||||
StopIteration_dealloc(PyStopIterationObject *self)
|
StopIteration_dealloc(PyStopIterationObject *self)
|
||||||
{
|
{
|
||||||
_PyObject_GC_UNTRACK(self);
|
PyObject_GC_UnTrack(self);
|
||||||
StopIteration_clear(self);
|
StopIteration_clear(self);
|
||||||
Py_TYPE(self)->tp_free((PyObject *)self);
|
Py_TYPE(self)->tp_free((PyObject *)self);
|
||||||
}
|
}
|
||||||
|
@ -629,6 +630,516 @@ ComplexExtendsException(PyExc_BaseException, SystemExit, SystemExit,
|
||||||
0, 0, SystemExit_members, 0, 0,
|
0, 0, SystemExit_members, 0, 0,
|
||||||
"Request to exit from the interpreter.");
|
"Request to exit from the interpreter.");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* BaseExceptionGroup extends BaseException
|
||||||
|
* ExceptionGroup extends BaseExceptionGroup and Exception
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
static inline PyBaseExceptionGroupObject*
|
||||||
|
_PyBaseExceptionGroupObject_cast(PyObject *exc)
|
||||||
|
{
|
||||||
|
assert(_PyBaseExceptionGroup_Check(exc));
|
||||||
|
return (PyBaseExceptionGroupObject *)exc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
BaseExceptionGroup_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
|
{
|
||||||
|
struct _Py_exc_state *state = get_exc_state();
|
||||||
|
PyTypeObject *PyExc_ExceptionGroup =
|
||||||
|
(PyTypeObject*)state->PyExc_ExceptionGroup;
|
||||||
|
|
||||||
|
PyObject *message = NULL;
|
||||||
|
PyObject *exceptions = NULL;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "UO", &message, &exceptions)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PySequence_Check(exceptions)) {
|
||||||
|
PyErr_SetString(
|
||||||
|
PyExc_TypeError,
|
||||||
|
"second argument (exceptions) must be a sequence");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
exceptions = PySequence_Tuple(exceptions);
|
||||||
|
if (!exceptions) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We are now holding a ref to the exceptions tuple */
|
||||||
|
|
||||||
|
Py_ssize_t numexcs = PyTuple_GET_SIZE(exceptions);
|
||||||
|
if (numexcs == 0) {
|
||||||
|
PyErr_SetString(
|
||||||
|
PyExc_ValueError,
|
||||||
|
"second argument (exceptions) must be a non-empty sequence");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nested_base_exceptions = false;
|
||||||
|
for (Py_ssize_t i = 0; i < numexcs; i++) {
|
||||||
|
PyObject *exc = PyTuple_GET_ITEM(exceptions, i);
|
||||||
|
if (!exc) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (!PyExceptionInstance_Check(exc)) {
|
||||||
|
PyErr_Format(
|
||||||
|
PyExc_ValueError,
|
||||||
|
"Item %d of second argument (exceptions) is not an exception",
|
||||||
|
i);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
int is_nonbase_exception = PyObject_IsInstance(exc, PyExc_Exception);
|
||||||
|
if (is_nonbase_exception < 0) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
else if (is_nonbase_exception == 0) {
|
||||||
|
nested_base_exceptions = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyTypeObject *cls = type;
|
||||||
|
if (cls == PyExc_ExceptionGroup) {
|
||||||
|
if (nested_base_exceptions) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"Cannot nest BaseExceptions in an ExceptionGroup");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (cls == (PyTypeObject*)PyExc_BaseExceptionGroup) {
|
||||||
|
if (!nested_base_exceptions) {
|
||||||
|
/* All nested exceptions are Exception subclasses,
|
||||||
|
* wrap them in an ExceptionGroup
|
||||||
|
*/
|
||||||
|
cls = PyExc_ExceptionGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Do nothing - we don't interfere with subclasses */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cls) {
|
||||||
|
/* Don't crash during interpreter shutdown
|
||||||
|
* (PyExc_ExceptionGroup may have been cleared)
|
||||||
|
*/
|
||||||
|
cls = (PyTypeObject*)PyExc_BaseExceptionGroup;
|
||||||
|
}
|
||||||
|
PyBaseExceptionGroupObject *self =
|
||||||
|
_PyBaseExceptionGroupObject_cast(BaseException_new(cls, args, kwds));
|
||||||
|
if (!self) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->msg = Py_NewRef(message);
|
||||||
|
self->excs = exceptions;
|
||||||
|
return (PyObject*)self;
|
||||||
|
error:
|
||||||
|
Py_DECREF(exceptions);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
BaseExceptionGroup_init(PyBaseExceptionGroupObject *self,
|
||||||
|
PyObject *args, PyObject *kwds)
|
||||||
|
{
|
||||||
|
if (!_PyArg_NoKeywords(Py_TYPE(self)->tp_name, kwds)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (BaseException_init((PyBaseExceptionObject *)self, args, kwds) == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
BaseExceptionGroup_clear(PyBaseExceptionGroupObject *self)
|
||||||
|
{
|
||||||
|
Py_CLEAR(self->msg);
|
||||||
|
Py_CLEAR(self->excs);
|
||||||
|
return BaseException_clear((PyBaseExceptionObject *)self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
BaseExceptionGroup_dealloc(PyBaseExceptionGroupObject *self)
|
||||||
|
{
|
||||||
|
_PyObject_GC_UNTRACK(self);
|
||||||
|
BaseExceptionGroup_clear(self);
|
||||||
|
Py_TYPE(self)->tp_free((PyObject *)self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
BaseExceptionGroup_traverse(PyBaseExceptionGroupObject *self,
|
||||||
|
visitproc visit, void *arg)
|
||||||
|
{
|
||||||
|
Py_VISIT(self->msg);
|
||||||
|
Py_VISIT(self->excs);
|
||||||
|
return BaseException_traverse((PyBaseExceptionObject *)self, visit, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
BaseExceptionGroup_str(PyBaseExceptionGroupObject *self)
|
||||||
|
{
|
||||||
|
assert(self->msg);
|
||||||
|
assert(PyUnicode_Check(self->msg));
|
||||||
|
return Py_NewRef(self->msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
BaseExceptionGroup_derive(PyObject *self_, PyObject *args)
|
||||||
|
{
|
||||||
|
PyBaseExceptionGroupObject *self = _PyBaseExceptionGroupObject_cast(self_);
|
||||||
|
PyObject *excs = NULL;
|
||||||
|
if (!PyArg_ParseTuple(args, "O", &excs)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PyObject *init_args = PyTuple_Pack(2, self->msg, excs);
|
||||||
|
if (!init_args) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PyObject *eg = PyObject_CallObject(
|
||||||
|
PyExc_BaseExceptionGroup, init_args);
|
||||||
|
Py_DECREF(init_args);
|
||||||
|
return eg;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
exceptiongroup_subset(
|
||||||
|
PyBaseExceptionGroupObject *_orig, PyObject *excs, PyObject **result)
|
||||||
|
{
|
||||||
|
/* Sets *result to an ExceptionGroup wrapping excs with metadata from
|
||||||
|
* _orig. If excs is empty, sets *result to NULL.
|
||||||
|
* Returns 0 on success and -1 on error.
|
||||||
|
|
||||||
|
* This function is used by split() to construct the match/rest parts,
|
||||||
|
* so excs is the matching or non-matching sub-sequence of orig->excs
|
||||||
|
* (this function does not verify that it is a subsequence).
|
||||||
|
*/
|
||||||
|
PyObject *orig = (PyObject *)_orig;
|
||||||
|
|
||||||
|
*result = NULL;
|
||||||
|
Py_ssize_t num_excs = PySequence_Size(excs);
|
||||||
|
if (num_excs < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (num_excs == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *eg = PyObject_CallMethod(
|
||||||
|
orig, "derive", "(O)", excs);
|
||||||
|
if (!eg) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_PyBaseExceptionGroup_Check(eg)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"derive must return an instance of BaseExceptionGroup");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now we hold a reference to the new eg */
|
||||||
|
|
||||||
|
PyObject *tb = PyException_GetTraceback(orig);
|
||||||
|
if (tb) {
|
||||||
|
int res = PyException_SetTraceback(eg, tb);
|
||||||
|
Py_DECREF(tb);
|
||||||
|
if (res == -1) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PyException_SetContext(eg, PyException_GetContext(orig));
|
||||||
|
PyException_SetCause(eg, PyException_GetCause(orig));
|
||||||
|
*result = eg;
|
||||||
|
return 0;
|
||||||
|
error:
|
||||||
|
Py_DECREF(eg);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
/* Exception type or tuple of thereof */
|
||||||
|
EXCEPTION_GROUP_MATCH_BY_TYPE = 0,
|
||||||
|
/* A PyFunction returning True for matching exceptions */
|
||||||
|
EXCEPTION_GROUP_MATCH_BY_PREDICATE = 1,
|
||||||
|
/* An instance or container thereof, checked with equality
|
||||||
|
* This matcher type is only used internally by the
|
||||||
|
* interpreter, it is not exposed to python code */
|
||||||
|
EXCEPTION_GROUP_MATCH_INSTANCES = 2
|
||||||
|
} _exceptiongroup_split_matcher_type;
|
||||||
|
|
||||||
|
static int
|
||||||
|
get_matcher_type(PyObject *value,
|
||||||
|
_exceptiongroup_split_matcher_type *type)
|
||||||
|
{
|
||||||
|
/* the python API supports only BY_TYPE and BY_PREDICATE */
|
||||||
|
if (PyExceptionClass_Check(value) ||
|
||||||
|
PyTuple_CheckExact(value)) {
|
||||||
|
*type = EXCEPTION_GROUP_MATCH_BY_TYPE;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (PyFunction_Check(value)) {
|
||||||
|
*type = EXCEPTION_GROUP_MATCH_BY_PREDICATE;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
PyErr_SetString(
|
||||||
|
PyExc_TypeError,
|
||||||
|
"expected a function, exception type or tuple of exception types");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
exceptiongroup_split_check_match(PyObject *exc,
|
||||||
|
_exceptiongroup_split_matcher_type matcher_type,
|
||||||
|
PyObject *matcher_value)
|
||||||
|
{
|
||||||
|
switch (matcher_type) {
|
||||||
|
case EXCEPTION_GROUP_MATCH_BY_TYPE: {
|
||||||
|
assert(PyExceptionClass_Check(matcher_value) ||
|
||||||
|
PyTuple_CheckExact(matcher_value));
|
||||||
|
return PyErr_GivenExceptionMatches(exc, matcher_value);
|
||||||
|
}
|
||||||
|
case EXCEPTION_GROUP_MATCH_BY_PREDICATE: {
|
||||||
|
assert(PyFunction_Check(matcher_value));
|
||||||
|
PyObject *exc_matches = PyObject_CallOneArg(matcher_value, exc);
|
||||||
|
if (exc_matches == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int is_true = PyObject_IsTrue(exc_matches);
|
||||||
|
Py_DECREF(exc_matches);
|
||||||
|
return is_true;
|
||||||
|
}
|
||||||
|
case EXCEPTION_GROUP_MATCH_INSTANCES: {
|
||||||
|
if (PySequence_Check(matcher_value)) {
|
||||||
|
return PySequence_Contains(matcher_value, exc);
|
||||||
|
}
|
||||||
|
return matcher_value == exc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyObject *match;
|
||||||
|
PyObject *rest;
|
||||||
|
} _exceptiongroup_split_result;
|
||||||
|
|
||||||
|
static int
|
||||||
|
exceptiongroup_split_recursive(PyObject *exc,
|
||||||
|
_exceptiongroup_split_matcher_type matcher_type,
|
||||||
|
PyObject *matcher_value,
|
||||||
|
bool construct_rest,
|
||||||
|
_exceptiongroup_split_result *result)
|
||||||
|
{
|
||||||
|
result->match = NULL;
|
||||||
|
result->rest = NULL;
|
||||||
|
|
||||||
|
int is_match = exceptiongroup_split_check_match(
|
||||||
|
exc, matcher_type, matcher_value);
|
||||||
|
if (is_match < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_match) {
|
||||||
|
/* Full match */
|
||||||
|
result->match = Py_NewRef(exc);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if (!_PyBaseExceptionGroup_Check(exc)) {
|
||||||
|
/* Leaf exception and no match */
|
||||||
|
if (construct_rest) {
|
||||||
|
result->rest = Py_NewRef(exc);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Partial match */
|
||||||
|
|
||||||
|
PyBaseExceptionGroupObject *eg = _PyBaseExceptionGroupObject_cast(exc);
|
||||||
|
assert(PyTuple_CheckExact(eg->excs));
|
||||||
|
Py_ssize_t num_excs = PyTuple_Size(eg->excs);
|
||||||
|
if (num_excs < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
assert(num_excs > 0); /* checked in constructor, and excs is read-only */
|
||||||
|
|
||||||
|
int retval = -1;
|
||||||
|
PyObject *match_list = PyList_New(0);
|
||||||
|
if (!match_list) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *rest_list = NULL;
|
||||||
|
if (construct_rest) {
|
||||||
|
rest_list = PyList_New(0);
|
||||||
|
if (!rest_list) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* recursive calls */
|
||||||
|
for (Py_ssize_t i = 0; i < num_excs; i++) {
|
||||||
|
PyObject *e = PyTuple_GET_ITEM(eg->excs, i);
|
||||||
|
_exceptiongroup_split_result rec_result;
|
||||||
|
if (Py_EnterRecursiveCall(" in exceptiongroup_split_recursive")) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (exceptiongroup_split_recursive(
|
||||||
|
e, matcher_type, matcher_value,
|
||||||
|
construct_rest, &rec_result) == -1) {
|
||||||
|
assert(!rec_result.match);
|
||||||
|
assert(!rec_result.rest);
|
||||||
|
Py_LeaveRecursiveCall();
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
Py_LeaveRecursiveCall();
|
||||||
|
if (rec_result.match) {
|
||||||
|
assert(PyList_CheckExact(match_list));
|
||||||
|
if (PyList_Append(match_list, rec_result.match) == -1) {
|
||||||
|
Py_DECREF(rec_result.match);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
Py_DECREF(rec_result.match);
|
||||||
|
}
|
||||||
|
if (rec_result.rest) {
|
||||||
|
assert(construct_rest);
|
||||||
|
assert(PyList_CheckExact(rest_list));
|
||||||
|
if (PyList_Append(rest_list, rec_result.rest) == -1) {
|
||||||
|
Py_DECREF(rec_result.rest);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
Py_DECREF(rec_result.rest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* construct result */
|
||||||
|
if (exceptiongroup_subset(eg, match_list, &result->match) == -1) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (construct_rest) {
|
||||||
|
assert(PyList_CheckExact(rest_list));
|
||||||
|
if (exceptiongroup_subset(eg, rest_list, &result->rest) == -1) {
|
||||||
|
Py_CLEAR(result->match);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
Py_DECREF(match_list);
|
||||||
|
Py_XDECREF(rest_list);
|
||||||
|
if (retval == -1) {
|
||||||
|
Py_CLEAR(result->match);
|
||||||
|
Py_CLEAR(result->rest);
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
BaseExceptionGroup_split(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
PyObject *matcher_value = NULL;
|
||||||
|
if (!PyArg_UnpackTuple(args, "split", 1, 1, &matcher_value)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
_exceptiongroup_split_matcher_type matcher_type;
|
||||||
|
if (get_matcher_type(matcher_value, &matcher_type) == -1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
_exceptiongroup_split_result split_result;
|
||||||
|
bool construct_rest = true;
|
||||||
|
if (exceptiongroup_split_recursive(
|
||||||
|
self, matcher_type, matcher_value,
|
||||||
|
construct_rest, &split_result) == -1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *result = PyTuple_Pack(
|
||||||
|
2,
|
||||||
|
split_result.match ? split_result.match : Py_None,
|
||||||
|
split_result.rest ? split_result.rest : Py_None);
|
||||||
|
|
||||||
|
Py_XDECREF(split_result.match);
|
||||||
|
Py_XDECREF(split_result.rest);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
BaseExceptionGroup_subgroup(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
PyObject *matcher_value = NULL;
|
||||||
|
if (!PyArg_UnpackTuple(args, "subgroup", 1, 1, &matcher_value)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
_exceptiongroup_split_matcher_type matcher_type;
|
||||||
|
if (get_matcher_type(matcher_value, &matcher_type) == -1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
_exceptiongroup_split_result split_result;
|
||||||
|
bool construct_rest = false;
|
||||||
|
if (exceptiongroup_split_recursive(
|
||||||
|
self, matcher_type, matcher_value,
|
||||||
|
construct_rest, &split_result) == -1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *result = Py_NewRef(
|
||||||
|
split_result.match ? split_result.match : Py_None);
|
||||||
|
|
||||||
|
Py_XDECREF(split_result.match);
|
||||||
|
assert(!split_result.rest);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMemberDef BaseExceptionGroup_members[] = {
|
||||||
|
{"message", T_OBJECT, offsetof(PyBaseExceptionGroupObject, msg), READONLY,
|
||||||
|
PyDoc_STR("exception message")},
|
||||||
|
{"exceptions", T_OBJECT, offsetof(PyBaseExceptionGroupObject, excs), READONLY,
|
||||||
|
PyDoc_STR("nested exceptions")},
|
||||||
|
{NULL} /* Sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyMethodDef BaseExceptionGroup_methods[] = {
|
||||||
|
{"__class_getitem__", (PyCFunction)Py_GenericAlias,
|
||||||
|
METH_O|METH_CLASS, PyDoc_STR("See PEP 585")},
|
||||||
|
{"derive", (PyCFunction)BaseExceptionGroup_derive, METH_VARARGS},
|
||||||
|
{"split", (PyCFunction)BaseExceptionGroup_split, METH_VARARGS},
|
||||||
|
{"subgroup", (PyCFunction)BaseExceptionGroup_subgroup, METH_VARARGS},
|
||||||
|
{NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
ComplexExtendsException(PyExc_BaseException, BaseExceptionGroup,
|
||||||
|
BaseExceptionGroup, BaseExceptionGroup_new /* new */,
|
||||||
|
BaseExceptionGroup_methods, BaseExceptionGroup_members,
|
||||||
|
0 /* getset */, BaseExceptionGroup_str,
|
||||||
|
"A combination of multiple unrelated exceptions.");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ExceptionGroup extends BaseExceptionGroup, Exception
|
||||||
|
*/
|
||||||
|
static PyObject*
|
||||||
|
create_exception_group_class(void) {
|
||||||
|
struct _Py_exc_state *state = get_exc_state();
|
||||||
|
|
||||||
|
PyObject *bases = PyTuple_Pack(
|
||||||
|
2, PyExc_BaseExceptionGroup, PyExc_Exception);
|
||||||
|
if (bases == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!state->PyExc_ExceptionGroup);
|
||||||
|
state->PyExc_ExceptionGroup = PyErr_NewException(
|
||||||
|
"builtins.ExceptionGroup", bases, NULL);
|
||||||
|
|
||||||
|
Py_DECREF(bases);
|
||||||
|
return state->PyExc_ExceptionGroup;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* KeyboardInterrupt extends BaseException
|
* KeyboardInterrupt extends BaseException
|
||||||
*/
|
*/
|
||||||
|
@ -2671,6 +3182,7 @@ _PyExc_Init(PyInterpreterState *interp)
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
PRE_INIT(BaseException);
|
PRE_INIT(BaseException);
|
||||||
|
PRE_INIT(BaseExceptionGroup);
|
||||||
PRE_INIT(Exception);
|
PRE_INIT(Exception);
|
||||||
PRE_INIT(TypeError);
|
PRE_INIT(TypeError);
|
||||||
PRE_INIT(StopAsyncIteration);
|
PRE_INIT(StopAsyncIteration);
|
||||||
|
@ -2805,8 +3317,15 @@ _PyBuiltins_AddExceptions(PyObject *bltinmod)
|
||||||
return _PyStatus_ERR("exceptions bootstrapping error.");
|
return _PyStatus_ERR("exceptions bootstrapping error.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject *PyExc_ExceptionGroup = create_exception_group_class();
|
||||||
|
if (!PyExc_ExceptionGroup) {
|
||||||
|
return _PyStatus_ERR("exceptions bootstrapping error.");
|
||||||
|
}
|
||||||
|
|
||||||
POST_INIT(BaseException);
|
POST_INIT(BaseException);
|
||||||
POST_INIT(Exception);
|
POST_INIT(Exception);
|
||||||
|
POST_INIT(BaseExceptionGroup);
|
||||||
|
POST_INIT(ExceptionGroup);
|
||||||
POST_INIT(TypeError);
|
POST_INIT(TypeError);
|
||||||
POST_INIT(StopAsyncIteration);
|
POST_INIT(StopAsyncIteration);
|
||||||
POST_INIT(StopIteration);
|
POST_INIT(StopIteration);
|
||||||
|
@ -2885,6 +3404,13 @@ _PyBuiltins_AddExceptions(PyObject *bltinmod)
|
||||||
#undef INIT_ALIAS
|
#undef INIT_ALIAS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_PyExc_ClearExceptionGroupType(PyInterpreterState *interp)
|
||||||
|
{
|
||||||
|
struct _Py_exc_state *state = &interp->exc_state;
|
||||||
|
Py_CLEAR(state->PyExc_ExceptionGroup);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
_PyExc_Fini(PyInterpreterState *interp)
|
_PyExc_Fini(PyInterpreterState *interp)
|
||||||
{
|
{
|
||||||
|
|
|
@ -754,6 +754,7 @@ EXPORT_DATA(PyExc_ArithmeticError)
|
||||||
EXPORT_DATA(PyExc_AssertionError)
|
EXPORT_DATA(PyExc_AssertionError)
|
||||||
EXPORT_DATA(PyExc_AttributeError)
|
EXPORT_DATA(PyExc_AttributeError)
|
||||||
EXPORT_DATA(PyExc_BaseException)
|
EXPORT_DATA(PyExc_BaseException)
|
||||||
|
EXPORT_DATA(PyExc_BaseExceptionGroup)
|
||||||
EXPORT_DATA(PyExc_BlockingIOError)
|
EXPORT_DATA(PyExc_BlockingIOError)
|
||||||
EXPORT_DATA(PyExc_BrokenPipeError)
|
EXPORT_DATA(PyExc_BrokenPipeError)
|
||||||
EXPORT_DATA(PyExc_BufferError)
|
EXPORT_DATA(PyExc_BufferError)
|
||||||
|
|
|
@ -1662,6 +1662,8 @@ finalize_interp_clear(PyThreadState *tstate)
|
||||||
{
|
{
|
||||||
int is_main_interp = _Py_IsMainInterpreter(tstate->interp);
|
int is_main_interp = _Py_IsMainInterpreter(tstate->interp);
|
||||||
|
|
||||||
|
_PyExc_ClearExceptionGroupType(tstate->interp);
|
||||||
|
|
||||||
/* Clear interpreter state and all thread states */
|
/* Clear interpreter state and all thread states */
|
||||||
_PyInterpreterState_Clear(tstate);
|
_PyInterpreterState_Clear(tstate);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue