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,7 +619,11 @@ def _make_cached_property_getattr(cached_properties, original_getattr, cls):
|
||||||
else:
|
else:
|
||||||
lines.extend(
|
lines.extend(
|
||||||
[
|
[
|
||||||
" if hasattr(super(), '__getattr__'):",
|
" try:",
|
||||||
|
" return super().__getattribute__(item)",
|
||||||
|
" except AttributeError:",
|
||||||
|
" if not hasattr(super(), '__getattr__'):",
|
||||||
|
" raise",
|
||||||
" return super().__getattr__(item)",
|
" return super().__getattr__(item)",
|
||||||
" original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"",
|
" original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"",
|
||||||
" raise AttributeError(original_error)",
|
" raise AttributeError(original_error)",
|
||||||
|
|
|
@ -806,6 +806,45 @@ def test_slots_cached_property_with_empty_getattr_raises_attribute_error_of_requ
|
||||||
a.z
|
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+")
|
@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():
|
def test_slots_cached_property_with_getattr_calls_getattr_for_missing_attributes():
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue