Preserve AttributeError in slotted classes with cached_property (#1253)
* Preserve AttributeError in slotted classes with cached_property In slotted classes' generated __getattr__(), we try __getattribute__() before __getattr__(), if available, and eventually let AttributeError propagate. This matches better with the behaviour described in Python's documentation "Customizing attribute access": https://docs.python.org/3/reference/datamodel.html#customizing-attribute-access Fix https://github.com/python-attrs/attrs/issues/1230 * Update changelog.d/1253.change.md --------- Co-authored-by: Hynek Schlawack <hs@ox.cx>
This commit is contained in:
parent
82a14627fd
commit
88e2896ca9
|
@ -0,0 +1 @@
|
|||
Preserve `AttributeError` raised by properties of slotted classes with `functools.cached_properties`.
|
|
@ -619,8 +619,12 @@ def _make_cached_property_getattr(cached_properties, original_getattr, cls):
|
|||
else:
|
||||
lines.extend(
|
||||
[
|
||||
" if hasattr(super(), '__getattr__'):",
|
||||
" return super().__getattr__(item)",
|
||||
" try:",
|
||||
" return super().__getattribute__(item)",
|
||||
" except AttributeError:",
|
||||
" if not hasattr(super(), '__getattr__'):",
|
||||
" raise",
|
||||
" return super().__getattr__(item)",
|
||||
" original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"",
|
||||
" raise AttributeError(original_error)",
|
||||
]
|
||||
|
|
|
@ -806,6 +806,45 @@ def test_slots_cached_property_with_empty_getattr_raises_attribute_error_of_requ
|
|||
a.z
|
||||
|
||||
|
||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
||||
def test_slots_cached_property_raising_attributeerror():
|
||||
"""
|
||||
Ensures AttributeError raised by a property is preserved by __getattr__()
|
||||
implementation.
|
||||
|
||||
Regression test for issue https://github.com/python-attrs/attrs/issues/1230
|
||||
"""
|
||||
|
||||
@attr.s(slots=True)
|
||||
class A:
|
||||
x = attr.ib()
|
||||
|
||||
@functools.cached_property
|
||||
def f(self):
|
||||
return self.p
|
||||
|
||||
@property
|
||||
def p(self):
|
||||
raise AttributeError("I am a property")
|
||||
|
||||
@functools.cached_property
|
||||
def g(self):
|
||||
return self.q
|
||||
|
||||
@property
|
||||
def q(self):
|
||||
return 2
|
||||
|
||||
a = A(1)
|
||||
with pytest.raises(AttributeError, match=r"^I am a property$"):
|
||||
a.p
|
||||
with pytest.raises(AttributeError, match=r"^I am a property$"):
|
||||
a.f
|
||||
|
||||
assert a.g == 2
|
||||
assert a.q == 2
|
||||
|
||||
|
||||
@pytest.mark.skipif(not PY_3_8_PLUS, reason="cached_property is 3.8+")
|
||||
def test_slots_cached_property_with_getattr_calls_getattr_for_missing_attributes():
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue