mirror of
https://github.com/python/cpython.git
synced 2026-04-14 07:41:00 +00:00
gh-146171: Fix nested AttributeError suggestions (#146188)
This commit is contained in:
parent
acfb4528de
commit
2f4eb34bd2
3 changed files with 18 additions and 10 deletions
|
|
@ -4420,19 +4420,21 @@ def __init__(self):
|
|||
self.assertNotIn("inner.foo", actual)
|
||||
|
||||
def test_getattr_nested_with_property(self):
|
||||
# Test that descriptors (including properties) are suggested in nested attributes
|
||||
# Property suggestions should not execute the property getter.
|
||||
class Inner:
|
||||
@property
|
||||
def computed(self):
|
||||
return 42
|
||||
return missing_name
|
||||
|
||||
class Outer:
|
||||
def __init__(self):
|
||||
self.inner = Inner()
|
||||
|
||||
actual = self.get_suggestion(Outer(), 'computed')
|
||||
# Descriptors should not be suggested to avoid executing arbitrary code
|
||||
self.assertIn("inner.computed", actual)
|
||||
self.assertIn(
|
||||
"Did you mean '.inner.computed' instead of '.computed'",
|
||||
actual,
|
||||
)
|
||||
|
||||
def test_getattr_nested_no_suggestion_for_deep_nesting(self):
|
||||
# Test that deeply nested attributes (2+ levels) are not suggested
|
||||
|
|
|
|||
|
|
@ -1670,16 +1670,20 @@ def _check_for_nested_attribute(obj, wrong_name, attrs):
|
|||
|
||||
Returns the first nested attribute suggestion found, or None.
|
||||
Limited to checking 20 attributes.
|
||||
Only considers non-descriptor attributes to avoid executing arbitrary code.
|
||||
Only considers non-descriptor outer attributes to avoid executing
|
||||
arbitrary code. Checks nested attributes statically so descriptors such
|
||||
as properties can still be suggested without invoking them.
|
||||
Skips lazy imports to avoid triggering module loading.
|
||||
"""
|
||||
from inspect import getattr_static
|
||||
|
||||
# Check for nested attributes (only one level deep)
|
||||
attrs_to_check = [x for x in attrs if not x.startswith('_')][:20] # Limit number of attributes to check
|
||||
for attr_name in attrs_to_check:
|
||||
with suppress(Exception):
|
||||
# Check if attr_name is a descriptor - if so, skip it
|
||||
attr_from_class = getattr(type(obj), attr_name, None)
|
||||
if attr_from_class is not None and hasattr(attr_from_class, '__get__'):
|
||||
attr_from_class = getattr_static(type(obj), attr_name, _sentinel)
|
||||
if attr_from_class is not _sentinel and hasattr(attr_from_class, '__get__'):
|
||||
continue # Skip descriptors to avoid executing arbitrary code
|
||||
|
||||
# Skip lazy imports to avoid triggering module loading
|
||||
|
|
@ -1689,10 +1693,10 @@ def _check_for_nested_attribute(obj, wrong_name, attrs):
|
|||
# Safe to get the attribute since it's not a descriptor
|
||||
attr_obj = getattr(obj, attr_name)
|
||||
|
||||
# Check if the nested attribute exists and is not a descriptor
|
||||
nested_attr_from_class = getattr(type(attr_obj), wrong_name, None)
|
||||
if _is_lazy_import(attr_obj, wrong_name):
|
||||
continue
|
||||
|
||||
if hasattr(attr_obj, wrong_name):
|
||||
if getattr_static(attr_obj, wrong_name, _sentinel) is not _sentinel:
|
||||
return f"{attr_name}.{wrong_name}"
|
||||
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
Nested :exc:`AttributeError` suggestions now include property-backed
|
||||
attributes on nested objects without executing the property getter.
|
||||
Loading…
Add table
Add a link
Reference in a new issue