Skip to content

Commit 7775d93

Browse files
miss-islingtonambv
andauthored
[3.14] gh-130999: Avoid exiting the new REPL when there are non-string candidates for suggestions (gh-131001) (gh-135019)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent 0947773 commit 7775d93

File tree

4 files changed

+51
-4
lines changed

4 files changed

+51
-4
lines changed

Lib/test/test_pyrepl/test_pyrepl.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,6 +1672,17 @@ def test_null_byte(self):
16721672
self.assertEqual(exit_code, 0)
16731673
self.assertNotIn("TypeError", output)
16741674

1675+
@force_not_colorized
1676+
def test_non_string_suggestion_candidates(self):
1677+
commands = ("import runpy\n"
1678+
"runpy._run_module_code('blech', {0: '', 'bluch': ''}, '')\n"
1679+
"exit()\n")
1680+
1681+
output, exit_code = self.run_repl(commands)
1682+
self.assertEqual(exit_code, 0)
1683+
self.assertNotIn("all elements in 'candidates' must be strings", output)
1684+
self.assertIn("bluch", output)
1685+
16751686
def test_readline_history_file(self):
16761687
# skip, if readline module is not available
16771688
readline = import_module('readline')

Lib/test/test_traceback.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4188,6 +4188,15 @@ def __dir__(self):
41884188
self.assertNotIn("blech", actual)
41894189
self.assertNotIn("oh no!", actual)
41904190

4191+
def test_attribute_error_with_non_string_candidates(self):
4192+
class T:
4193+
bluch = 1
4194+
4195+
instance = T()
4196+
instance.__dict__[0] = 1
4197+
actual = self.get_suggestion(instance, 'blich')
4198+
self.assertIn("bluch", actual)
4199+
41914200
def test_attribute_error_with_bad_name(self):
41924201
def raise_attribute_error_with_bad_name():
41934202
raise AttributeError(name=12, obj=23)
@@ -4223,8 +4232,8 @@ def make_module(self, code):
42234232

42244233
return mod_name
42254234

4226-
def get_import_from_suggestion(self, mod_dict, name):
4227-
modname = self.make_module(mod_dict)
4235+
def get_import_from_suggestion(self, code, name):
4236+
modname = self.make_module(code)
42284237

42294238
def callable():
42304239
try:
@@ -4301,6 +4310,13 @@ def test_import_from_suggestions_underscored(self):
43014310
self.assertIn("'_bluch'", self.get_import_from_suggestion(code, '_luch'))
43024311
self.assertNotIn("'_bluch'", self.get_import_from_suggestion(code, 'bluch'))
43034312

4313+
def test_import_from_suggestions_non_string(self):
4314+
modWithNonStringAttr = textwrap.dedent("""\
4315+
globals()[0] = 1
4316+
bluch = 1
4317+
""")
4318+
self.assertIn("'bluch'", self.get_import_from_suggestion(modWithNonStringAttr, 'blech'))
4319+
43044320
def test_import_from_suggestions_do_not_trigger_for_long_attributes(self):
43054321
code = "blech = None"
43064322

@@ -4397,6 +4413,15 @@ def func():
43974413
actual = self.get_suggestion(func)
43984414
self.assertIn("'ZeroDivisionError'?", actual)
43994415

4416+
def test_name_error_suggestions_with_non_string_candidates(self):
4417+
def func():
4418+
abc = 1
4419+
custom_globals = globals().copy()
4420+
custom_globals[0] = 1
4421+
print(eval("abv", custom_globals, locals()))
4422+
actual = self.get_suggestion(func)
4423+
self.assertIn("abc", actual)
4424+
44004425
def test_name_error_suggestions_do_not_trigger_for_long_names(self):
44014426
def func():
44024427
somethingverywronghehehehehehe = None

Lib/traceback.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1595,7 +1595,11 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
15951595
if isinstance(exc_value, AttributeError):
15961596
obj = exc_value.obj
15971597
try:
1598-
d = dir(obj)
1598+
try:
1599+
d = dir(obj)
1600+
except TypeError: # Attributes are unsortable, e.g. int and str
1601+
d = list(obj.__class__.__dict__.keys()) + list(obj.__dict__.keys())
1602+
d = sorted([x for x in d if isinstance(x, str)])
15991603
hide_underscored = (wrong_name[:1] != '_')
16001604
if hide_underscored and tb is not None:
16011605
while tb.tb_next is not None:
@@ -1610,7 +1614,11 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
16101614
elif isinstance(exc_value, ImportError):
16111615
try:
16121616
mod = __import__(exc_value.name)
1613-
d = dir(mod)
1617+
try:
1618+
d = dir(mod)
1619+
except TypeError: # Attributes are unsortable, e.g. int and str
1620+
d = list(mod.__dict__.keys())
1621+
d = sorted([x for x in d if isinstance(x, str)])
16141622
if wrong_name[:1] != '_':
16151623
d = [x for x in d if x[:1] != '_']
16161624
except Exception:
@@ -1628,6 +1636,7 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
16281636
+ list(frame.f_globals)
16291637
+ list(frame.f_builtins)
16301638
)
1639+
d = [x for x in d if isinstance(x, str)]
16311640

16321641
# Check first if we are in a method and the instance
16331642
# has the wrong name as attribute
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Avoid exiting the new REPL and offer suggestions even if there are non-string
2+
candidates when errors occur.

0 commit comments

Comments
 (0)