-
Notifications
You must be signed in to change notification settings - Fork 351
fix(utils): surface original errors from faulty instrument properties #8039
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| Errors raised inside a ``@property`` getter on a subclass of | ||
| :class:`~qcodes.utils.DelegateAttributes` (such as :class:`~qcodes.instrument.Instrument`) | ||
| are now surfaced with their original traceback, instead of being masked by a | ||
| generic ``AttributeError: ... object and its delegates have no attribute ...`` | ||
| message. The underlying cause of the failure is now visible in the traceback, | ||
| making misbehaving properties much easier to debug. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -180,3 +180,58 @@ class ToBoth(DelegateAttributes): | |||||||||||||||||
| # all appropriate items are in dir() exactly once | ||||||||||||||||||
| for attr in ["rock", "paper", "scissors", "year", "water"]: | ||||||||||||||||||
| assert dir(tb).count(attr) == 1 | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| def test_faulty_property_surfaces_original_attribute_error() -> None: | ||||||||||||||||||
| """A ``@property`` whose getter raises ``AttributeError`` should surface | ||||||||||||||||||
| the *inner* error instead of being masked by the generic | ||||||||||||||||||
| ``DelegateAttributes.__getattr__`` fallback. | ||||||||||||||||||
| """ | ||||||||||||||||||
|
|
||||||||||||||||||
| class WithFaultyProperty(DelegateAttributes): | ||||||||||||||||||
| @property | ||||||||||||||||||
| def prop(self) -> int: | ||||||||||||||||||
| # ``missing`` does not exist; accessing it raises AttributeError. | ||||||||||||||||||
| # Before this fix, Python fell through to ``__getattr__`` which | ||||||||||||||||||
| # reported "no attribute 'prop'" and hid the real cause. | ||||||||||||||||||
| return self.missing # type: ignore[attr-defined] | ||||||||||||||||||
|
|
||||||||||||||||||
| obj = WithFaultyProperty() | ||||||||||||||||||
| with pytest.raises(AttributeError, match="missing"): | ||||||||||||||||||
| obj.prop | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| def test_faulty_property_preserves_inner_traceback() -> None: | ||||||||||||||||||
| """The traceback of the surfaced error must include the line inside the | ||||||||||||||||||
| property's getter that actually raised, so downstream debugging is | ||||||||||||||||||
| possible. | ||||||||||||||||||
| """ | ||||||||||||||||||
|
|
||||||||||||||||||
| class WithFaultyProperty(DelegateAttributes): | ||||||||||||||||||
| @property | ||||||||||||||||||
| def prop(self) -> int: | ||||||||||||||||||
| raise AttributeError("specific underlying failure") | ||||||||||||||||||
|
|
||||||||||||||||||
| obj = WithFaultyProperty() | ||||||||||||||||||
| with pytest.raises(AttributeError, match="specific underlying failure"): | ||||||||||||||||||
| obj.prop | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
|
Comment on lines
+216
to
+219
|
||||||||||||||||||
| with pytest.raises(AttributeError, match="specific underlying failure"): | |
| obj.prop | |
| with pytest.raises( | |
| AttributeError, match="specific underlying failure" | |
| ) as excinfo: | |
| obj.prop | |
| assert any(entry.name == "prop" for entry in excinfo.traceback) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DelegateAttributes.__getattr__is on a hot path for instruments (e.g., resolving parameters/functions/submodules). Addinginspect.getattr_staticon every delegated attribute lookup may introduce measurable overhead. Consider a cheaper descriptor lookup (e.g., walkingtype(self).__mro__and checking__dict__directly) and/or caching per-class descriptor results to avoid repeated introspection.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@officialasishkumar I think this is a real concern. Would it be possible to rewrite the code such that we only use inspect as a fall back if the attr cannot be found by lookup in the delegate attrs?