Skip to content

Commit 173de45

Browse files
committed
gh-130399: Set .name and .obj on AttributeError raised by descriptor __set__ | _PyObject_GenericSetAttrWithDict called the descriptor's tp_descr_set but never called _PyObject_SetAttributeErrorContext on failure. Every other error path in the function calls that. Adding the call provides context so a read only property provides a 'did you mean'
1 parent a14a4a6 commit 173de45

3 files changed

Lines changed: 67 additions & 0 deletions

File tree

‎Lib/test/test_traceback.py‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4355,6 +4355,43 @@ class DelattrSuggestionTests(BaseSuggestionTests):
43554355
attr_function = delattr
43564356

43574357

4358+
class SetattrSuggestionTests(SuggestionFormattingTestMixin):
4359+
def get_suggestion(self, obj, attr_name=None):
4360+
if attr_name is not None:
4361+
def callable():
4362+
setattr(obj, attr_name, None)
4363+
else:
4364+
callable = obj
4365+
result_lines = self.get_exception(callable, slice_start=-1, slice_end=None)
4366+
return result_lines[0]
4367+
4368+
def test_property_no_setter_suggests_similar(self):
4369+
class A:
4370+
@property
4371+
def computed_a(self):
4372+
return 1
4373+
4374+
@property
4375+
def computed_b(self):
4376+
return 2
4377+
4378+
@computed_b.setter
4379+
def computed_b(self, val):
4380+
pass
4381+
4382+
actual = self.get_suggestion(A(), 'computed_a')
4383+
self.assertIn("Did you mean '.computed_b'", actual)
4384+
4385+
def test_property_no_setter_no_false_suggestion(self):
4386+
class A:
4387+
@property
4388+
def computed_a(self):
4389+
return 1
4390+
4391+
actual = self.get_suggestion(A(), 'computed_a')
4392+
self.assertNotIn("Did you mean", actual)
4393+
4394+
43584395
class SuggestionFormattingTestBase(SuggestionFormattingTestMixin):
43594396
def test_attribute_error_with_failing_dict(self):
43604397
class T:
@@ -5178,6 +5215,30 @@ class CPythonDelattrSuggestionFormattingTests(
51785215
internal traceback printing.
51795216
"""
51805217

5218+
5219+
class PurePythonSetattrSuggestionFormattingTests(
5220+
PurePythonExceptionFormattingMixin,
5221+
SetattrSuggestionTests,
5222+
unittest.TestCase,
5223+
):
5224+
"""
5225+
Tests for "Did you mean" suggestions when setting a read-only property,
5226+
using the pure Python traceback implementation.
5227+
"""
5228+
5229+
5230+
@cpython_only
5231+
class CPythonSetattrSuggestionFormattingTests(
5232+
CAPIExceptionFormattingMixin,
5233+
SetattrSuggestionTests,
5234+
unittest.TestCase,
5235+
):
5236+
"""
5237+
Same set of tests (for attribute setting) but with Python's internal
5238+
traceback printing.
5239+
"""
5240+
5241+
51815242
class MiscTest(unittest.TestCase):
51825243

51835244
def test_all(self):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:exc:`AttributeError` raised when setting a read-only property now includes
2+
"Did you mean" suggestions, matching the behaviour already present for
3+
attribute lookup and deletion.

‎Objects/object.c‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2061,6 +2061,9 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
20612061
f = Py_TYPE(descr)->tp_descr_set;
20622062
if (f != NULL) {
20632063
res = f(descr, obj, value);
2064+
if (res < 0) {
2065+
_PyObject_SetAttributeErrorContext(obj, name);
2066+
}
20642067
goto done;
20652068
}
20662069
}

0 commit comments

Comments
 (0)