Skip to content

Commit e61330b

Browse files
authored
pythongh-92118: fix traceback of exceptions propagated from inside a contextlib.contextmanager (pythonGH-92202)
1 parent f8a2fab commit e61330b

File tree

3 files changed

+31
-0
lines changed

3 files changed

+31
-0
lines changed

Lib/contextlib.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ def __exit__(self, typ, value, traceback):
161161
except RuntimeError as exc:
162162
# Don't re-raise the passed in exception. (issue27122)
163163
if exc is value:
164+
exc.__traceback__ = traceback
164165
return False
165166
# Avoid suppressing if a StopIteration exception
166167
# was passed to throw() and later wrapped into a RuntimeError
@@ -172,6 +173,7 @@ def __exit__(self, typ, value, traceback):
172173
isinstance(value, StopIteration)
173174
and exc.__cause__ is value
174175
):
176+
exc.__traceback__ = traceback
175177
return False
176178
raise
177179
except BaseException as exc:
@@ -183,6 +185,7 @@ def __exit__(self, typ, value, traceback):
183185
# and the __exit__() protocol.
184186
if exc is not value:
185187
raise
188+
exc.__traceback__ = traceback
186189
return False
187190
raise RuntimeError("generator didn't stop after throw()")
188191

Lib/test/test_contextlib.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import sys
66
import tempfile
77
import threading
8+
import traceback
89
import unittest
910
from contextlib import * # Tests __all__
1011
from test import support
@@ -87,6 +88,32 @@ def woohoo():
8788
raise ZeroDivisionError()
8889
self.assertEqual(state, [1, 42, 999])
8990

91+
def test_contextmanager_traceback(self):
92+
@contextmanager
93+
def f():
94+
yield
95+
96+
try:
97+
with f():
98+
1/0
99+
except ZeroDivisionError as e:
100+
frames = traceback.extract_tb(e.__traceback__)
101+
102+
self.assertEqual(len(frames), 1)
103+
self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
104+
self.assertEqual(frames[0].line, '1/0')
105+
106+
# Repeat with RuntimeError (which goes through a different code path)
107+
try:
108+
with f():
109+
raise NotImplementedError(42)
110+
except NotImplementedError as e:
111+
frames = traceback.extract_tb(e.__traceback__)
112+
113+
self.assertEqual(len(frames), 1)
114+
self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
115+
self.assertEqual(frames[0].line, 'raise NotImplementedError(42)')
116+
90117
def test_contextmanager_no_reraise(self):
91118
@contextmanager
92119
def whee():
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a 3.11 regression in :func:`~contextlib.contextmanager`, which caused it to propagate exceptions with incorrect tracebacks.

0 commit comments

Comments
 (0)