##// END OF EJS Templates
Swallow potential exceptions from showtraceback() (#13934)...
Swallow potential exceptions from showtraceback() (#13934) The nbgrader project is aware of a form of cheating where students disrupt `InteractiveShell.showtraceback` in hopes of hiding exceptions to avoid losing points. They have implemented a solution to prevent this cheating from working on the client side, and have some tests to demonstrate this technique: https://github.com/jupyter/nbgrader/blob/main/nbgrader/tests/apps/files/submitted-cheat-attempt.ipynb https://github.com/jupyter/nbgrader/blob/main/nbgrader/tests/apps/files/submitted-cheat-attempt-alternative.ipynb In essence, these attacks import the interactive shell and erase the traceback handler so that their failing tests won't report failures. ```python import IPython.core.interactiveshell IPython.core.interactiveshell.InteractiveShell.showtraceback = None ``` The problem is that this causes an exception inside the kernel, leading to a stalled execution. The kernel has stopped working, but the client continues to wait for messages. So far, nbgrader's solution to this is to require a timeout value so the client can eventually decide it is done. This prevents allowing a value of `None` for `Execute.timeout` because this would cause a test case to infinitely hang. This commit addresses the problem by making `InteractiveShell._run_cell` a little more protective around it's call to `showtraceback()`. There is already a try/except block around running the cell. This commit adds a finally clause so that the method will _always_ return an `ExecutionResult`, even if a new exception is thrown within the except clause. For the record, the exception thrown is: TypeError: 'NoneType' object is not callable Accepting this change will allow nbgrader to update `nbgrader.preprocessors.Execute` to support a type of `Integer(allow_none=True)` as the parent `NotebookClient` intended. Discussion about this is ongoing in jupyter/nbgrader#1690.

File last commit:

r24579:ce22f499
r28101:e548ee23 merge
Show More
magics.py
46 lines | 1.5 KiB | text/x-python | PythonLexer
import re
from sphinx import addnodes
from sphinx.domains.std import StandardDomain
from sphinx.roles import XRefRole
name_re = re.compile(r"[\w_]+")
def parse_magic(env, sig, signode):
m = name_re.match(sig)
if not m:
raise Exception("Invalid magic command: %s" % sig)
name = "%" + sig
signode += addnodes.desc_name(name, name)
return m.group(0)
class LineMagicRole(XRefRole):
"""Cross reference role displayed with a % prefix"""
prefix = "%"
def process_link(self, env, refnode, has_explicit_title, title, target):
if not has_explicit_title:
title = self.prefix + title.lstrip("%")
target = target.lstrip("%")
return title, target
def parse_cell_magic(env, sig, signode):
m = name_re.match(sig)
if not m:
raise ValueError("Invalid cell magic: %s" % sig)
name = "%%" + sig
signode += addnodes.desc_name(name, name)
return m.group(0)
class CellMagicRole(LineMagicRole):
"""Cross reference role displayed with a %% prefix"""
prefix = "%%"
def setup(app):
app.add_object_type('magic', 'magic', 'pair: %s; magic command', parse_magic)
app.add_role_to_domain('std', 'magic', LineMagicRole(), override=True)
app.add_object_type('cellmagic', 'cellmagic', 'pair: %s; cell magic', parse_cell_magic)
app.add_role_to_domain('std', 'cellmagic', CellMagicRole(), override=True)
metadata = {'parallel_read_safe': True, 'parallel_write_safe': True}
return metadata