How to call `InteractiveShellEmbed`-based custom excepthook via `jupyter-console --existing`

17 Views Asked by At

Main question: How can I run an embedded IPython shell with a full-featured prompt (simple_prompt=False) in the namespace of a frame where an exception occurred in the context of a running IPython kernel of a Jupyter notebook?

Explanation:

I have the following usecase (which is almost solved, up to one major flaw):

In Jupyter notebook (python kernel) I run some nested function call failing_function(). Deep down in the call stack an exception happens. I want to debug the situation on the commandline with my custom excepthook (ipydex.ips_excepthook; it opens an emebedded IPython shell right in the frame where the exception occurs and offers the possibility to move up in the frame stack, which I find more useful than the classical IPython debugger).

I can achieve this with the following steps: Within an ordinary python script:

import sys
from jupyter_console.app import ZMQTerminalIPythonApp

# mimic jupyter-console --existing: attach shell to running kernel
sys.argv = [sys.argv[0], "--existing"]
app = ZMQTerminalIPythonApp.instance()
app.initialize(None)
super(ZMQTerminalIPythonApp, app).start()

code = """
import ipydex
import traceback
try:
    failing_function()
except Exception as ex:
    value, tb = traceback._parse_value_tb(ex, traceback._sentinel, traceback._sentinel)
    ipydex.ips_excepthook(type(ex), ex, tb)
"""

# run this code in the context of the running kernel
app.shell.run_cell(code)

Problem:

This works, but with one flaw: It only offers the simple_prompt-mode of the embedded IPython shell. Background: My custom excepthook internally creates an instance of from IPython.terminal.embed.InteractiveShellEmbed and calls its mainloop (as intended). This mainloop has two different prompt-modes: default and simple. The default mode has many desired features (colored output, TAB-completion, ...), which I desire but the simple mode does not offer. The mode is determined by this line IPython/terminal/interactiveshell.py#L134.

I tried to force the class variable InteractiveShellEmbed.simple_prompt to False but then I get RuntimeError: This event loop is already running (would have been too easy).

I see three possible ways to achieve the desired behavior:

  • a) Let InteractiveShellEmbed run in default (i.e. not simple) mode inside the running ZMQTerminalIPythonApp.
  • b) Run ZMQTerminalIPythonApp.shell.mainloop() directly in the namespace of the frame where the exception occurs (and offer the possibility to go up and down again in the frame stack).
  • c) Temporarily overwrite sys.excepthook with my custom ipydex.ips_excepthook and just execute failing_function() without try ... except.

For a): The whole story with the custom excepthook is just my motivation. The problem boils down to achieving that app.shell.run_cell('IPython.embed(colors="neutral")') runs in default (not simple) prompt mode. For b): I dont know whether that is possible because InteractiveShellEmbed.mainloop accepts key word args local_ns and module (global name space), whereas ZMQTerminalIPythonApp.shell.mainloop does not. For c): This works perfectly for normal scripts but not when "injecting" code in a running jupyter kernel, because overwriting sys.excepthook does not seem to have an effect.

Derived questions:

  1. Which of the directions (a, b, c) is the most promising?
  2. What would be a good next step?
  3. What other possibilities do I have?
0

There are 0 best solutions below