Skip to content

Commit ca21f97

Browse files
committed
Move sys._set_exception() to _contextlib._setup_genmgr_ctx
Since this is only used for a contextlib-specific hack, having it exposed in the sys module (even with an underscore) might not be a good idea.
1 parent 3ddb383 commit ca21f97

13 files changed

Lines changed: 358 additions & 219 deletions

File tree

‎Lib/contextlib.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
"chdir"]
2121

2222

23+
try:
24+
from _contextlib import _setup_genmgr_ctx
25+
except ImportError:
26+
_setup_genmgr_ctx = None
27+
28+
2329
class AbstractContextManager(abc.ABC):
2430

2531
"""An abstract base class for context managers."""
@@ -239,7 +245,8 @@ def __exit__(self, typ, value, traceback):
239245
# context to be what it was just before the generator's yield
240246
# statement before throwing the current exception into it.
241247
# (see gh-111676).
242-
sys._set_exception(exc_context)
248+
if _setup_genmgr_ctx is not None:
249+
_setup_genmgr_ctx(exc_context)
243250
self.gen.throw(value)
244251
except StopIteration as exc:
245252
# Suppress StopIteration *unless* it's the same exception that

‎Lib/test/test_contextlib.py‎

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,5 +1671,138 @@ def test_exception(self):
16711671
self.assertEqual(os.getcwd(), old_cwd)
16721672

16731673

1674+
try:
1675+
from _contextlib import _setup_genmgr_ctx
1676+
except ImportError:
1677+
_setup_genmgr_ctx = None
1678+
1679+
if _setup_genmgr_ctx:
1680+
class SetExceptionTests(unittest.TestCase):
1681+
1682+
def tearDown(self):
1683+
# make sure we don't leave the global exception set
1684+
_setup_genmgr_ctx(None);
1685+
1686+
def test_set_exc_invalid_values(self):
1687+
for x in (0, "1", b"2"):
1688+
with self.assertRaises(TypeError):
1689+
_setup_genmgr_ctx(x);
1690+
1691+
def test_clear_exc(self):
1692+
try:
1693+
raise ValueError()
1694+
except ValueError:
1695+
self.assertIsInstance(sys.exception(), ValueError)
1696+
_setup_genmgr_ctx(None)
1697+
self.assertIsNone(sys.exception())
1698+
1699+
def test_set_exc(self):
1700+
exc = ValueError()
1701+
self.assertIsNone(sys.exception())
1702+
_setup_genmgr_ctx(exc)
1703+
self.assertIs(sys.exception(), exc)
1704+
1705+
def test_set_exc_replaced_by_new_exception_and_restored(self):
1706+
exc = ValueError()
1707+
_setup_genmgr_ctx(exc)
1708+
self.assertIs(sys.exception(), exc)
1709+
try:
1710+
raise TypeError()
1711+
except TypeError:
1712+
self.assertIsInstance(sys.exception(), TypeError)
1713+
self.assertIs(sys.exception().__context__, exc)
1714+
1715+
self.assertIs(sys.exception(), exc)
1716+
1717+
def test_set_exc_popped_on_exit_except(self):
1718+
exc = ValueError()
1719+
try:
1720+
raise TypeError()
1721+
except TypeError:
1722+
self.assertIsInstance(sys.exception(), TypeError)
1723+
_setup_genmgr_ctx(exc)
1724+
self.assertIs(sys.exception(), exc)
1725+
self.assertIsNone(sys.exception())
1726+
1727+
def test_cleared_exc_overridden_and_restored(self):
1728+
try:
1729+
raise ValueError()
1730+
except ValueError:
1731+
try:
1732+
raise TypeError()
1733+
except TypeError:
1734+
self.assertIsInstance(sys.exception(), TypeError)
1735+
_setup_genmgr_ctx(None)
1736+
self.assertIsNone(sys.exception())
1737+
try:
1738+
raise IndexError()
1739+
except IndexError:
1740+
self.assertIsInstance(sys.exception(), IndexError)
1741+
self.assertIsNone(sys.exception().__context__)
1742+
self.assertIsNone(sys.exception())
1743+
self.assertIsInstance(sys.exception(), ValueError)
1744+
self.assertIsNone(sys.exception())
1745+
1746+
def test_clear_exc_in_generator(self):
1747+
def inner():
1748+
self.assertIsNone(sys.exception())
1749+
yield
1750+
self.assertIsInstance(sys.exception(), ValueError)
1751+
_setup_genmgr_ctx(None)
1752+
self.assertIsNone(sys.exception())
1753+
yield
1754+
self.assertIsNone(sys.exception())
1755+
1756+
# with a single exception in exc_info stack
1757+
g = inner()
1758+
next(g)
1759+
try:
1760+
raise ValueError()
1761+
except:
1762+
self.assertIsInstance(sys.exception(), ValueError)
1763+
next(g)
1764+
self.assertIsInstance(sys.exception(), ValueError)
1765+
self.assertIsNone(sys.exception())
1766+
with self.assertRaises(StopIteration):
1767+
next(g)
1768+
self.assertIsNone(sys.exception())
1769+
1770+
# with multiple exceptions in exc_info stack by chaining generators
1771+
def outer():
1772+
g = inner()
1773+
self.assertIsNone(sys.exception())
1774+
yield next(g)
1775+
self.assertIsInstance(sys.exception(), TypeError)
1776+
try:
1777+
raise ValueError()
1778+
except:
1779+
self.assertIsInstance(sys.exception(), ValueError)
1780+
self.assertIsInstance(sys.exception().__context__, TypeError)
1781+
yield next(g)
1782+
# at this point the TypeError from the caller has been handled
1783+
# by the caller's except block. Even still, it should still be
1784+
# referenced as the __context__ of the current exception.
1785+
self.assertIsInstance(sys.exception(), ValueError)
1786+
self.assertIsInstance(sys.exception().__context__, TypeError)
1787+
# not handling an exception, caller isn't handling one either
1788+
self.assertIsNone(sys.exception())
1789+
with self.assertRaises(StopIteration):
1790+
next(g)
1791+
self.assertIsNone(sys.exception())
1792+
1793+
g = outer()
1794+
next(g)
1795+
try:
1796+
raise TypeError()
1797+
except:
1798+
self.assertIsInstance(sys.exception(), TypeError)
1799+
next(g)
1800+
self.assertIsInstance(sys.exception(), TypeError)
1801+
self.assertIsNone(sys.exception())
1802+
with self.assertRaises(StopIteration):
1803+
next(g)
1804+
self.assertIsNone(sys.exception())
1805+
1806+
16741807
if __name__ == "__main__":
16751808
unittest.main()

‎Lib/test/test_sys.py‎

Lines changed: 0 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -160,131 +160,6 @@ def f():
160160
self.assertIsInstance(e, ValueError)
161161
self.assertIs(exc, e)
162162

163-
class SetExceptionTests(unittest.TestCase):
164-
165-
def tearDown(self):
166-
# make sure we don't leave the global exception set
167-
sys._set_exception(None);
168-
169-
def test_set_exc_invalid_values(self):
170-
for x in (0, "1", b"2"):
171-
with self.assertRaises(TypeError):
172-
sys._set_exception(x);
173-
174-
def test_clear_exc(self):
175-
try:
176-
raise ValueError()
177-
except ValueError:
178-
self.assertIsInstance(sys.exception(), ValueError)
179-
sys._set_exception(None)
180-
self.assertIsNone(sys.exception())
181-
182-
def test_set_exc(self):
183-
exc = ValueError()
184-
self.assertIsNone(sys.exception())
185-
sys._set_exception(exc)
186-
self.assertIs(sys.exception(), exc)
187-
188-
def test_set_exc_replaced_by_new_exception_and_restored(self):
189-
exc = ValueError()
190-
sys._set_exception(exc)
191-
self.assertIs(sys.exception(), exc)
192-
try:
193-
raise TypeError()
194-
except TypeError:
195-
self.assertIsInstance(sys.exception(), TypeError)
196-
self.assertIs(sys.exception().__context__, exc)
197-
198-
self.assertIs(sys.exception(), exc)
199-
200-
def test_set_exc_popped_on_exit_except(self):
201-
exc = ValueError()
202-
try:
203-
raise TypeError()
204-
except TypeError:
205-
self.assertIsInstance(sys.exception(), TypeError)
206-
sys._set_exception(exc)
207-
self.assertIs(sys.exception(), exc)
208-
self.assertIsNone(sys.exception())
209-
210-
def test_cleared_exc_overridden_and_restored(self):
211-
try:
212-
raise ValueError()
213-
except ValueError:
214-
try:
215-
raise TypeError()
216-
except TypeError:
217-
self.assertIsInstance(sys.exception(), TypeError)
218-
sys._set_exception(None)
219-
self.assertIsNone(sys.exception())
220-
try:
221-
raise IndexError()
222-
except IndexError:
223-
self.assertIsInstance(sys.exception(), IndexError)
224-
self.assertIsNone(sys.exception().__context__)
225-
self.assertIsNone(sys.exception())
226-
self.assertIsInstance(sys.exception(), ValueError)
227-
self.assertIsNone(sys.exception())
228-
229-
def test_clear_exc_in_generator(self):
230-
def inner():
231-
self.assertIsNone(sys.exception())
232-
yield
233-
self.assertIsInstance(sys.exception(), ValueError)
234-
sys._set_exception(None)
235-
self.assertIsNone(sys.exception())
236-
yield
237-
self.assertIsNone(sys.exception())
238-
239-
# with a single exception in exc_info stack
240-
g = inner()
241-
next(g)
242-
try:
243-
raise ValueError()
244-
except:
245-
self.assertIsInstance(sys.exception(), ValueError)
246-
next(g)
247-
self.assertIsInstance(sys.exception(), ValueError)
248-
self.assertIsNone(sys.exception())
249-
with self.assertRaises(StopIteration):
250-
next(g)
251-
self.assertIsNone(sys.exception())
252-
253-
# with multiple exceptions in exc_info stack by chaining generators
254-
def outer():
255-
g = inner()
256-
self.assertIsNone(sys.exception())
257-
yield next(g)
258-
self.assertIsInstance(sys.exception(), TypeError)
259-
try:
260-
raise ValueError()
261-
except:
262-
self.assertIsInstance(sys.exception(), ValueError)
263-
self.assertIsInstance(sys.exception().__context__, TypeError)
264-
yield next(g)
265-
# at this point the TypeError from the caller has been handled
266-
# by the caller's except block. Even still, it should still be
267-
# referenced as the __context__ of the current exception.
268-
self.assertIsInstance(sys.exception(), ValueError)
269-
self.assertIsInstance(sys.exception().__context__, TypeError)
270-
# not handling an exception, caller isn't handling one either
271-
self.assertIsNone(sys.exception())
272-
with self.assertRaises(StopIteration):
273-
next(g)
274-
self.assertIsNone(sys.exception())
275-
276-
g = outer()
277-
next(g)
278-
try:
279-
raise TypeError()
280-
except:
281-
self.assertIsInstance(sys.exception(), TypeError)
282-
next(g)
283-
self.assertIsInstance(sys.exception(), TypeError)
284-
self.assertIsNone(sys.exception())
285-
with self.assertRaises(StopIteration):
286-
next(g)
287-
self.assertIsNone(sys.exception())
288163

289164
class ExceptHookTest(unittest.TestCase):
290165

‎Misc/NEWS.d/next/Core_and_Builtins/2023-12-19-17-27-08.gh-issue-111375.XeHOl_.rst‎

Lines changed: 0 additions & 2 deletions
This file was deleted.

‎Modules/Setup.bootstrap.in‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ _weakref _weakref.c
3131

3232
# commonly used core modules
3333
_abc _abc.c
34+
_contextlib _contextlibmodule.c
3435
_functools _functoolsmodule.c
3536
_locale _localemodule.c
3637
_opcode _opcode.c

‎Modules/_contextlibmodule.c‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
2+
/* Contextlib module */
3+
4+
#include "Python.h"
5+
6+
#include "clinic/_contextlibmodule.c.h"
7+
8+
/*[clinic input]
9+
module _contextlib
10+
[clinic start generated code]*/
11+
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=60c4760e25c64b5e]*/
12+
13+
14+
/*[clinic input]
15+
_contextlib._setup_genmgr_ctx
16+
exception: object
17+
18+
Force the provided exception to be set as the current exception
19+
20+
Subsequent calls to sys.exception()/sys.exc_info() will return
21+
the provided exception until another exception is caught in the
22+
current thread or the execution stack returns to a frame where
23+
another exception is being handled.
24+
[clinic start generated code]*/
25+
26+
static PyObject *
27+
_contextlib__setup_genmgr_ctx_impl(PyObject *module, PyObject *exception)
28+
/*[clinic end generated code: output=d8996a9f8f52204b input=d61410ba5659bf6f]*/
29+
{
30+
if (!Py_IsNone(exception) && !PyExceptionInstance_Check(exception)){
31+
PyErr_SetString(
32+
PyExc_TypeError,
33+
"must be an exception/None"
34+
);
35+
return NULL;
36+
}
37+
PyErr_SetHandledException(exception);
38+
Py_RETURN_NONE;
39+
}
40+
41+
42+
/* module level code ********************************************************/
43+
44+
PyDoc_STRVAR(_contextlib_doc,
45+
"Helpers for contextlib.");
46+
47+
static PyMethodDef contextlib_methods[] = {
48+
_CONTEXTLIB__SETUP_GENMGR_CTX_METHODDEF
49+
{NULL, NULL} /* sentinel */
50+
};
51+
52+
static struct PyModuleDef _contextlibmodule = {
53+
PyModuleDef_HEAD_INIT,
54+
.m_name = "_contextlib",
55+
.m_doc = _contextlib_doc,
56+
.m_size = -1,
57+
.m_methods = contextlib_methods,
58+
};
59+
60+
PyMODINIT_FUNC
61+
PyInit__contextlib(void)
62+
{
63+
return PyModule_Create(&_contextlibmodule);
64+
}

0 commit comments

Comments
 (0)