When an exception propagates through a frame that has an exception handler (try/except … raise, or try/finally), the traceback loses every frame below that frame. With except … raise, the frame that holds the handler also points at the except line instead of the call that raised.
Reproduced with pydantic-monty 1.0.0, compared with CPython 3.14.8.
Reproducer
def g():
raise ValueError('boom')
def f():
try:
g()
except BaseException:
raise
f()Run with:
import pydantic_monty as pm
with pm.Monty() as pool:
with pool.checkout(script_name='main.py') as session:
session.feed_run(CODE)Monty 1.0.0
Traceback (most recent call last):
File "<python-input-0>", line 10, in <module>
f()
~~~
File "<python-input-0>", line 7, in f
except BaseException:
ValueError: boomCPython 3.14.8
Traceback (most recent call last):
File "main.py", line 10, in <module>
f()
~^^
File "main.py", line 6, in f
g()
~^^
File "main.py", line 2, in g
raise ValueError('boom')
ValueError: boomThe g frame is missing, and f's frame shows line 7 (except BaseException:) instead of line 6 (g()).
Variants
try/finally(replace theexceptclause withfinally: pass): thegframe is also missing, thoughf's frame does show line 6 (g()).- No handler (
fcallsg()directly): the traceback is complete and matches CPython, so the frames are lost only when the exception passes through a handler.
Why it matters
Re-raising from a handler and cleanup in finally are common in ordinary code, and the frame that's lost is the one where the error actually happened. In our case (an embedder that runs a program's own modules through exec() inside a prelude), we had to write the prelude without any try/finally around the module calls, so that tracebacks from inside the modules aren't cut.
Expected
As in CPython, an exception that passes through a handler (re-raised bare, or after a finally) keeps the frames it collected below that frame, and each frame keeps the location of its call.
Activity
2I've implemented traceback preservation for bare re-raises, with regression coverage, in my fork: https://github.com/Tiancheng-Xu/monty/tree/fix/preserve-reraised-traceback
The upstream PR is https://github.com/pydantic/monty/pull/973. It's still under review, and the snapshot-serialization feedback needs targeted verification.
I'd like to work on this. I'll compare traceback propagation with CPython and add regression cases for bare re-raises and finally blocks, preserving the originating frames and call-site locations.