8000 Add no_traceback argument to :okexcept: option of ipython sphinx dire… · ipython/ipython@c9e46c5 · GitHub
[go: up one dir, main page]

Skip to content

Commit c9e46c5

Browse files
committed
Add no_traceback argument to :okexcept: option of ipython sphinx directive
- add optional no_traceback argument to :ok_except: which prevents the (sometimes long) traceback from being printed, default is no argument, i.e. traceback is printed - enable @okexcept pseudodecorator so that it can be applied to individual instructions instead of the whole block - adjust IPythonConsoleLexer so that it formats exceptions without preceding traceback as usual, add tests for this - apply darker to this commit
1 parent ae0dfcd commit c9e46c5

File tree

3 files changed

+97
-23
lines changed

3 files changed

+97
-23
lines changed

IPython/lib/lexers.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#-----------------------------------------------------------------------------
3232

3333
# Standard library
34+
import builtins
3435
import re
3536

3637
# Third party
@@ -255,8 +256,19 @@ class IPythonConsoleLexer(Lexer):
255256
in2_regex = r' \.\.+\.: '
256257
out_regex = r'Out\[[0-9]+\]: '
257258

258-
#: The regex to determine when a traceback starts.
259-
ipytb_start = re.compile(r'^(\^C)?(-+\n)|^( File)(.*)(, line )(\d+\n)')
259+
#: The regex to determine when a traceback or exception starts.
260+
exceptions = [
261+
name
262+
for name, value in builtins.__dict__.items()
263+
if isinstance(value, type)
264+
and issubclass(value, Exception)
265+
and not issubclass(value, Warning)
266+
]
267+
ipytb_start = re.compile(
268+
r"^(\^C)?(-+\n)|^( File)(.*)(, line )(\d+\n)|^("
269+
+ "|".join(exceptions)
270+
+ r"): \S.*\n"
271+
)
260272

261273
def __init__(self, **options):
262274
"""Initialize the IPython console lexer.

IPython/lib/tests/test_lexers.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from unittest import TestCase
77
from pygments.token import Token
88
from pygments.lexers import BashLexer
9+
import pytest
910

1011
from .. import lexers
1112

@@ -174,3 +175,42 @@ def testIPythonLexer(self):
174175
(Token.Text, '\n'),
175176
]
176177
self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
178+
179+
180+
@pytest.mark.parametrize(
181+
"fragment,expected",
182+
[
183+
(
184+
"---\nZeroDivisionError: division by zero\n",
185+
[
186+
(Token.Generic.Output, ""),
187+
(Token.Generic.Traceback, "---\n"),
188+
(Token.Name.Exception, "ZeroDivisionError"),
189+
(Token.Text, ": division by zero\n"),
190+
],
191+
),
192+
(
193+
' File "x.py", line 1\nSyntaxError: xxx',
194+
[
195+
(Token.Generic.Output, ""),
196+
(Token.Generic.Traceback, " File"),
197+
(Token.Name.Namespace, ' "x.py"'),
198+
(Token.Generic.Traceback, ", line "),
199+
(Token.Literal.Number.Integer, "1\n"),
200+
(Token.Name.Exception, "SyntaxError"),
201+
(Token.Text, ": xxx\n"),
202+
],
203+
),
204+
(
205+
"ZeroDivisionError: division by zero",
206+
[
207+
(Token.Generic.Output, ""),
208+
(Token.Name.Exception, "ZeroDivisionError"),
209+
(Token.Text, ": division by zero\n"),
210+
],
211+
),
212+
],
213+
ids=["exception traceback", "syntax error traceback", "no traceback"],
214+
)
215+
def test_IPythonConsoleLexer(fragment, expected):
216+
assert expected == list(lexers.IPythonConsoleLexer().get_tokens(fragment))

IPython/sphinxext/ipython_directive.py

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@
220220
# for tokenizing blocks
221221
COMMENT, INPUT, OUTPUT = range(3)
222222

223-
PSEUDO_DECORATORS = ["suppress", "verbatim", "savefig", "doctest"]
223+
PSEUDO_DECORATORS = ["suppress", "verbatim", "savefig", "doctest", "okexcept"]
224224

225225
#-----------------------------------------------------------------------------
226226
# Functions and class declarations
@@ -443,6 +443,16 @@ def process_image(self, decorator):
443443
image_directive = '\n'.join(imagerows)
444444
return image_file, image_directive
445445

446+
@staticmethod
447+
def no_traceback_handler(shell, etype, value, tb, tb_offset=None):
448+
"""
449+
Exception handler that shows only the exception without the traceback.
450+
"""
451+
shell.InteractiveTB.color_toggle()
452+
stb = shell.InteractiveTB.get_exception_only(etype, value)
453+
print(shell.InteractiveTB.stb2text(stb))
454+
return stb
455+
446456
# Callbacks for each type of token
447457
def process_input(self, data, input_prompt, lineno):
448458
"""
@@ -453,14 +463,20 @@ def process_input(self, data, input_prompt, lineno):
453463
image_file = None
454464
image_directive = None
455465

456-
is_verbatim = decorator=='@verbatim' or self.is_verbatim
457-
is_doctest = (decorator is not None and \
458-
decorator.startswith('@doctest')) or self.is_doctest
459-
is_suppress = decorator=='@suppress' or self.is_suppress
460-
is_okexcept = decorator=='@okexcept' or self.is_okexcept
461-
is_okwarning = decorator=='@okwarning' or self.is_okwarning
462-
is_savefig = decorator is not None and \
463-
decorator.startswith('@savefig')
466+
is_verbatim = decorator == "@verbatim" or self.is_verbatim
467+
is_doctest = (
468+
decorator is not None and decorator.startswith("@doctest")
469+
) or self.is_doctest
470+
is_suppress = decorator == "@suppress" or self.is_suppress
471+
is_okexcept = (
472+
decorator is not None and decorator.startswith("@okexcept")
473+
) or self.is_okexcept
474+
no_traceback = (
475+
decorator is not None
476+
and decorator.partition(" ")[2].startswith("no_traceback")
477+
) or self.no_traceback
478+
is_okwarning = decorator == "@okwarning" or self.is_okwarning
479+
is_savefig = decorator is not None and decorator.startswith("@savefig")
464480

465481
input_lines = input.split('\n')
466482
if len(input_lines) > 1:
@@ -494,7 +510,11 @@ def process_input(self, data, input_prompt, lineno):
494510
self.IP.execution_count += 1 # increment it anyway
495511
else:
496512
# only submit the line in non-verbatim mode
513+
if no_traceback:
514+
self.IP.set_custom_exc((BaseException,), self.no_traceback_handler)
497515
self.process_input_lines(input_lines, store_history=store_history)
516+
if no_traceback:
517+
self.IP.set_custom_exc((), None)
498518

499519
if not is_suppress:
500520
for i, line in enumerate(input_lines):
@@ -901,13 +921,14 @@ class IPythonDirective(Directive):
901921
required_arguments = 0
902922
optional_arguments = 4 # python, suppress, verbatim, doctest
903923
final_argumuent_whitespace = True
904-
option_spec = { 'python': directives.unchanged,
905-
'suppress' : directives.flag,
906-
'verbatim' : directives.flag,
907-
'doctest' : directives.flag,
908-
'okexcept': directives.flag,
909-
'okwarning': directives.flag
910-
}
924+
option_spec = {
925+
"python": directives.unchanged,
926+
"suppress": directives.flag,
927+
"verbatim": directives.flag,
928+
"doctest": directives.flag,
929+
"okexcept": directives.unchanged,
930+
"okwarning": directives.flag,
931+
}
911932

912933
shell = None
913934

@@ -1001,11 +1022,12 @@ def run(self):
10011022
rgxin, rgxout, promptin, promptout = self.setup()
10021023

10031024
options = self.options
1004-
self.shell.is_suppress = 'suppress' in options
1005-
self.shell.is_doctest = 'doctest' in options
1006-
self.shell.is_verbatim = 'verbatim' in options
1007-
self.shell.is_okexcept = 'okexcept' in options
1008-
self.shell.is_okwarning = 'okwarning' in options
1025+
self.shell.is_suppress = "suppress" in options
1026+
self.shell.is_doctest = "doctest" in options
1027+
self.shell.is_verbatim = "verbatim" in options
1028+
self.shell.is_okexcept = "okexcept" in options
1029+
self.shell.is_okwarning = "okwarning" in options
1030+
self.shell.no_traceback = options.get("okexcept") == "no_traceback"
10091031

10101032
# handle pure python code
10111033
if 'python' in self.arguments:

0 commit comments

Comments
 (0)
0