Skip to content

Commit 3164cda

Browse files
committed
Add more tests
1 parent 7511697 commit 3164cda

2 files changed

Lines changed: 56 additions & 5 deletions

File tree

Lib/test/test_traceback.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4343,6 +4343,57 @@ def __init__(self):
43434343
actual = self.get_suggestion(Outer(), 'public_value')
43444344
self.assertNotIn("Did you mean", actual)
43454345

4346+
def test_getattr_nested_limits_attribute_checks(self):
4347+
# Test that nested suggestions are limited to checking first 20 non-private attributes
4348+
class Inner:
4349+
def __init__(self):
4350+
self.target_value = 42
4351+
4352+
class Outer:
4353+
def __init__(self):
4354+
# Add many attributes before 'inner'
4355+
for i in range(25):
4356+
setattr(self, f'attr_{i:02d}', i)
4357+
# Add the inner object after 20+ attributes
4358+
self.inner = Inner()
4359+
4360+
obj = Outer()
4361+
# Verify that 'inner' is indeed present but after position 20
4362+
attrs = [x for x in sorted(dir(obj)) if not x.startswith('_')]
4363+
inner_position = attrs.index('inner')
4364+
self.assertGreater(inner_position, 19, "inner should be after position 20 in sorted attributes")
4365+
4366+
# Should not suggest 'inner.target_value' because inner is beyond the first 20 attributes checked
4367+
actual = self.get_suggestion(obj, 'target_value')
4368+
self.assertNotIn("inner.target_value", actual)
4369+
4370+
def test_getattr_nested_returns_first_match_only(self):
4371+
# Test that only the first nested match is returned (not multiple)
4372+
class Inner1:
4373+
def __init__(self):
4374+
self.value = 1
4375+
4376+
class Inner2:
4377+
def __init__(self):
4378+
self.value = 2
4379+
4380+
class Inner3:
4381+
def __init__(self):
4382+
self.value = 3
4383+
4384+
class Outer:
4385+
def __init__(self):
4386+
# Multiple inner objects with same attribute
4387+
self.a_inner = Inner1()
4388+
self.b_inner = Inner2()
4389+
self.c_inner = Inner3()
4390+
4391+
# Should suggest only the first match (alphabetically)
4392+
actual = self.get_suggestion(Outer(), 'value')
4393+
self.assertIn("'a_inner.value'", actual)
4394+
# Verify it's a single suggestion, not multiple
4395+
self.assertEqual(actual.count("Did you mean"), 1)
4396+
43464397
def make_module(self, code):
43474398
tmpdir = Path(tempfile.mkdtemp())
43484399
self.addCleanup(shutil.rmtree, tmpdir)

Lib/traceback.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,13 +1603,13 @@ def _substitution_cost(ch_a, ch_b):
16031603

16041604
def _check_for_nested_attribute(obj, wrong_name, attrs):
16051605
"""Check if any attribute of obj has the wrong_name as a nested attribute.
1606-
1606+
16071607
Returns the first nested attribute suggestion found, or None.
16081608
Limited to checking 20 attributes and returning up to 5 suggestions.
16091609
"""
16101610
nested_suggestions = []
16111611
max_nested_suggestions = 5
1612-
1612+
16131613
# Check for nested attributes (only one level deep)
16141614
attrs_to_check = [x for x in attrs if not x.startswith('_')][:20] # Limit number of attributes to check
16151615
for attr_name in attrs_to_check:
@@ -1622,7 +1622,7 @@ def _check_for_nested_attribute(obj, wrong_name, attrs):
16221622
break
16231623
except:
16241624
pass
1625-
1625+
16261626
return nested_suggestions[0] if nested_suggestions else None
16271627

16281628

@@ -1718,7 +1718,7 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
17181718
if not suggestion or current_distance < best_distance:
17191719
suggestion = possible_name
17201720
best_distance = current_distance
1721-
1721+
17221722
# If no direct attribute match found, check for nested attributes
17231723
if not suggestion and isinstance(exc_value, AttributeError):
17241724
try:
@@ -1727,7 +1727,7 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
17271727
return nested_suggestion
17281728
except:
17291729
pass
1730-
1730+
17311731
return suggestion
17321732

17331733

0 commit comments

Comments
 (0)