diff --git a/src/qcodes/instrument/channel.py b/src/qcodes/instrument/channel.py index 95baf3d91ca5..e4dee120f82e 100644 --- a/src/qcodes/instrument/channel.py +++ b/src/qcodes/instrument/channel.py @@ -26,6 +26,7 @@ from typing_extensions import Unpack + from .instrument import Instrument from .instrument_base import InstrumentBaseKWArgs @@ -86,8 +87,14 @@ def parent(self) -> _TIB_co: return self._parent @property - def root_instrument(self) -> InstrumentBase: - return self._parent.root_instrument + def root_instrument(self) -> Instrument: # type: ignore[override] + # 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) @property def name_parts(self) -> list[str]: diff --git a/src/qcodes/instrument/instrument_base.py b/src/qcodes/instrument/instrument_base.py index 52894a2e7284..7e673798546b 100644 --- a/src/qcodes/instrument/instrument_base.py +++ b/src/qcodes/instrument/instrument_base.py @@ -18,7 +18,7 @@ if TYPE_CHECKING: from collections.abc import Callable, Mapping, Sequence - from typing import NotRequired + from typing import NotRequired, Self from qcodes.instrument.channel import ChannelTuple, InstrumentModule from qcodes.logger.instrument_logger import InstrumentLoggerAdapter @@ -579,7 +579,7 @@ def ancestors(self) -> tuple[InstrumentBase, ...]: return (self,) @property - def root_instrument(self) -> InstrumentBase: + def root_instrument(self) -> Self: """ The topmost parent of this module. diff --git a/src/qcodes/instrument_drivers/Keysight/Infiniium.py b/src/qcodes/instrument_drivers/Keysight/Infiniium.py index e411beed9cff..82fad5a67769 100644 --- a/src/qcodes/instrument_drivers/Keysight/Infiniium.py +++ b/src/qcodes/instrument_drivers/Keysight/Infiniium.py @@ -526,7 +526,10 @@ def __init__( ) @property - 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 + # because the return type is more specific than the parent class. root_instrument = super().root_instrument assert isinstance(root_instrument, KeysightInfiniium) return root_instrument diff --git a/src/qcodes/instrument_drivers/Keysight/KtMAwg.py b/src/qcodes/instrument_drivers/Keysight/KtMAwg.py index 54054e88a8a4..586e829aa36b 100644 --- a/src/qcodes/instrument_drivers/Keysight/KtMAwg.py +++ b/src/qcodes/instrument_drivers/Keysight/KtMAwg.py @@ -142,7 +142,10 @@ def __init__( """Parameter digital_gain""" @property - 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 + # because the return type is more specific than the parent class. root_instrument = super().root_instrument assert isinstance(root_instrument, KeysightM9336A) return root_instrument diff --git a/src/qcodes/instrument_drivers/Keysight/N52xx.py b/src/qcodes/instrument_drivers/Keysight/N52xx.py index d7c3e4cfddab..3bcc6d914ae9 100644 --- a/src/qcodes/instrument_drivers/Keysight/N52xx.py +++ b/src/qcodes/instrument_drivers/Keysight/N52xx.py @@ -300,7 +300,10 @@ def __init__( """Parameter polar""" @property - 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 + # because the return type is more specific than the parent class. root_instrument = super().root_instrument assert isinstance(root_instrument, KeysightPNABase) return root_instrument diff --git a/src/qcodes/instrument_drivers/tektronix/DPO7200xx.py b/src/qcodes/instrument_drivers/tektronix/DPO7200xx.py index 3c546d2c0173..ea5afdcb55b7 100644 --- a/src/qcodes/instrument_drivers/tektronix/DPO7200xx.py +++ b/src/qcodes/instrument_drivers/tektronix/DPO7200xx.py @@ -355,7 +355,10 @@ def __init__( """Parameter trace""" @property - 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 + # because the return type is more specific than the parent class. root_instrument = super().root_instrument assert isinstance(root_instrument, TektronixDPO7000xx) return root_instrument diff --git a/src/qcodes/monitor/monitor.py b/src/qcodes/monitor/monitor.py index e8bcf8f6a795..f5b06ec99a75 100644 --- a/src/qcodes/monitor/monitor.py +++ b/src/qcodes/monitor/monitor.py @@ -46,6 +46,8 @@ from websockets.asyncio.server import ServerConnection + from qcodes.instrument import InstrumentBase + WEBSOCKET_PORT = 5678 SERVER_PORT = 3000 @@ -78,7 +80,7 @@ def _get_metadata( # find the base instrument that this parameter belongs to if use_root_instrument: - baseinst = parameter.root_instrument + baseinst: InstrumentBase | None = parameter.root_instrument else: baseinst = parameter.instrument if baseinst is None: diff --git a/src/qcodes/parameters/parameter_base.py b/src/qcodes/parameters/parameter_base.py index 64d076848d9a..874920eb0e49 100644 --- a/src/qcodes/parameters/parameter_base.py +++ b/src/qcodes/parameters/parameter_base.py @@ -8,7 +8,7 @@ from contextlib import contextmanager from datetime import datetime from functools import cached_property, wraps -from typing import TYPE_CHECKING, Any, ClassVar, Generic, overload +from typing import TYPE_CHECKING, Any, ClassVar, Generic, cast, overload import numpy as np from typing_extensions import TypeVar @@ -46,7 +46,7 @@ from types import TracebackType from qcodes.dataset.data_set_protocol import ValuesType - from qcodes.instrument import InstrumentBase + from qcodes.instrument import Instrument, InstrumentBase from qcodes.logger.instrument_logger import InstrumentLoggerAdapter ParameterDataTypeVar = TypeVar("ParameterDataTypeVar", default=Any) # InstrumentTypeVar_co is a covariant type variable representing the instrument @@ -1080,7 +1080,7 @@ def instrument(self) -> InstrumentTypeVar_co: return self._instrument @property - def root_instrument(self) -> InstrumentBase | None: + def root_instrument(self) -> Instrument | None: """ Return the fundamental instrument that this parameter belongs too. E.g if the parameter is bound to a channel this will return the @@ -1088,7 +1088,7 @@ def root_instrument(self) -> InstrumentBase | None: :meth:`instrument` to get the channel. """ if self._instrument is not None: - return self._instrument.root_instrument + return cast("Instrument | None", self._instrument.root_instrument) else: return None diff --git a/tests/drivers/keysight_b1500/b1500_driver_tests/test_b1517a_smu.py b/tests/drivers/keysight_b1500/b1500_driver_tests/test_b1517a_smu.py index b66a514cceb4..4b728241117a 100644 --- a/tests/drivers/keysight_b1500/b1500_driver_tests/test_b1517a_smu.py +++ b/tests/drivers/keysight_b1500/b1500_driver_tests/test_b1517a_smu.py @@ -433,7 +433,7 @@ def test_iv_sweep_delay(smu: KeysightB1517A) -> None: smu.iv_sweep.step_delay(0.01) smu.iv_sweep.trigger_delay(0.1) smu.iv_sweep.measure_delay(15.4) - + assert isinstance(mainframe, MagicMock) mainframe.write.assert_has_calls( [ call("WT 43.12,0.0,0.0,0.0,0.0"), diff --git a/tests/drivers/keysight_b1500/b1500_driver_tests/test_b1520a_cmu.py b/tests/drivers/keysight_b1500/b1500_driver_tests/test_b1520a_cmu.py index 4a5532f5d2c7..bdbecb2ded33 100644 --- a/tests/drivers/keysight_b1500/b1500_driver_tests/test_b1520a_cmu.py +++ b/tests/drivers/keysight_b1500/b1500_driver_tests/test_b1520a_cmu.py @@ -179,7 +179,7 @@ def test_get_post_sweep_voltage_cond(cmu: KeysightB1520A) -> None: def test_cv_sweep_delay(cmu: KeysightB1520A) -> None: mainframe = cmu.root_instrument - + assert isinstance(mainframe, MagicMock) mainframe.ask.return_value = "WTDCV0.0,0.0,0.0,0.0,0.0" cmu.cv_sweep.hold_time(1.0) @@ -192,6 +192,7 @@ def test_cv_sweep_delay(cmu: KeysightB1520A) -> None: def test_cmu_sweep_steps(cmu: KeysightB1520A) -> None: mainframe = cmu.root_instrument + assert isinstance(mainframe, MagicMock) mainframe.ask.return_value = "WDCV3,1,0.0,0.0,1" cmu.cv_sweep.sweep_start(2.0) cmu.cv_sweep.sweep_end(4.0) @@ -208,6 +209,7 @@ def test_cv_sweep_voltages(cmu: KeysightB1520A) -> None: end = 1.0 steps = 5 return_string = f"WDCV3,1,{start},{end},{steps}" + assert isinstance(mainframe, MagicMock) mainframe.ask.return_value = return_string cmu.cv_sweep.sweep_start(start) @@ -226,6 +228,7 @@ def test_sweep_modes(cmu: KeysightB1520A) -> None: steps = 5 mode = constants.SweepMode.LINEAR_TWO_WAY return_string = f"WDCV3,{mode},{start},{end},{steps}" + assert isinstance(mainframe, MagicMock) mainframe.ask.return_value = return_string cmu.cv_sweep.sweep_start(start) @@ -249,6 +252,7 @@ def test_run_sweep(cmu: KeysightB1520A) -> None: f"0.0000;WDCV3," f"1,{start},{end},{steps};ACT0,1" ) + assert isinstance(mainframe, MagicMock) mainframe.ask.return_value = return_string cmu.setup_fnc_already_run = True cmu.impedance_model(constants.IMP.MeasurementMode.G_X) diff --git a/tests/drivers/keysight_b1500/b1500_driver_tests/test_sampling_measurement.py b/tests/drivers/keysight_b1500/b1500_driver_tests/test_sampling_measurement.py index 973d29f139e7..4758b5d0388f 100644 --- a/tests/drivers/keysight_b1500/b1500_driver_tests/test_sampling_measurement.py +++ b/tests/drivers/keysight_b1500/b1500_driver_tests/test_sampling_measurement.py @@ -83,6 +83,7 @@ def test_sampling_measurement( actual_data = smu.sampling_measurement_trace.get() np.testing.assert_allclose(actual_data, data_to_return, atol=1e-3) + assert isinstance(smu.root_instrument.ask, Mock) smu.root_instrument.ask.assert_called_with("XE")