gh-137967: Restore suggestions on nested attribute access (#137968)
Some checks failed
Tests / Docs (push) Blocked by required conditions
Tests / Windows MSI (push) Blocked by required conditions
Tests / (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / Ubuntu SSL tests with AWS-LC (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
mypy / Run mypy on Tools/cases_generator (push) Waiting to run
mypy / Run mypy on Lib/_pyrepl (push) Waiting to run
mypy / Run mypy on Lib/test/libregrtest (push) Waiting to run
mypy / Run mypy on Lib/tomllib (push) Waiting to run
Tests / Sanitizers (push) Blocked by required conditions
Tail calling interpreter / aarch64-apple-darwin/clang (push) Waiting to run
Tail calling interpreter / aarch64-unknown-linux-gnu/gcc (push) Waiting to run
Tail calling interpreter / x86_64-pc-windows-msvc/msvc (push) Waiting to run
Tail calling interpreter / x86_64-apple-darwin/clang (push) Waiting to run
Tail calling interpreter / free-threading (push) Waiting to run
Tail calling interpreter / x86_64-unknown-linux-gnu/gcc (push) Waiting to run
mypy / Run mypy on Tools/build (push) Waiting to run
mypy / Run mypy on Tools/clinic (push) Waiting to run
mypy / Run mypy on Tools/jit (push) Waiting to run
mypy / Run mypy on Tools/peg_generator (push) Waiting to run
JIT / Interpreter (Debug) (push) Has been cancelled
JIT / aarch64-pc-windows-msvc/msvc (Release) (push) Has been cancelled
JIT / aarch64-pc-windows-msvc/msvc (Debug) (push) Has been cancelled
JIT / i686-pc-windows-msvc/msvc (Debug) (push) Has been cancelled
JIT / aarch64-apple-darwin/clang (Release) (push) Has been cancelled
JIT / x86_64-pc-windows-msvc/msvc (Release) (push) Has been cancelled
JIT / x86_64-pc-windows-msvc/msvc (Debug) (push) Has been cancelled
JIT / x86_64-apple-darwin/clang (Release) (push) Has been cancelled
JIT / x86_64-unknown-linux-gnu/gcc (Release) (push) Has been cancelled
JIT / x86_64-unknown-linux-gnu/gcc (Debug) (push) Has been cancelled
JIT / i686-pc-windows-msvc/msvc (Release) (push) Has been cancelled
JIT / aarch64-unknown-linux-gnu/gcc (Release) (push) Has been cancelled
JIT / aarch64-apple-darwin/clang (Debug) (push) Has been cancelled
JIT / aarch64-unknown-linux-gnu/gcc (Debug) (push) Has been cancelled
JIT / x86_64-apple-darwin/clang (Debug) (push) Has been cancelled

This commit is contained in:
Pablo Galindo Salgado 2025-08-21 16:56:57 +01:00 committed by GitHub
parent 339f5da639
commit 539a4ca1b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 257 additions and 1 deletions

View file

@ -1601,6 +1601,34 @@ def _substitution_cost(ch_a, ch_b):
return _MOVE_COST
def _check_for_nested_attribute(obj, wrong_name, attrs):
"""Check if any attribute of obj has the wrong_name as a nested attribute.
Returns the first nested attribute suggestion found, or None.
Limited to checking 20 attributes.
Only considers non-descriptor attributes to avoid executing arbitrary code.
"""
# 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__'):
continue # Skip descriptors to avoid executing arbitrary code
# 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 hasattr(attr_obj, wrong_name):
return f"{attr_name}.{wrong_name}"
return None
def _compute_suggestion_error(exc_value, tb, wrong_name):
if wrong_name is None or not isinstance(wrong_name, str):
return None
@ -1666,7 +1694,9 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
except ImportError:
pass
else:
return _suggestions._generate_suggestions(d, wrong_name)
suggestion = _suggestions._generate_suggestions(d, wrong_name)
if suggestion:
return suggestion
# Compute closest match
@ -1691,6 +1721,14 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
if not suggestion or current_distance < best_distance:
suggestion = possible_name
best_distance = current_distance
# If no direct attribute match found, check for nested attributes
if not suggestion and isinstance(exc_value, AttributeError):
with suppress(Exception):
nested_suggestion = _check_for_nested_attribute(exc_value.obj, wrong_name, d)
if nested_suggestion:
return nested_suggestion
return suggestion