Skip to content

Commit 8110f28

Browse files
committed
gh-95882 fix traceback of exceptions propagated from inside a contextlib.asynccontextmanager
1 parent 63140b4 commit 8110f28

File tree

2 files changed

+30
-0
lines changed

2 files changed

+30
-0
lines changed

Lib/contextlib.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ async def __aexit__(self, typ, value, traceback):
228228
except RuntimeError as exc:
229229
# Don't re-raise the passed in exception. (issue27122)
230230
if exc is value:
231+
exc.__traceback__ = traceback
231232
return False
232233
# Avoid suppressing if a Stop(Async)Iteration exception
233234
# was passed to athrow() and later wrapped into a RuntimeError
@@ -239,6 +240,7 @@ async def __aexit__(self, typ, value, traceback):
239240
isinstance(value, (StopIteration, StopAsyncIteration))
240241
and exc.__cause__ is value
241242
):
243+
exc.__traceback__ = traceback
242244
return False
243245
raise
244246
except BaseException as exc:
@@ -250,6 +252,7 @@ async def __aexit__(self, typ, value, traceback):
250252
# and the __exit__() protocol.
251253
if exc is not value:
252254
raise
255+
exc.__traceback__ = traceback
253256
return False
254257
raise RuntimeError("generator didn't stop after athrow()")
255258

Lib/test/test_contextlib_async.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,33 @@ async def woohoo():
125125
raise ZeroDivisionError()
126126
self.assertEqual(state, [1, 42, 999])
127127

128+
@_async_test
129+
async def test_contextmanager_traceback(self):
130+
@asynccontextmanager
131+
async def f():
132+
yield
133+
134+
try:
135+
async with f():
136+
1/0
137+
except ZeroDivisionError as e:
138+
frames = traceback.extract_tb(e.__traceback__)
139+
140+
self.assertEqual(len(frames), 1)
141+
self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
142+
self.assertEqual(frames[0].line, '1/0')
143+
144+
# Repeat with RuntimeError (which goes through a different code path)
145+
try:
146+
async with f():
147+
raise NotImplementedError(42)
148+
except NotImplementedError as e:
149+
frames = traceback.extract_tb(e.__traceback__)
150+
151+
self.assertEqual(len(frames), 1)
152+
self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
153+
self.assertEqual(frames[0].line, 'raise NotImplementedError(42)')
154+
128155
@_async_test
129156
async def test_contextmanager_no_reraise(self):
130157
@asynccontextmanager

0 commit comments

Comments
 (0)