Skip to content

Retain execution context under handled exception #11512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
jaraco opened this issue Oct 17, 2023 · 4 comments
Open

Retain execution context under handled exception #11512

jaraco opened this issue Oct 17, 2023 · 4 comments
Labels
type: proposal proposal for a new feature, often to gather opinions or design the API around the new feature

Comments

@jaraco
Copy link
Contributor

jaraco commented Oct 17, 2023

What's the problem this feature will solve?

I've got a test similar to the following with a nested exception:

def raise_something():
  raise TypeError()


def test_something():
  try:
    raise_something()
  except TypeError:
    raise ValueError()

Running the test produces:

    def raise_something():
>     raise TypeError()
E     TypeError

test.py:2: TypeError

During handling of the above exception, another exception occurred:

    def test_something():
      try:
        raise_something()
      except TypeError:
>       raise ValueError()
E       ValueError

test.py:9: ValueError

I'd like to be able to inspect the stack where raise_something() failed. Best I can tell, that context is unreachable.

If I use --pdb, the code breaks at test.py:9 with the ValueError, but by the time pytest has handled the error, sys.exc_info() no longer points to the execution context where the failure occurred, but instead may be (None, None, None) or perhaps an unrelated context like (<class 'AttributeError'>, AttributeError("'PytestPdbWrapper' object has no attribute 'do_sys'"), <traceback object at 0x101f26c40>). Therefore, I'm unable to take advantages of techniques to trace to the inner exception stack.

Describe the solution you'd like

In the pdb context or in a global variable or in some other way, expose the original unhandled exception context.

@Zac-HD
Copy link
Member

Zac-HD commented Oct 21, 2023

Can you inspect the .__context__ of the ValueError? That'd be the usual way of working with chained exceptions.

@Zac-HD Zac-HD added the type: proposal proposal for a new feature, often to gather opinions or design the API around the new feature label Oct 21, 2023
@jaraco
Copy link
Contributor Author

jaraco commented Nov 5, 2023

Can you inspect the .__context__ of the ValueError?

Perhaps, but how do I get a handle on that exception instance? In the SO post, OP points out that sys.last_value is viable in my example:

> /Users/jaraco/draft/test_something.py(9)test_something()
-> raise ValueError()
(Pdb) import sys
(Pdb) sys.last_value
ValueError()
(Pdb) sys.last_value.__context__
TypeError()
(Pdb) import pdb, sys; pdb.post_mortem(sys.last_value.__context__.__traceback__)
> /Users/jaraco/draft/test_something.py(2)raise_something()
-> raise TypeError()
(Pdb) 

I'm 90% sure when I attempted this in a real-world case, sys.last_value wasn't viable (had been cleared), I suspect by the presence of a fixture or by the fixture itself. I don't recall now what I was working on when I stumbled into this issue, so I may struggle to replicate the situation I had encountered.

@jaraco
Copy link
Contributor Author

jaraco commented Nov 5, 2023

Aha. The issue is with a handled exception:

def raise_something():
  raise TypeError()


def test_something():
  try:
    raise_something()
  except TypeError:
    try:
      raise ValueError()
    except ValueError:
      breakpoint()

In this example, after the breakpoint is reached, there's no longer a handle to the ValueError:

--Return--
> /Users/jaraco/draft/test_something.py(12)test_something()->None
-> breakpoint()
(Pdb) import sys
(Pdb) sys.last_value
*** AttributeError: module 'sys' has no attribute 'last_value'
(Pdb) sys.exc_info()
(<class 'AttributeError'>, AttributeError("'PytestPdbWrapper' object has no attribute 'do_sys'"), <traceback object at 0x103b2d7c0>)

I even tried changing the catch to except ValueError as exc:, but even then, exc appears not to be in the namespace:

--Return--
> /Users/jaraco/draft/test_something.py(12)test_something()->None
-> breakpoint()
(Pdb) exc.__context__
*** NameError: name 'exc' is not defined
(Pdb) l
  7         raise_something()
  8       except TypeError:
  9         try:
 10           raise ValueError()
 11         except ValueError as exc:
 12  ->       breakpoint()
[EOF]

Interestingly, the issue seems to be that if I'm handling the exception, I need a statement after the breakpoint in order for that block to still be active. Changing the code to the following allows the breakpoint to expose exc, from which __context__ can be used:

def raise_something():
  raise TypeError()


def test_something():
  try:
    raise_something()
  except TypeError:
    try:
      raise ValueError()
    except ValueError as exc:
      breakpoint()
      pass

Still, it would be nice if sys.exc_info() contained the exception context from when the exception occurred and not the AttributeError for PytestPdbWrapper.

@jaraco
Copy link
Contributor Author

jaraco commented Nov 5, 2023

I even tried changing the catch to except ValueError as exc:, but even then, exc appears not to be in the namespace

That behavior appears to be a known issue (python/cpython/#111744).

@jaraco jaraco changed the title Retain execution context under unhandled exception Retain execution context under handled exception Nov 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: proposal proposal for a new feature, often to gather opinions or design the API around the new feature
Projects
None yet
Development

No branches or pull requests

2 participants