Skip to content

Commit

Permalink
Explicit IPython handling for exceptions
Browse files Browse the repository at this point in the history
This is so that we always use the original excepthook in custom excepthook calls
instead of using the excepthook possibly set in other cells.
The reason this is important is if we create a Puzzle in one cell, but don't show it for some reason, and then create a new Puzzle in a different cell, the old excepthook would belong to the old Puzzle, and since we still call that, two error messages appear.
  • Loading branch information
jdranczewski committed Jan 31, 2024
1 parent c105eab commit cb8239a
Showing 1 changed file with 32 additions and 20 deletions.
52 changes: 32 additions & 20 deletions puzzlepiece/puzzle.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,39 @@ def __init__(self, app, name, debug=True, *args, **kwargs):

self.wrapper_layout.addLayout(self._button_layout(), 1, 0)

# This hack allows us to handle exceptions through the Puzzle in IPython.
# Normally when a cell is executed in an IPython InteractiveShell,
# sys.excepthook is overwritten with shell.excepthook, and then restored
# to sys.excepthook after the cell run finishes. Any changes we make to
# sys.excepthook in here directly will thus be overwritten as soon as the
# cell that defines the Puzzle finishes running.

# Instead, we schedule set_excepthook on a QTimer, meaning that it will
# execute in the Qt loop rather than in a cell, so it can modify
# sys.excepthook without risk of the changes being immediately overwritten,

# For bonus points, we could set _old_excepthook to shell.excepthook,
# which would result in all tracebacks appearing in the Notebook rather
# than the console, but I think that is not desireable.

# In normal Python, we could just say "sys.excepthook = self._excepthook"
# but this method works for both.
def set_excepthook():
self._old_excepthook = sys.excepthook
try:
# If this doesn't raise a NameError, we're in IPython
shell = get_ipython()
# _orig_sys_module_state stores the original IPKernelApp excepthook,
# irrespective of possible modifications in other cells
self._old_excepthook = shell._orig_sys_module_state['excepthook']

# The following hack allows us to handle exceptions through the Puzzle in IPython.
# Normally when a cell is executed in an IPython InteractiveShell,
# sys.excepthook is overwritten with shell.excepthook, and then restored
# to sys.excepthook after the cell run finishes. Any changes we make to
# sys.excepthook in here directly will thus be overwritten as soon as the
# cell that defines the Puzzle finishes running.

# Instead, we schedule set_excepthook on a QTimer, meaning that it will
# execute in the Qt loop rather than in a cell, so it can modify
# sys.excepthook without risk of the changes being immediately overwritten,

# For bonus points, we could set _old_excepthook to shell.excepthook,
# which would result in all tracebacks appearing in the Notebook rather
# than the console, but I think that is not desireable.
def set_excepthook():
sys.excepthook = self._excepthook
QtCore.QTimer.singleShot(0, set_excepthook)
except NameError:
# In normal Python (not IPython) this is comparatively easy.
# We use the original system hook here instead of sys.excepthook
# to avoid unexpected behaviour if multiple things try to override
# the hook in various ways.
# If you need to implement custom exception handling, please assign
# a value to your Puzzle's ``custom_excepthook`` method.
self._old_excepthook = sys.__excepthook__
sys.excepthook = self._excepthook
QtCore.QTimer.singleShot(0, set_excepthook)

@property
def pieces(self):
Expand Down

0 comments on commit cb8239a

Please sign in to comment.