Change return type of root_instrument to Instrument#7924
Change return type of root_instrument to Instrument#7924jenshnielsen wants to merge 3 commits intomicrosoft:mainfrom
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7924 +/- ##
=======================================
Coverage 70.26% 70.26%
=======================================
Files 333 333
Lines 32169 32169
=======================================
Hits 22604 22604
Misses 9565 9565 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
269ad6e to
9fa2192
Compare
9fa2192 to
9fa7995
Compare
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Updates root_instrument typing/usage to better represent the “top-level instrument” concept and adjusts tests accordingly.
Changes:
- Changes
ParameterBase.root_instrumentreturn type toInstrument | None(with casting) and adjusts monitor typing usage. - Updates
InstrumentBase.root_instrumenttyping and adds/updates overrides in several drivers/channels. - Strengthens driver tests by asserting mocked
root_instrumentbehavior/types.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/drivers/keysight_b1500/b1500_driver_tests/test_sampling_measurement.py | Adds assertion that root_instrument.ask is mocked in tests. |
| tests/drivers/keysight_b1500/b1500_driver_tests/test_b1520a_cmu.py | Adds assertions that root_instrument is a MagicMock before using .ask. |
| tests/drivers/keysight_b1500/b1500_driver_tests/test_b1517a_smu.py | Adds assertion that root_instrument is a MagicMock before verifying .write calls. |
| src/qcodes/parameters/parameter_base.py | Changes root_instrument type to `Instrument |
| src/qcodes/monitor/monitor.py | Updates local typing for baseinst when using parameter.root_instrument. |
| src/qcodes/instrument_drivers/tektronix/DPO7200xx.py | Overrides root_instrument return type with type: ignore[override] and runtime assert. |
| src/qcodes/instrument_drivers/Keysight/N52xx.py | Same as above for Keysight PNA driver. |
| src/qcodes/instrument_drivers/Keysight/KtMAwg.py | Same as above for Keysight AWG driver. |
| src/qcodes/instrument_drivers/Keysight/Infiniium.py | Same as above for Keysight Infiniium driver. |
| src/qcodes/instrument/instrument_base.py | Changes root_instrument return type annotation to Self. |
| src/qcodes/instrument/channel.py | Overrides root_instrument return type to Instrument (via cast) with expanded comment. |
|
|
||
| @property | ||
| def root_instrument(self) -> InstrumentBase: | ||
| def root_instrument(self) -> Self: |
There was a problem hiding this comment.
The PR title/description says the return type of root_instrument is being changed to Instrument, but InstrumentBase.root_instrument is now annotated as returning Self. Self implies the return value is the same concrete type as self, which is not generally true for modules/channels where root_instrument typically returns the top-level Instrument (a different class than the module/channel). Consider adjusting the base annotation to a type that matches the actual contract (e.g., InstrumentBase or InstrumentBase + a narrower override on Instrument/channels), or update the PR description/title to reflect that this is a Self-typing change rather than an Instrument return-type change.
| # the root instrument is the top level parent of this module, we need to | ||
| # go up the parent hierarchy until we find an object that returns itself as the parent, this should be the root instrument. We also | ||
| # this is required to be an Instrument. | ||
| # Once 3.13 is the minimum supported version | ||
| # consider replacing with a generic parameter with a default | ||
| # value of Instrument. | ||
| return cast("Instrument", self._parent.root_instrument) | ||
|
|
There was a problem hiding this comment.
The comment describes walking the parent hierarchy to locate the root, but the implementation simply returns self._parent.root_instrument with a cast. This makes the comment misleading and also relies on cast (type-only) rather than a runtime check. Consider either (a) simplifying/updating the comment to match the implementation and invariants, and/or (b) using a runtime isinstance(..., Instrument) assertion (similar to the driver overrides in this PR) to enforce the contract that the channel’s root is an Instrument.
| # the root instrument is the top level parent of this module, we need to | |
| # go up the parent hierarchy until we find an object that returns itself as the parent, this should be the root instrument. We also | |
| # this is required to be an Instrument. | |
| # Once 3.13 is the minimum supported version | |
| # consider replacing with a generic parameter with a default | |
| # value of Instrument. | |
| return cast("Instrument", self._parent.root_instrument) | |
| # Delegate root resolution to the parent and enforce that the | |
| # resolved root instrument is an Instrument at runtime. | |
| # Once 3.13 is the minimum supported version | |
| # consider replacing with a generic parameter with a default | |
| # value of Instrument. | |
| from .instrument import Instrument | |
| root_instrument = self._parent.root_instrument | |
| assert isinstance(root_instrument, Instrument) | |
| return root_instrument |
| def root_instrument(self) -> "TektronixDPO7000xx": | ||
| def root_instrument(self) -> "TektronixDPO7000xx": # type: ignore[override] | ||
| # ideally this should be a generic type parameter but | ||
| # for now we override it here. This requies a mypy ignore |
There was a problem hiding this comment.
Corrected spelling of 'requies' to 'requires'.
| # for now we override it here. This requies a mypy ignore | |
| # for now we override it here. This requires a mypy ignore |
| def root_instrument(self) -> "KeysightPNABase": | ||
| def root_instrument(self) -> "KeysightPNABase": # type: ignore[override] | ||
| # ideally this should be a generic type parameter but | ||
| # for now we override it here. This requies a mypy ignore |
There was a problem hiding this comment.
Corrected spelling of 'requies' to 'requires'.
| # for now we override it here. This requies a mypy ignore | |
| # for now we override it here. This requires a mypy ignore |
| def root_instrument(self) -> "KeysightM9336A": | ||
| def root_instrument(self) -> "KeysightM9336A": # type: ignore[override] | ||
| # ideally this should be a generic type parameter but | ||
| # for now we override it here. This requies a mypy ignore |
There was a problem hiding this comment.
Corrected spelling of 'requies' to 'requires'.
| # for now we override it here. This requies a mypy ignore | |
| # for now we override it here. This requires a mypy ignore |
| def root_instrument(self) -> "KeysightInfiniium": | ||
| def root_instrument(self) -> "KeysightInfiniium": # type: ignore[override] | ||
| # ideally this should be a generic type parameter but | ||
| # for now we override it here. This requies a mypy ignore |
There was a problem hiding this comment.
Corrected spelling of 'requies' to 'requires'.
| # for now we override it here. This requies a mypy ignore | |
| # for now we override it here. This requires a mypy ignore |
The point of root_instrument is to get to the instrument.