Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
52 changes: 52 additions & 0 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -3802,6 +3802,50 @@ def test_traceback_header(self):
exc = traceback.TracebackException(Exception, Exception("haven"), None)
self.assertEqual(list(exc.format()), ["Exception: haven\n"])

def test_name_error_punctuation_with_suggestions(self):
def format_error(message, name):
return self.format_error(NameError, message, name=name)

test_cases = [
("a.", "time", "NameError: a. Did you forget to import 'time'?\n"),
("b?", "time", "NameError: b? Did you forget to import 'time'?\n"),
("c!", "time", "NameError: c! Did you forget to import 'time'?\n"),
("d", "time", "NameError: d. Did you forget to import 'time'?\n"),
("e", "foo123", "NameError: e\n"),
]
for message, name, expected in test_cases:
with self.subTest(message=message):
message = format_error(message, name)
self.assertEqual(message, expected)

with self.subTest("stdlib module import suggestion"):
message = format_error("foo", "abc")
expected_message = (
"NameError: foo. Did you mean: 'abs'? "
"Or did you forget to import 'abc'?\n"
)
self.assertEqual(message, expected_message)

with self.subTest("'did you mean' suggestion"):
message = format_error("bar", "flaot")
expected_message = "NameError: bar. Did you mean: 'float'?\n"
self.assertEqual(message, expected_message)

def test_import_error_punctuation_handling_with_suggestions(self):
def format_error(message):
return self.format_error(ImportError, message, name="math", name_from="sinq")

test_cases = [
("a.", "ImportError: a. Did you mean: 'sin'?\n"),
("b?", "ImportError: b? Did you mean: 'sin'?\n"),
("c!", "ImportError: c! Did you mean: 'sin'?\n"),
("d", "ImportError: d. Did you mean: 'sin'?\n"),
]
for message, expected in test_cases:
with self.subTest(message=message):
message = format_error(message)
self.assertEqual(message, expected)

@requires_debug_ranges()
def test_print(self):
def f():
Expand Down Expand Up @@ -3839,6 +3883,14 @@ def test_dont_swallow_cause_or_context_of_falsey_exception(self):
except FalseyException as e:
self.assertIn(context_message, traceback.format_exception(e))

@staticmethod
def format_error(exc_type, exc_message, *args, **kwargs):
try:
raise exc_type(exc_message, *args, **kwargs)
except exc_type as e:
exc = traceback.TracebackException.from_exception(e)
return list(exc.format())[-1]


class TestTracebackException_ExceptionGroups(unittest.TestCase):
def setUp(self):
Expand Down
91 changes: 53 additions & 38 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -1081,50 +1081,33 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,

self._is_syntax_error = False
self._have_exc_type = exc_type is not None
if exc_type is not None:

if self._have_exc_type:
self.exc_type_qualname = exc_type.__qualname__
self.exc_type_module = exc_type.__module__
if issubclass(exc_type, SyntaxError):
# Handle SyntaxErrors specially
self.filename = exc_value.filename
lno = exc_value.lineno
self.lineno = str(lno) if lno is not None else None
end_lno = exc_value.end_lineno
self.end_lineno = str(end_lno) if end_lno is not None else None
self.text = exc_value.text
self.offset = exc_value.offset
self.end_offset = exc_value.end_offset
self.msg = exc_value.msg
self._is_syntax_error = True
self._exc_metadata = getattr(exc_value, "_metadata", None)
elif suggestion := _suggestion_message(exc_type, exc_value, exc_traceback):
if self._str.endswith(('.', '?', '!')):
punctuation = ''
else:
punctuation = '.'
self._str += f"{punctuation} {suggestion}"
else:
self.exc_type_qualname = None
self.exc_type_module = None

if exc_type and issubclass(exc_type, SyntaxError):
# Handle SyntaxError's specially
self.filename = exc_value.filename
lno = exc_value.lineno
self.lineno = str(lno) if lno is not None else None
end_lno = exc_value.end_lineno
self.end_lineno = str(end_lno) if end_lno is not None else None
self.text = exc_value.text
self.offset = exc_value.offset
self.end_offset = exc_value.end_offset
self.msg = exc_value.msg
self._is_syntax_error = True
self._exc_metadata = getattr(exc_value, "_metadata", None)
elif exc_type and issubclass(exc_type, ImportError) and \
getattr(exc_value, "name_from", None) is not None:
wrong_name = getattr(exc_value, "name_from", None)
suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
if suggestion:
self._str += f". Did you mean: '{suggestion}'?"
elif exc_type and issubclass(exc_type, ModuleNotFoundError) and \
sys.flags.no_site and \
getattr(exc_value, "name", None) not in sys.stdlib_module_names:
self._str += (". Site initialization is disabled, did you forget to "
+ "add the site-packages directory to sys.path?")
elif exc_type and issubclass(exc_type, (NameError, AttributeError)) and \
getattr(exc_value, "name", None) is not None:
wrong_name = getattr(exc_value, "name", None)
suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
if suggestion:
self._str += f". Did you mean: '{suggestion}'?"
if issubclass(exc_type, NameError):
wrong_name = getattr(exc_value, "name", None)
if wrong_name is not None and wrong_name in sys.stdlib_module_names:
if suggestion:
self._str += f" Or did you forget to import '{wrong_name}'?"
else:
self._str += f". Did you forget to import '{wrong_name}'?"
if lookup_lines:
self._load_lines()
self.__suppress_context__ = \
Expand Down Expand Up @@ -1732,6 +1715,38 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
return suggestion


def _suggestion_message(exc_type, exc_value, exc_traceback):
if (
issubclass(exc_type, ModuleNotFoundError)
and sys.flags.no_site
and getattr(exc_value, "name", None) not in sys.stdlib_module_names
):
return ("Site initialization is disabled, did you forget to "
"add the site-packages directory to sys.path?")
if issubclass(exc_type, (ImportError, NameError, AttributeError)):
if issubclass(exc_type, ImportError):
wrong_name = getattr(exc_value, "name_from", None)
else:
wrong_name = getattr(exc_value, "name", None)
if wrong_name:
other_name = _compute_suggestion_error(
exc_value, exc_traceback, wrong_name
)
maybe_stdlib_import = (
issubclass(exc_type, NameError)
and wrong_name in sys.stdlib_module_names
)
if not other_name:
if maybe_stdlib_import:
return f"Did you forget to import '{wrong_name}'?"
return None
text = f"Did you mean: '{other_name}'?"
if maybe_stdlib_import:
return f"{text} Or did you forget to import '{wrong_name}'?"
return text
return None


def _levenshtein_distance(a, b, max_cost):
# A Python implementation of Python/suggestions.c:levenshtein_distance.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Avoid double punctuation in :class:`~traceback.TracebackException` messages
Loading