Skip to content

Commit dfb18f5

Browse files
committed
implement Giovanni's improvement for the traceback massages
1 parent 6e0cd73 commit dfb18f5

File tree

4 files changed

+170
-80
lines changed

4 files changed

+170
-80
lines changed

examples/introduction.ipynb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"metadata": {},
1414
"outputs": [],
1515
"source": [
16-
"from widget_code_input import WidgetCodeInput "
16+
"from widget_code_input import WidgetCodeInput"
1717
]
1818
},
1919
{
@@ -24,11 +24,11 @@
2424
"source": [
2525
"w = WidgetCodeInput(\n",
2626
" function_name = \"my_function\",\n",
27-
" function_parameters = \"\",\n",
27+
" function_parameters = \"a, b\",\n",
2828
" docstring=\"\"\"\n",
2929
" Input the docstring here.\n",
3030
" \"\"\",\n",
31-
" function_body=\"# Give information for the function\\n\",\n",
31+
" function_body=\"# Give information for the function\\nreturn a+b\",\n",
3232
" code_theme = 'nord'\n",
3333
")"
3434
]
@@ -57,7 +57,7 @@
5757
"metadata": {},
5858
"outputs": [],
5959
"source": [
60-
"my_function()"
60+
"my_function(1, 2)"
6161
]
6262
},
6363
{
@@ -84,7 +84,7 @@
8484
"name": "python",
8585
"nbconvert_exporter": "python",
8686
"pygments_lexer": "ipython3",
87-
"version": "3.8.8"
87+
"version": "3.9.12"
8888
}
8989
},
9090
"nbformat": 4,
Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,43 @@
11
#!/usr/bin/env python
22
# coding: utf-8
33

4-
# Copyright (c) Dou Du.
4+
# Copyright (c) Giovanni Pizzi and Dou Du.
55
# Distributed under the terms of the Modified BSD License.
66

7-
import pytest
7+
from widget_code_input import WidgetCodeInput
88

9-
from ..example import ExampleWidget
109

10+
def test_example_creation():
11+
w = WidgetCodeInput(
12+
function_name="my_f",
13+
function_parameters="a, b=1",
14+
docstring="some\nmultiline",
15+
function_body="c = a+b\nreturn c",
16+
)
1117

12-
def test_example_creation_blank():
13-
w = ExampleWidget()
14-
assert w.value == 'Hello World'
18+
assert w.function_body == "c = a+b\nreturn c"
19+
20+
expected_body = f'''def my_f(a, b=1):
21+
"""some
22+
multiline"""
23+
c = a+b
24+
return c
25+
'''
26+
assert w.full_function_code == expected_body
27+
28+
29+
def test_example_running():
30+
function_name = "my_f"
31+
function_parameters = "a, b=1"
32+
docstring = "some\nmultiline"
33+
function_body = "c = a+b\nreturn c"
34+
35+
w = WidgetCodeInput(
36+
function_name=function_name,
37+
function_parameters=function_parameters,
38+
docstring=docstring,
39+
function_body=function_body,
40+
)
41+
42+
f = w.get_function_object()
43+
assert f(1, 2) == 3

widget_code_input/utils.py

Lines changed: 67 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,43 @@
11
import ast
2-
#from xml.sax.saxutils import escape
2+
import sys
3+
import traceback
4+
5+
6+
class CodeValidationError(Exception):
7+
"""Class raised when there is an exception within a WidgetCodeInput."""
8+
9+
def __init__(self, msg, orig_exc):
10+
super().__init__(msg)
11+
self.orig_exc = orig_exc
12+
313

414
def is_valid_variable_name(name):
515
"""
6-
Check if the value specified is a valid variable name (e.g. to
16+
Check if the value specified is a valid variable name (e.g. to
717
validate the function name)
818
919
:return: True if the name is valid, False otherwise
1020
"""
1121
try:
12-
ast.parse('{} = None'.format(name))
22+
ast.parse("{} = None".format(name))
1323
return True
1424
except (SyntaxError, ValueError, TypeError):
1525
return False
1626

27+
1728
def prepend_indent(string, indent_level):
1829
"""
1930
Add a given indent before every line of the string
2031
2132
:string: string to which we want to append the indent
2233
:indent_level: integer number of spaces to prepend
2334
"""
24-
indented = "".join("{}{}\n".format(" "*indent_level, line) for line in string.splitlines())
35+
indented = "".join(
36+
"{}{}\n".format(" " * indent_level, line) for line in string.splitlines()
37+
)
2538
return indented
26-
39+
40+
2741
def build_pre_body(signature, docstring, indent_level=4):
2842
"""
2943
Prepare the part of the function before the function body
@@ -35,12 +49,13 @@ def build_pre_body(signature, docstring, indent_level=4):
3549
"""
3650
if '"""' in docstring:
3751
raise ValueError('Triple double quotes (""") not allowed in docstring')
38-
52+
3953
return "{}\n{}".format(
40-
signature,
54+
signature,
4155
prepend_indent('"""{}"""'.format(docstring), indent_level=indent_level),
4256
)
43-
57+
58+
4459
def build_function(signature, docstring, body, indent_level=4):
4560
"""
4661
Return the full function content
@@ -54,9 +69,10 @@ def build_function(signature, docstring, body, indent_level=4):
5469
# adds already a \n at the end of each line
5570
return "{}{}".format(
5671
build_pre_body(signature, docstring, indent_level=indent_level),
57-
prepend_indent(body, indent_level=indent_level)
72+
prepend_indent(body, indent_level=indent_level),
5873
)
5974

75+
6076
def build_signature(function_name, function_parameters):
6177
"""
6278
Given the function name and function parameters, returns the signature line
@@ -67,38 +83,45 @@ def build_signature(function_name, function_parameters):
6783
"""
6884
return "def {}({}):".format(function_name, function_parameters)
6985

70-
# def build_function_signature(function_name, args, defaults=None, varargs=None, keywords=None):
71-
# """
72-
# For the following function:
73-
74-
# def fn(a, b, c=1, d="a", *args):
75-
# pass
76-
77-
# we have:
78-
79-
# - function_name = "fn"
80-
# - args = ['a', 'b', 'c', 'd']
81-
# - defaults = [1, 'a']
82-
# - varargs = 'args'
83-
# - keywords = None
84-
85-
# For now, defaults are not implemented (they require e.g. to convert a string to its python representation with quotes)
86-
# """
87-
# assert is_valid_variable_name(function_name)
88-
# for arg in args:
89-
# assert is_valid_variable_name(function_name)
90-
# if varargs is not None:
91-
# assert is_valid_variable_name(varargs)
92-
# if keywords is not None:
93-
# assert is_valid_variable_name(keywords)
94-
95-
# mangled_args = [arg for arg in args] # here one could put the logic for defaults as well
96-
# if varargs is not None:
97-
# mangled_args.append('*{}'.format(varargs))
98-
# if keywords is not None:
99-
# mangled_args.append('*{}'.format(keywords))
100-
101-
# args_string = ", ".join(args)
102-
103-
# signature="def {}({}):".format(function_name, args_string)
104-
# return signature
86+
87+
def format_syntax_error_msg(exc):
88+
"""
89+
Return a string reproducing the output of a SyntaxError.
90+
91+
:param exc: The exception that is being processed.
92+
"""
93+
se_args = exc.args[1]
94+
return f"""SyntaxError in code input: {exc.args[0]}
95+
File "{se_args[0]}", line {se_args[1]}
96+
{se_args[3]}{' ' * max(0, se_args[2] - 1)}^
97+
"""
98+
99+
100+
def format_generic_error_msg(exc, code_widget):
101+
"""
102+
Return a string reproducing the traceback of a typical error.
103+
This includes line numbers, as well as neighboring lines.
104+
105+
It will require also the code_widget instance, to get the actual source code.
106+
107+
:note: this must be called from withou the exception, as it will get the current traceback state.
108+
109+
:param exc: The exception that is being processed.
110+
:param code_widget: the instance of the code widget with the code that raised the exception.
111+
"""
112+
error_class, _, tb = sys.exc_info()
113+
line_number = traceback.extract_tb(tb)[-1][1]
114+
code_lines = code_widget.full_function_code.splitlines()
115+
116+
err_msg = f"{error_class.__name__} in code input: {str(exc)}\n"
117+
if line_number > 2:
118+
err_msg += f" {line_number - 2:4d} {code_lines[line_number - 3]}\n"
119+
if line_number > 1:
120+
err_msg += f" {line_number - 1:4d} {code_lines[line_number - 2]}\n"
121+
err_msg += f"---> {line_number:4d} {code_lines[line_number - 1]}\n"
122+
if line_number < len(code_lines):
123+
err_msg += f" {line_number + 1:4d} {code_lines[line_number]}\n"
124+
if line_number < len(code_lines) - 1:
125+
err_msg += f" {line_number + 2:4d} {code_lines[line_number + 1]}\n"
126+
127+
return err_msg

0 commit comments

Comments
 (0)