From ee4081fb0f6f24b31f2f8189b83fe00ecb87d1a6 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 22 Feb 2025 01:23:50 +0300 Subject: [PATCH 01/10] gh-130425: Add "Did you mean" suggestion for `del obj.attr` --- .../2025-02-22-01-23-23.gh-issue-130425.x5SNQ8.rst | 2 ++ Objects/dictobject.c | 1 + 2 files changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-02-22-01-23-23.gh-issue-130425.x5SNQ8.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-22-01-23-23.gh-issue-130425.x5SNQ8.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-22-01-23-23.gh-issue-130425.x5SNQ8.rst new file mode 100644 index 00000000000000..7dbff8f76df6ea --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-22-01-23-23.gh-issue-130425.x5SNQ8.rst @@ -0,0 +1,2 @@ +Add ``"Did you mean"`` suggestion when using ``del obj.attr`` if ``attr`` +does not exist. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index be62ae5eefd00d..f8e63a9315d9c0 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -6939,6 +6939,7 @@ store_instance_attr_lock_held(PyObject *obj, PyDictValues *values, PyErr_Format(PyExc_AttributeError, "'%.100s' object has no attribute '%U'", Py_TYPE(obj)->tp_name, name); + _PyObject_SetAttributeErrorContext(obj, name); return -1; } From cf9a052aecbf15e54a7adf60a10bb3b7c1a90260 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 22 Feb 2025 09:56:57 +0300 Subject: [PATCH 02/10] Update NEWS --- .../2025-02-22-01-23-23.gh-issue-130425.x5SNQ8.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-22-01-23-23.gh-issue-130425.x5SNQ8.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-22-01-23-23.gh-issue-130425.x5SNQ8.rst index 7dbff8f76df6ea..a655cf2f2a765b 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-22-01-23-23.gh-issue-130425.x5SNQ8.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-22-01-23-23.gh-issue-130425.x5SNQ8.rst @@ -1,2 +1,2 @@ -Add ``"Did you mean"`` suggestion when using ``del obj.attr`` if ``attr`` +Add ``"Did you mean: 'attr'?"`` suggestion when using ``del obj.attr`` if ``attr`` does not exist. From c3698760b2281f1e9d1fe44c1693f677b73a8414 Mon Sep 17 00:00:00 2001 From: Pranjal095 Date: Sat, 22 Feb 2025 17:42:43 +0530 Subject: [PATCH 03/10] gh-130428: Add tests for delattr suggestions --- Lib/test/test_traceback.py | 98 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 74b979d009664d..be6d7ca5bcac2f 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -4178,6 +4178,104 @@ def __dir__(self): actual = self.get_suggestion(A(), 'blech') self.assertNotIn("Did you mean", actual) + def test_delattr_suggestions(self): + class Substitution: + noise = more_noise = a = bc = None + blech = None + + class Elimination: + noise = more_noise = a = bc = None + blch = None + + class Addition: + noise = more_noise = a = bc = None + bluchin = None + + class SubstitutionOverElimination: + blach = None + bluc = None + + class SubstitutionOverAddition: + blach = None + bluchi = None + + class EliminationOverAddition: + blucha = None + bluc = None + + class CaseChangeOverSubstitution: + Luch = None + fluch = None + BLuch = None + + for cls, suggestion in [ + (Addition, "'bluchin'?"), + (Substitution, "'blech'?"), + (Elimination, "'blch'?"), + (Addition, "'bluchin'?"), + (SubstitutionOverElimination, "'blach'?"), + (SubstitutionOverAddition, "'blach'?"), + (EliminationOverAddition, "'bluc'?"), + (CaseChangeOverSubstitution, "'BLuch'?"), + ]: + obj = cls() + def callable(): + delattr(obj, 'bluch') + actual = self.get_suggestion(callable) + self.assertIn(suggestion, actual) + + def test_delattr_suggestions_underscored(self): + class A: + bluch = None + + obj = A() + self.assertIn("'bluch'", self.get_suggestion(lambda: delattr(obj, 'blach'))) + self.assertIn("'bluch'", self.get_suggestion(lambda: delattr(obj, '_luch'))) + self.assertIn("'bluch'", self.get_suggestion(lambda: delattr(obj, '_bluch'))) + + class B: + _bluch = None + + obj = B() + self.assertIn("'_bluch'", self.get_suggestion(lambda: delattr(obj, '_blach'))) + self.assertIn("'_bluch'", self.get_suggestion(lambda: delattr(obj, '_luch'))) + self.assertNotIn("'_bluch'", self.get_suggestion(lambda: delattr(obj, 'bluch'))) + + def test_delattr_suggestions_do_not_trigger_for_long_attributes(self): + class A: + blech = None + + obj = A() + actual = self.get_suggestion(lambda: delattr(obj, 'somethingverywrong')) + self.assertNotIn("blech", actual) + + def test_delattr_error_bad_suggestions_do_not_trigger_for_small_names(self): + class MyClass: + vvv = mom = w = id = pytho = None + + obj = MyClass() + for name in ("b", "v", "m", "py"): + with self.subTest(name=name): + actual = self.get_suggestion(lambda: delattr(obj, name)) + self.assertNotIn("Did you mean", actual) + self.assertNotIn("'vvv", actual) + self.assertNotIn("'mom'", actual) + self.assertNotIn("'id'", actual) + self.assertNotIn("'w'", actual) + self.assertNotIn("'pytho'", actual) + + def test_delattr_suggestions_do_not_trigger_for_big_dicts(self): + class A: + blech = None + # A class with a very big __dict__ will not be considered + # for suggestions. + obj = A() + for index in range(2000): + setattr(obj, f"index_{index}", None) + + actual = self.get_suggestion(lambda: delattr(obj, 'bluch')) + self.assertNotIn("blech", actual) + def test_attribute_error_with_failing_dict(self): class T: bluch = 1 From 04254addb93c336306195e2c785837ec4b644c27 Mon Sep 17 00:00:00 2001 From: Pranjal095 Date: Sun, 23 Feb 2025 02:00:18 +0530 Subject: [PATCH 04/10] Refactored getattr and delattr tests --- Lib/test/test_traceback.py | 193 +++++++++++++++---------------------- 1 file changed, 77 insertions(+), 116 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index be6d7ca5bcac2f..f951c82062f1e4 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -4034,7 +4034,7 @@ def callable(): ) return result_lines[0] - def test_getattr_suggestions(self): + def run_suggestion_tests(self, operation): class Substitution: noise = more_noise = a = bc = None blech = None @@ -4074,44 +4074,87 @@ class CaseChangeOverSubstitution: (EliminationOverAddition, "'bluc'?"), (CaseChangeOverSubstitution, "'BLuch'?"), ]: - actual = self.get_suggestion(cls(), 'bluch') + obj = cls() + + if operation == "getattr": + actual = self.get_suggestion(obj, 'bluch') + elif operation == "delattr": + actual = self.get_suggestion(lambda: delattr(obj, 'bluch')) + self.assertIn(suggestion, actual) - def test_getattr_suggestions_underscored(self): + def test_getattr_suggestions(self): + self.run_suggestion_tests("getattr") + + def test_delattr_suggestions(self): + self.run_suggestion_tests("delattr") + + def run_underscored_tests(self, operation): class A: bluch = None - self.assertIn("'bluch'", self.get_suggestion(A(), 'blach')) - self.assertIn("'bluch'", self.get_suggestion(A(), '_luch')) - self.assertIn("'bluch'", self.get_suggestion(A(), '_bluch')) + obj = A() + if operation == "getattr": + self.assertIn("'bluch'", self.get_suggestion(obj, 'blach')) + self.assertIn("'bluch'", self.get_suggestion(obj, '_luch')) + self.assertIn("'bluch'", self.get_suggestion(obj, '_bluch')) + elif operation == "delattr": + self.assertIn("'bluch'", self.get_suggestion(lambda: delattr(obj, 'blach'))) + self.assertIn("'bluch'", self.get_suggestion(lambda: delattr(obj, '_luch'))) + self.assertIn("'bluch'", self.get_suggestion(lambda: delattr(obj, '_bluch'))) class B: _bluch = None def method(self, name): getattr(self, name) - self.assertIn("'_bluch'", self.get_suggestion(B(), '_blach')) - self.assertIn("'_bluch'", self.get_suggestion(B(), '_luch')) - self.assertNotIn("'_bluch'", self.get_suggestion(B(), 'bluch')) + obj = B() + if operation == "getattr": + self.assertIn("'_bluch'", self.get_suggestion(obj, '_blach')) + self.assertIn("'_bluch'", self.get_suggestion(obj, '_luch')) + self.assertNotIn("'_bluch'", self.get_suggestion(obj, 'bluch')) + self.assertIn("'_bluch'", self.get_suggestion(partial(obj.method, '_blach'))) + self.assertIn("'_bluch'", self.get_suggestion(partial(obj.method, '_luch'))) + self.assertIn("'_bluch'", self.get_suggestion(partial(obj.method, 'bluch'))) + elif operation == "delattr": + self.assertIn("'_bluch'", self.get_suggestion(lambda: delattr(obj, '_blach'))) + self.assertIn("'_bluch'", self.get_suggestion(lambda: delattr(obj, '_luch'))) + self.assertNotIn("'_bluch'", self.get_suggestion(lambda: delattr(obj, 'bluch'))) - self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, '_blach'))) - self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, '_luch'))) - self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, 'bluch'))) + def test_getattr_suggestions_underscored(self): + self.run_underscored_tests("getattr") - def test_getattr_suggestions_do_not_trigger_for_long_attributes(self): + def test_delattr_suggestions_underscored(self): + self.run_underscored_tests("delattr") + + def run_do_not_trigger_for_long_attributes_tests(self, operation): class A: blech = None - actual = self.get_suggestion(A(), 'somethingverywrong') + obj = A() + if operation == "getattr": + actual = self.get_suggestion(obj, 'somethingverywrong') + elif operation == "delattr": + actual = self.get_suggestion(lambda: delattr(obj, 'somethingverywrong')) self.assertNotIn("blech", actual) - def test_getattr_error_bad_suggestions_do_not_trigger_for_small_names(self): + def test_getattr_suggestions_do_not_trigger_for_long_attributes(self): + self.run_do_not_trigger_for_long_attributes_tests("getattr") + + def test_delattr_suggestions_do_not_trigger_for_long_attributes(self): + self.run_do_not_trigger_for_long_attributes_tests("delattr") + + def run_do_not_trigger_for_small_names_tests(self, operation): class MyClass: vvv = mom = w = id = pytho = None + obj = MyClass() for name in ("b", "v", "m", "py"): with self.subTest(name=name): - actual = self.get_suggestion(MyClass, name) + if operation == "getattr": + actual = self.get_suggestion(MyClass, name) + elif operation == "delattr": + actual = self.get_suggestion(lambda: delattr(obj, name)) self.assertNotIn("Did you mean", actual) self.assertNotIn("'vvv", actual) self.assertNotIn("'mom'", actual) @@ -4119,7 +4162,13 @@ class MyClass: self.assertNotIn("'w'", actual) self.assertNotIn("'pytho'", actual) - def test_getattr_suggestions_do_not_trigger_for_big_dicts(self): + def test_getattr_error_bad_suggestions_do_not_trigger_for_small_names(self): + self.run_do_not_trigger_for_small_names_tests("getattr") + + def test_delattr_error_bad_suggestions_do_not_trigger_for_small_names(self): + self.run_do_not_trigger_for_small_names_tests("delattr") + + def run_do_not_trigger_for_big_dicts_tests(self, operation): class A: blech = None # A class with a very big __dict__ will not be considered @@ -4127,9 +4176,19 @@ class A: for index in range(2000): setattr(A, f"index_{index}", None) - actual = self.get_suggestion(A(), 'bluch') + obj = A() + if operation == "getattr": + actual = self.get_suggestion(obj, 'bluch') + elif operation == "delattr": + actual = self.get_suggestion(lambda: delattr(obj, 'bluch')) self.assertNotIn("blech", actual) + def test_getattr_suggestions_do_not_trigger_for_big_dicts(self): + self.run_do_not_trigger_for_big_dicts_tests("getattr") + + def test_delattr_suggestions_do_not_trigger_for_big_dicts(self): + self.run_do_not_trigger_for_big_dicts_tests("delattr") + def test_getattr_suggestions_no_args(self): class A: blech = None @@ -4178,104 +4237,6 @@ def __dir__(self): actual = self.get_suggestion(A(), 'blech') self.assertNotIn("Did you mean", actual) - def test_delattr_suggestions(self): - class Substitution: - noise = more_noise = a = bc = None - blech = None - - class Elimination: - noise = more_noise = a = bc = None - blch = None - - class Addition: - noise = more_noise = a = bc = None - bluchin = None - - class SubstitutionOverElimination: - blach = None - bluc = None - - class SubstitutionOverAddition: - blach = None - bluchi = None - - class EliminationOverAddition: - blucha = None - bluc = None - - class CaseChangeOverSubstitution: - Luch = None - fluch = None - BLuch = None - - for cls, suggestion in [ - (Addition, "'bluchin'?"), - (Substitution, "'blech'?"), - (Elimination, "'blch'?"), - (Addition, "'bluchin'?"), - (SubstitutionOverElimination, "'blach'?"), - (SubstitutionOverAddition, "'blach'?"), - (EliminationOverAddition, "'bluc'?"), - (CaseChangeOverSubstitution, "'BLuch'?"), - ]: - obj = cls() - def callable(): - delattr(obj, 'bluch') - actual = self.get_suggestion(callable) - self.assertIn(suggestion, actual) - - def test_delattr_suggestions_underscored(self): - class A: - bluch = None - - obj = A() - self.assertIn("'bluch'", self.get_suggestion(lambda: delattr(obj, 'blach'))) - self.assertIn("'bluch'", self.get_suggestion(lambda: delattr(obj, '_luch'))) - self.assertIn("'bluch'", self.get_suggestion(lambda: delattr(obj, '_bluch'))) - - class B: - _bluch = None - - obj = B() - self.assertIn("'_bluch'", self.get_suggestion(lambda: delattr(obj, '_blach'))) - self.assertIn("'_bluch'", self.get_suggestion(lambda: delattr(obj, '_luch'))) - self.assertNotIn("'_bluch'", self.get_suggestion(lambda: delattr(obj, 'bluch'))) - - def test_delattr_suggestions_do_not_trigger_for_long_attributes(self): - class A: - blech = None - - obj = A() - actual = self.get_suggestion(lambda: delattr(obj, 'somethingverywrong')) - self.assertNotIn("blech", actual) - - def test_delattr_error_bad_suggestions_do_not_trigger_for_small_names(self): - class MyClass: - vvv = mom = w = id = pytho = None - - obj = MyClass() - for name in ("b", "v", "m", "py"): - with self.subTest(name=name): - actual = self.get_suggestion(lambda: delattr(obj, name)) - self.assertNotIn("Did you mean", actual) - self.assertNotIn("'vvv", actual) - self.assertNotIn("'mom'", actual) - self.assertNotIn("'id'", actual) - self.assertNotIn("'w'", actual) - self.assertNotIn("'pytho'", actual) - - def test_delattr_suggestions_do_not_trigger_for_big_dicts(self): - class A: - blech = None - # A class with a very big __dict__ will not be considered - # for suggestions. - obj = A() - for index in range(2000): - setattr(obj, f"index_{index}", None) - - actual = self.get_suggestion(lambda: delattr(obj, 'bluch')) - self.assertNotIn("blech", actual) - def test_attribute_error_with_failing_dict(self): class T: bluch = 1 From 57deaf3c4051ec3ab31ee0c543959efb636520de Mon Sep 17 00:00:00 2001 From: Pranjal095 Date: Sun, 23 Feb 2025 02:14:36 +0530 Subject: [PATCH 05/10] Added else branches to handle unrecognized operations --- Lib/test/test_traceback.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index f951c82062f1e4..fe0865e2a942ad 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -4075,12 +4075,14 @@ class CaseChangeOverSubstitution: (CaseChangeOverSubstitution, "'BLuch'?"), ]: obj = cls() - + if operation == "getattr": actual = self.get_suggestion(obj, 'bluch') elif operation == "delattr": actual = self.get_suggestion(lambda: delattr(obj, 'bluch')) - + else: + raise ValueError(f"operation '{operation}' not recognized") + self.assertIn(suggestion, actual) def test_getattr_suggestions(self): @@ -4102,6 +4104,8 @@ class A: self.assertIn("'bluch'", self.get_suggestion(lambda: delattr(obj, 'blach'))) self.assertIn("'bluch'", self.get_suggestion(lambda: delattr(obj, '_luch'))) self.assertIn("'bluch'", self.get_suggestion(lambda: delattr(obj, '_bluch'))) + else: + raise ValueError(f"operation '{operation}' not recognized") class B: _bluch = None @@ -4120,6 +4124,8 @@ def method(self, name): self.assertIn("'_bluch'", self.get_suggestion(lambda: delattr(obj, '_blach'))) self.assertIn("'_bluch'", self.get_suggestion(lambda: delattr(obj, '_luch'))) self.assertNotIn("'_bluch'", self.get_suggestion(lambda: delattr(obj, 'bluch'))) + else: + raise ValueError(f"operation '{operation}' not recognized") def test_getattr_suggestions_underscored(self): self.run_underscored_tests("getattr") @@ -4136,6 +4142,8 @@ class A: actual = self.get_suggestion(obj, 'somethingverywrong') elif operation == "delattr": actual = self.get_suggestion(lambda: delattr(obj, 'somethingverywrong')) + else: + raise ValueError(f"operation '{operation}' not recognized") self.assertNotIn("blech", actual) def test_getattr_suggestions_do_not_trigger_for_long_attributes(self): @@ -4155,6 +4163,8 @@ class MyClass: actual = self.get_suggestion(MyClass, name) elif operation == "delattr": actual = self.get_suggestion(lambda: delattr(obj, name)) + else: + raise ValueError(f"operation '{operation}' not recognized") self.assertNotIn("Did you mean", actual) self.assertNotIn("'vvv", actual) self.assertNotIn("'mom'", actual) @@ -4181,6 +4191,8 @@ class A: actual = self.get_suggestion(obj, 'bluch') elif operation == "delattr": actual = self.get_suggestion(lambda: delattr(obj, 'bluch')) + else: + raise ValueError(f"operation '{operation}' not recognized") self.assertNotIn("blech", actual) def test_getattr_suggestions_do_not_trigger_for_big_dicts(self): @@ -4188,7 +4200,7 @@ def test_getattr_suggestions_do_not_trigger_for_big_dicts(self): def test_delattr_suggestions_do_not_trigger_for_big_dicts(self): self.run_do_not_trigger_for_big_dicts_tests("delattr") - + def test_getattr_suggestions_no_args(self): class A: blech = None From bbae851b0c0c2aca4e528c52ebe9e94a8bf17e2b Mon Sep 17 00:00:00 2001 From: Pranjal095 Date: Tue, 4 Mar 2025 16:33:55 +0530 Subject: [PATCH 06/10] Refactor getattr and setattr suggestion tests --- Lib/test/test_traceback.py | 405 +++++++++++++++++++------------------ 1 file changed, 208 insertions(+), 197 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index fe0865e2a942ad..89eea5bf65975d 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -4022,232 +4022,200 @@ def test_dont_swallow_subexceptions_of_falsey_exceptiongroup(self): class SuggestionFormattingTestBase: - def get_suggestion(self, obj, attr_name=None): - if attr_name is not None: - def callable(): - getattr(obj, attr_name) - else: - callable = obj - - result_lines = self.get_exception( - callable, slice_start=-1, slice_end=None - ) - return result_lines[0] - - def run_suggestion_tests(self, operation): - class Substitution: - noise = more_noise = a = bc = None - blech = None - - class Elimination: - noise = more_noise = a = bc = None - blch = None - - class Addition: - noise = more_noise = a = bc = None - bluchin = None - - class SubstitutionOverElimination: - blach = None - bluc = None - - class SubstitutionOverAddition: - blach = None - bluchi = None - - class EliminationOverAddition: - blucha = None - bluc = None - - class CaseChangeOverSubstitution: - Luch = None - fluch = None - BLuch = None - - for cls, suggestion in [ - (Addition, "'bluchin'?"), - (Substitution, "'blech'?"), - (Elimination, "'blch'?"), - (Addition, "'bluchin'?"), - (SubstitutionOverElimination, "'blach'?"), - (SubstitutionOverAddition, "'blach'?"), - (EliminationOverAddition, "'bluc'?"), - (CaseChangeOverSubstitution, "'BLuch'?"), - ]: - obj = cls() - - if operation == "getattr": + class BaseSuggestionTests: + """ + Subclasses need to implement the get_suggestion method. + """ + def test_suggestions(self): + class Substitution: + noise = more_noise = a = bc = None + blech = None + + class Elimination: + noise = more_noise = a = bc = None + blch = None + + class Addition: + noise = more_noise = a = bc = None + bluchin = None + + class SubstitutionOverElimination: + blach = None + bluc = None + + class SubstitutionOverAddition: + blach = None + bluchi = None + + class EliminationOverAddition: + blucha = None + bluc = None + + class CaseChangeOverSubstitution: + Luch = None + fluch = None + BLuch = None + + for cls, suggestion in [ + (Addition, "'bluchin'?"), + (Substitution, "'blech'?"), + (Elimination, "'blch'?"), + (Addition, "'bluchin'?"), + (SubstitutionOverElimination, "'blach'?"), + (SubstitutionOverAddition, "'blach'?"), + (EliminationOverAddition, "'bluc'?"), + (CaseChangeOverSubstitution, "'BLuch'?"), + ]: + obj = cls() actual = self.get_suggestion(obj, 'bluch') - elif operation == "delattr": - actual = self.get_suggestion(lambda: delattr(obj, 'bluch')) - else: - raise ValueError(f"operation '{operation}' not recognized") - - self.assertIn(suggestion, actual) + self.assertIn(suggestion, actual) - def test_getattr_suggestions(self): - self.run_suggestion_tests("getattr") + def test_suggestions_underscored(self): + class A: + bluch = None - def test_delattr_suggestions(self): - self.run_suggestion_tests("delattr") - - def run_underscored_tests(self, operation): - class A: - bluch = None - - obj = A() - if operation == "getattr": + obj = A() self.assertIn("'bluch'", self.get_suggestion(obj, 'blach')) self.assertIn("'bluch'", self.get_suggestion(obj, '_luch')) self.assertIn("'bluch'", self.get_suggestion(obj, '_bluch')) - elif operation == "delattr": - self.assertIn("'bluch'", self.get_suggestion(lambda: delattr(obj, 'blach'))) - self.assertIn("'bluch'", self.get_suggestion(lambda: delattr(obj, '_luch'))) - self.assertIn("'bluch'", self.get_suggestion(lambda: delattr(obj, '_bluch'))) - else: - raise ValueError(f"operation '{operation}' not recognized") - class B: - _bluch = None - def method(self, name): - getattr(self, name) + class B: + _bluch = None + def method(self, name): + getattr(self, name) - obj = B() - if operation == "getattr": + obj = B() self.assertIn("'_bluch'", self.get_suggestion(obj, '_blach')) self.assertIn("'_bluch'", self.get_suggestion(obj, '_luch')) self.assertNotIn("'_bluch'", self.get_suggestion(obj, 'bluch')) - self.assertIn("'_bluch'", self.get_suggestion(partial(obj.method, '_blach'))) - self.assertIn("'_bluch'", self.get_suggestion(partial(obj.method, '_luch'))) - self.assertIn("'_bluch'", self.get_suggestion(partial(obj.method, 'bluch'))) - elif operation == "delattr": - self.assertIn("'_bluch'", self.get_suggestion(lambda: delattr(obj, '_blach'))) - self.assertIn("'_bluch'", self.get_suggestion(lambda: delattr(obj, '_luch'))) - self.assertNotIn("'_bluch'", self.get_suggestion(lambda: delattr(obj, 'bluch'))) - else: - raise ValueError(f"operation '{operation}' not recognized") - - def test_getattr_suggestions_underscored(self): - self.run_underscored_tests("getattr") - def test_delattr_suggestions_underscored(self): - self.run_underscored_tests("delattr") + if hasattr(self, 'test_with_method_call'): + self.assertIn("'_bluch'", self.get_suggestion(partial(obj.method, '_blach'))) + self.assertIn("'_bluch'", self.get_suggestion(partial(obj.method, '_luch'))) + self.assertIn("'_bluch'", self.get_suggestion(partial(obj.method, 'bluch'))) - def run_do_not_trigger_for_long_attributes_tests(self, operation): - class A: - blech = None + def test_do_not_trigger_for_long_attributes(self): + class A: + blech = None - obj = A() - if operation == "getattr": + obj = A() actual = self.get_suggestion(obj, 'somethingverywrong') - elif operation == "delattr": - actual = self.get_suggestion(lambda: delattr(obj, 'somethingverywrong')) - else: - raise ValueError(f"operation '{operation}' not recognized") - self.assertNotIn("blech", actual) - - def test_getattr_suggestions_do_not_trigger_for_long_attributes(self): - self.run_do_not_trigger_for_long_attributes_tests("getattr") - - def test_delattr_suggestions_do_not_trigger_for_long_attributes(self): - self.run_do_not_trigger_for_long_attributes_tests("delattr") - - def run_do_not_trigger_for_small_names_tests(self, operation): - class MyClass: - vvv = mom = w = id = pytho = None - - obj = MyClass() - for name in ("b", "v", "m", "py"): - with self.subTest(name=name): - if operation == "getattr": - actual = self.get_suggestion(MyClass, name) - elif operation == "delattr": - actual = self.get_suggestion(lambda: delattr(obj, name)) - else: - raise ValueError(f"operation '{operation}' not recognized") - self.assertNotIn("Did you mean", actual) - self.assertNotIn("'vvv", actual) - self.assertNotIn("'mom'", actual) - self.assertNotIn("'id'", actual) - self.assertNotIn("'w'", actual) - self.assertNotIn("'pytho'", actual) - - def test_getattr_error_bad_suggestions_do_not_trigger_for_small_names(self): - self.run_do_not_trigger_for_small_names_tests("getattr") - - def test_delattr_error_bad_suggestions_do_not_trigger_for_small_names(self): - self.run_do_not_trigger_for_small_names_tests("delattr") + self.assertNotIn("blech", actual) + + def test_do_not_trigger_for_small_names(self): + class MyClass: + vvv = mom = w = id = pytho = None + + obj = MyClass() + for name in ("b", "v", "m", "py"): + with self.subTest(name=name): + actual = self.get_suggestion(obj, name) + self.assertNotIn("Did you mean", actual) + self.assertNotIn("'vvv", actual) + self.assertNotIn("'mom'", actual) + self.assertNotIn("'id'", actual) + self.assertNotIn("'w'", actual) + self.assertNotIn("'pytho'", actual) + + def test_do_not_trigger_for_big_dicts(self): + class A: + blech = None + # A class with a very big __dict__ will not be considered + # for suggestions. + for index in range(2000): + setattr(A, f"index_{index}", None) + + obj = A() + actual = self.get_suggestion(obj, 'bluch') + self.assertNotIn("blech", actual) - def run_do_not_trigger_for_big_dicts_tests(self, operation): - class A: - blech = None - # A class with a very big __dict__ will not be considered - # for suggestions. - for index in range(2000): - setattr(A, f"index_{index}", None) + class GetattrSuggestionTests(BaseSuggestionTests): + def get_suggestion(self, obj, attr_name=None): + if attr_name is not None: + def callable(): + getattr(obj, attr_name) + else: + callable = obj - obj = A() - if operation == "getattr": - actual = self.get_suggestion(obj, 'bluch') - elif operation == "delattr": - actual = self.get_suggestion(lambda: delattr(obj, 'bluch')) - else: - raise ValueError(f"operation '{operation}' not recognized") - self.assertNotIn("blech", actual) + result_lines = self.get_exception( + callable, slice_start=-1, slice_end=None + ) + return result_lines[0] - def test_getattr_suggestions_do_not_trigger_for_big_dicts(self): - self.run_do_not_trigger_for_big_dicts_tests("getattr") + def test_with_method_call(self): + # This is a placeholder method to make + # hasattr(self, 'test_with_method_call') return True + pass - def test_delattr_suggestions_do_not_trigger_for_big_dicts(self): - self.run_do_not_trigger_for_big_dicts_tests("delattr") + def test_suggestions_no_args(self): + class A: + blech = None + def __getattr__(self, attr): + raise AttributeError() - def test_getattr_suggestions_no_args(self): - class A: - blech = None - def __getattr__(self, attr): - raise AttributeError() + actual = self.get_suggestion(A(), 'bluch') + self.assertIn("blech", actual) - actual = self.get_suggestion(A(), 'bluch') - self.assertIn("blech", actual) + class A: + blech = None + def __getattr__(self, attr): + raise AttributeError - class A: - blech = None - def __getattr__(self, attr): - raise AttributeError + actual = self.get_suggestion(A(), 'bluch') + self.assertIn("blech", actual) - actual = self.get_suggestion(A(), 'bluch') - self.assertIn("blech", actual) + def test_suggestions_invalid_args(self): + class NonStringifyClass: + __str__ = None + __repr__ = None - def test_getattr_suggestions_invalid_args(self): - class NonStringifyClass: - __str__ = None - __repr__ = None + class A: + blech = None + def __getattr__(self, attr): + raise AttributeError(NonStringifyClass()) - class A: - blech = None - def __getattr__(self, attr): - raise AttributeError(NonStringifyClass()) + class B: + blech = None + def __getattr__(self, attr): + raise AttributeError("Error", 23) - class B: - blech = None - def __getattr__(self, attr): - raise AttributeError("Error", 23) + class C: + blech = None + def __getattr__(self, attr): + raise AttributeError(23) + + for cls in [A, B, C]: + actual = self.get_suggestion(cls(), 'bluch') + self.assertIn("blech", actual) + + def test_suggestions_for_same_name(self): + class A: + def __dir__(self): + return ['blech'] + actual = self.get_suggestion(A(), 'blech') + self.assertNotIn("Did you mean", actual) + + class DelattrSuggestionTests(BaseSuggestionTests): + def get_suggestion(self, obj, attr_name): + def callable(): + delattr(obj, attr_name) - class C: - blech = None - def __getattr__(self, attr): - raise AttributeError(23) + result_lines = self.get_exception( + callable, slice_start=-1, slice_end=None + ) + return result_lines[0] - for cls in [A, B, C]: - actual = self.get_suggestion(cls(), 'bluch') - self.assertIn("blech", actual) + def get_suggestion(self, obj, attr_name=None): + if attr_name is not None: + def callable(): + getattr(obj, attr_name) + else: + callable = obj - def test_getattr_suggestions_for_same_name(self): - class A: - def __dir__(self): - return ['blech'] - actual = self.get_suggestion(A(), 'blech') - self.assertNotIn("Did you mean", actual) + result_lines = self.get_exception( + callable, slice_start=-1, slice_end=None + ) + return result_lines[0] def test_attribute_error_with_failing_dict(self): class T: @@ -4726,6 +4694,49 @@ class CPythonSuggestionFormattingTests( """ +class PurePythonGetattrSuggestionFormattingTests( + PurePythonExceptionFormattingMixin, + SuggestionFormattingTestBase.GetattrSuggestionTests, + unittest.TestCase, +): + """ + Same set of tests (for attribute access) as above using the pure Python implementation of + traceback printing in traceback.py. + """ + + +class PurePythonDelattrSuggestionFormattingTests( + PurePythonExceptionFormattingMixin, + SuggestionFormattingTestBase.DelattrSuggestionTests, + unittest.TestCase, +): + """ + Same set of tests (for attribute deletion) as above using the pure Python implementation of + traceback printing in traceback.py. + """ + + +@cpython_only +class CPythonGetattrSuggestionFormattingTests( + CAPIExceptionFormattingMixin, + SuggestionFormattingTestBase.GetattrSuggestionTests, + unittest.TestCase, +): + """ + Same set of tests (for attribute access) as above but with Python's internal traceback printing. + """ + + +@cpython_only +class CPythonDelattrSuggestionFormattingTests( + CAPIExceptionFormattingMixin, + SuggestionFormattingTestBase.DelattrSuggestionTests, + unittest.TestCase, +): + """ + Same set of tests (for attribute deletion) as above but with Python's internal traceback printing. + """ + class MiscTest(unittest.TestCase): def test_all(self): From 8ba26c455bacb71544292f754d5a0f23284974a9 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 28 Mar 2025 12:59:03 -0600 Subject: [PATCH 07/10] docs: -I also implies -P (#131539) From b1210ddc330998e2579ef407180944db84716e57 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 19 May 2025 18:39:43 +0300 Subject: [PATCH 08/10] GH-134236: make regen-all (GH-134237) From 60e6243235840c4bcd7e995a716bab5b759e7e27 Mon Sep 17 00:00:00 2001 From: "Tomas R." Date: Thu, 22 May 2025 14:05:43 -0400 Subject: [PATCH 09/10] GH-131798: Optimize away isinstance calls in the JIT (GH-134369) From 0ecb6d8d030f4c59d6e0f0b95affd3a973d91819 Mon Sep 17 00:00:00 2001 From: Pranjal095 Date: Sat, 12 Jul 2025 20:31:43 +0530 Subject: [PATCH 10/10] Refactor getattr and delattr suggestion tests --- Lib/test/test_traceback.py | 348 ++++++++++++++++++------------------- 1 file changed, 174 insertions(+), 174 deletions(-) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 89eea5bf65975d..bdc34a29022ffe 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -4020,191 +4020,127 @@ def test_dont_swallow_subexceptions_of_falsey_exceptiongroup(self): global_for_suggestions = None +class SuggestionFormattingTestBaseParent: + def get_suggestion(self, obj, attr_name=None): + if attr_name is not None: + def callable(): + getattr(obj, attr_name) + else: + callable = obj -class SuggestionFormattingTestBase: - class BaseSuggestionTests: - """ - Subclasses need to implement the get_suggestion method. - """ - def test_suggestions(self): - class Substitution: - noise = more_noise = a = bc = None - blech = None - - class Elimination: - noise = more_noise = a = bc = None - blch = None - - class Addition: - noise = more_noise = a = bc = None - bluchin = None - - class SubstitutionOverElimination: - blach = None - bluc = None - - class SubstitutionOverAddition: - blach = None - bluchi = None - - class EliminationOverAddition: - blucha = None - bluc = None - - class CaseChangeOverSubstitution: - Luch = None - fluch = None - BLuch = None - - for cls, suggestion in [ - (Addition, "'bluchin'?"), - (Substitution, "'blech'?"), - (Elimination, "'blch'?"), - (Addition, "'bluchin'?"), - (SubstitutionOverElimination, "'blach'?"), - (SubstitutionOverAddition, "'blach'?"), - (EliminationOverAddition, "'bluc'?"), - (CaseChangeOverSubstitution, "'BLuch'?"), - ]: - obj = cls() - actual = self.get_suggestion(obj, 'bluch') - self.assertIn(suggestion, actual) - - def test_suggestions_underscored(self): - class A: - bluch = None - - obj = A() - self.assertIn("'bluch'", self.get_suggestion(obj, 'blach')) - self.assertIn("'bluch'", self.get_suggestion(obj, '_luch')) - self.assertIn("'bluch'", self.get_suggestion(obj, '_bluch')) + result_lines = self.get_exception( + callable, slice_start=-1, slice_end=None + ) + return result_lines[0] + +class BaseSuggestionTests(SuggestionFormattingTestBaseParent): + """ + Subclasses need to implement the get_suggestion method. + """ + def test_suggestions(self): + class Substitution: + noise = more_noise = a = bc = None + blech = None - class B: - _bluch = None - def method(self, name): - getattr(self, name) - - obj = B() - self.assertIn("'_bluch'", self.get_suggestion(obj, '_blach')) - self.assertIn("'_bluch'", self.get_suggestion(obj, '_luch')) - self.assertNotIn("'_bluch'", self.get_suggestion(obj, 'bluch')) - - if hasattr(self, 'test_with_method_call'): - self.assertIn("'_bluch'", self.get_suggestion(partial(obj.method, '_blach'))) - self.assertIn("'_bluch'", self.get_suggestion(partial(obj.method, '_luch'))) - self.assertIn("'_bluch'", self.get_suggestion(partial(obj.method, 'bluch'))) - - def test_do_not_trigger_for_long_attributes(self): - class A: - blech = None - - obj = A() - actual = self.get_suggestion(obj, 'somethingverywrong') - self.assertNotIn("blech", actual) - - def test_do_not_trigger_for_small_names(self): - class MyClass: - vvv = mom = w = id = pytho = None - - obj = MyClass() - for name in ("b", "v", "m", "py"): - with self.subTest(name=name): - actual = self.get_suggestion(obj, name) - self.assertNotIn("Did you mean", actual) - self.assertNotIn("'vvv", actual) - self.assertNotIn("'mom'", actual) - self.assertNotIn("'id'", actual) - self.assertNotIn("'w'", actual) - self.assertNotIn("'pytho'", actual) - - def test_do_not_trigger_for_big_dicts(self): - class A: - blech = None - # A class with a very big __dict__ will not be considered - # for suggestions. - for index in range(2000): - setattr(A, f"index_{index}", None) - - obj = A() - actual = self.get_suggestion(obj, 'bluch') - self.assertNotIn("blech", actual) + class Elimination: + noise = more_noise = a = bc = None + blch = None - class GetattrSuggestionTests(BaseSuggestionTests): - def get_suggestion(self, obj, attr_name=None): - if attr_name is not None: - def callable(): - getattr(obj, attr_name) - else: - callable = obj + class Addition: + noise = more_noise = a = bc = None + bluchin = None - result_lines = self.get_exception( - callable, slice_start=-1, slice_end=None - ) - return result_lines[0] + class SubstitutionOverElimination: + blach = None + bluc = None - def test_with_method_call(self): - # This is a placeholder method to make - # hasattr(self, 'test_with_method_call') return True - pass + class SubstitutionOverAddition: + blach = None + bluchi = None - def test_suggestions_no_args(self): - class A: - blech = None - def __getattr__(self, attr): - raise AttributeError() + class EliminationOverAddition: + blucha = None + bluc = None - actual = self.get_suggestion(A(), 'bluch') - self.assertIn("blech", actual) + class CaseChangeOverSubstitution: + Luch = None + fluch = None + BLuch = None - class A: - blech = None - def __getattr__(self, attr): - raise AttributeError + for cls, suggestion in [ + (Addition, "'bluchin'?"), + (Substitution, "'blech'?"), + (Elimination, "'blch'?"), + (Addition, "'bluchin'?"), + (SubstitutionOverElimination, "'blach'?"), + (SubstitutionOverAddition, "'blach'?"), + (EliminationOverAddition, "'bluc'?"), + (CaseChangeOverSubstitution, "'BLuch'?"), + ]: + obj = cls() + actual = self.get_suggestion(obj, 'bluch') + self.assertIn(suggestion, actual) - actual = self.get_suggestion(A(), 'bluch') - self.assertIn("blech", actual) + def test_suggestions_underscored(self): + class A: + bluch = None - def test_suggestions_invalid_args(self): - class NonStringifyClass: - __str__ = None - __repr__ = None + obj = A() + self.assertIn("'bluch'", self.get_suggestion(obj, 'blach')) + self.assertIn("'bluch'", self.get_suggestion(obj, '_luch')) + self.assertIn("'bluch'", self.get_suggestion(obj, '_bluch')) - class A: - blech = None - def __getattr__(self, attr): - raise AttributeError(NonStringifyClass()) + class B: + _bluch = None + def method(self, name): + getattr(self, name) - class B: - blech = None - def __getattr__(self, attr): - raise AttributeError("Error", 23) + obj = B() + self.assertIn("'_bluch'", self.get_suggestion(obj, '_blach')) + self.assertIn("'_bluch'", self.get_suggestion(obj, '_luch')) + self.assertNotIn("'_bluch'", self.get_suggestion(obj, 'bluch')) - class C: - blech = None - def __getattr__(self, attr): - raise AttributeError(23) - - for cls in [A, B, C]: - actual = self.get_suggestion(cls(), 'bluch') - self.assertIn("blech", actual) - - def test_suggestions_for_same_name(self): - class A: - def __dir__(self): - return ['blech'] - actual = self.get_suggestion(A(), 'blech') - self.assertNotIn("Did you mean", actual) - - class DelattrSuggestionTests(BaseSuggestionTests): - def get_suggestion(self, obj, attr_name): - def callable(): - delattr(obj, attr_name) + if hasattr(self, 'test_with_method_call'): + self.assertIn("'_bluch'", self.get_suggestion(partial(obj.method, '_blach'))) + self.assertIn("'_bluch'", self.get_suggestion(partial(obj.method, '_luch'))) + self.assertIn("'_bluch'", self.get_suggestion(partial(obj.method, 'bluch'))) - result_lines = self.get_exception( - callable, slice_start=-1, slice_end=None - ) - return result_lines[0] + def test_do_not_trigger_for_long_attributes(self): + class A: + blech = None + obj = A() + actual = self.get_suggestion(obj, 'somethingverywrong') + self.assertNotIn("blech", actual) + + def test_do_not_trigger_for_small_names(self): + class MyClass: + vvv = mom = w = id = pytho = None + + obj = MyClass() + for name in ("b", "v", "m", "py"): + with self.subTest(name=name): + actual = self.get_suggestion(obj, name) + self.assertNotIn("Did you mean", actual) + self.assertNotIn("'vvv", actual) + self.assertNotIn("'mom'", actual) + self.assertNotIn("'id'", actual) + self.assertNotIn("'w'", actual) + self.assertNotIn("'pytho'", actual) + + def test_do_not_trigger_for_big_dicts(self): + class A: + blech = None + # A class with a very big __dict__ will not be considered + # for suggestions. + for index in range(2000): + setattr(A, f"index_{index}", None) + + obj = A() + actual = self.get_suggestion(obj, 'bluch') + self.assertNotIn("blech", actual) + +class GetattrSuggestionTests(BaseSuggestionTests): def get_suggestion(self, obj, attr_name=None): if attr_name is not None: def callable(): @@ -4217,6 +4153,70 @@ def callable(): ) return result_lines[0] + def test_with_method_call(self): + # This is a placeholder method to make + # hasattr(self, 'test_with_method_call') return True + pass + + def test_suggestions_no_args(self): + class A: + blech = None + def __getattr__(self, attr): + raise AttributeError() + + actual = self.get_suggestion(A(), 'bluch') + self.assertIn("blech", actual) + + class A: + blech = None + def __getattr__(self, attr): + raise AttributeError + + actual = self.get_suggestion(A(), 'bluch') + self.assertIn("blech", actual) + + def test_suggestions_invalid_args(self): + class NonStringifyClass: + __str__ = None + __repr__ = None + + class A: + blech = None + def __getattr__(self, attr): + raise AttributeError(NonStringifyClass()) + + class B: + blech = None + def __getattr__(self, attr): + raise AttributeError("Error", 23) + + class C: + blech = None + def __getattr__(self, attr): + raise AttributeError(23) + + for cls in [A, B, C]: + actual = self.get_suggestion(cls(), 'bluch') + self.assertIn("blech", actual) + + def test_suggestions_for_same_name(self): + class A: + def __dir__(self): + return ['blech'] + actual = self.get_suggestion(A(), 'blech') + self.assertNotIn("Did you mean", actual) + +class DelattrSuggestionTests(BaseSuggestionTests): + def get_suggestion(self, obj, attr_name): + def callable(): + delattr(obj, attr_name) + + result_lines = self.get_exception( + callable, slice_start=-1, slice_end=None + ) + return result_lines[0] + +class SuggestionFormattingTestBase(SuggestionFormattingTestBaseParent): def test_attribute_error_with_failing_dict(self): class T: bluch = 1 @@ -4696,7 +4696,7 @@ class CPythonSuggestionFormattingTests( class PurePythonGetattrSuggestionFormattingTests( PurePythonExceptionFormattingMixin, - SuggestionFormattingTestBase.GetattrSuggestionTests, + GetattrSuggestionTests, unittest.TestCase, ): """ @@ -4707,7 +4707,7 @@ class PurePythonGetattrSuggestionFormattingTests( class PurePythonDelattrSuggestionFormattingTests( PurePythonExceptionFormattingMixin, - SuggestionFormattingTestBase.DelattrSuggestionTests, + DelattrSuggestionTests, unittest.TestCase, ): """ @@ -4719,7 +4719,7 @@ class PurePythonDelattrSuggestionFormattingTests( @cpython_only class CPythonGetattrSuggestionFormattingTests( CAPIExceptionFormattingMixin, - SuggestionFormattingTestBase.GetattrSuggestionTests, + GetattrSuggestionTests, unittest.TestCase, ): """ @@ -4730,7 +4730,7 @@ class CPythonGetattrSuggestionFormattingTests( @cpython_only class CPythonDelattrSuggestionFormattingTests( CAPIExceptionFormattingMixin, - SuggestionFormattingTestBase.DelattrSuggestionTests, + DelattrSuggestionTests, unittest.TestCase, ): """ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy