@@ -4394,6 +4394,51 @@ def __init__(self):
43944394 # Verify it's a single suggestion, not multiple
43954395 self .assertEqual (actual .count ("Did you mean" ), 1 )
43964396
4397+ def test_getattr_nested_handles_attribute_access_exceptions (self ):
4398+ # Test that exceptions raised when accessing attributes don't crash the suggestion system
4399+ class ExplodingProperty :
4400+ @property
4401+ def exploding_attr (self ):
4402+ raise RuntimeError ("BOOM! This property always explodes" )
4403+
4404+ def __repr__ (self ):
4405+ raise RuntimeError ("repr also explodes" )
4406+
4407+ class SafeInner :
4408+ def __init__ (self ):
4409+ self .target = 42
4410+
4411+ class Outer :
4412+ def __init__ (self ):
4413+ self .exploder = ExplodingProperty () # Accessing attributes will raise
4414+ self .safe_inner = SafeInner ()
4415+
4416+ # Should still suggest 'safe_inner.target' without crashing
4417+ # even though accessing exploder.target would raise an exception
4418+ actual = self .get_suggestion (Outer (), 'target' )
4419+ self .assertIn ("'safe_inner.target'" , actual )
4420+
4421+ def test_getattr_nested_handles_hasattr_exceptions (self ):
4422+ # Test that exceptions in hasattr don't crash the system
4423+ class WeirdObject :
4424+ def __getattr__ (self , name ):
4425+ if name == 'target' :
4426+ raise RuntimeError ("Can't check for target attribute" )
4427+ raise AttributeError (f"No attribute { name } " )
4428+
4429+ class NormalInner :
4430+ def __init__ (self ):
4431+ self .target = 100
4432+
4433+ class Outer :
4434+ def __init__ (self ):
4435+ self .weird = WeirdObject () # hasattr will raise for 'target'
4436+ self .normal = NormalInner ()
4437+
4438+ # Should still find 'normal.target' even though weird.target check fails
4439+ actual = self .get_suggestion (Outer (), 'target' )
4440+ self .assertIn ("'normal.target'" , actual )
4441+
43974442 def make_module (self , code ):
43984443 tmpdir = Path (tempfile .mkdtemp ())
43994444 self .addCleanup (shutil .rmtree , tmpdir )
0 commit comments