From 8a5ba4e619009fd98bc893a67e07c688eb8d4c8d Mon Sep 17 00:00:00 2001 From: "Tobias Hangleiter (Valhalla)" Date: Thu, 9 May 2024 16:16:04 +0200 Subject: [PATCH 1/8] Accept sequences of values for setting. --- .../multi_channel_instrument_parameter.py | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/qcodes/parameters/multi_channel_instrument_parameter.py b/src/qcodes/parameters/multi_channel_instrument_parameter.py index 137fafd619b..c466cc3282a 100644 --- a/src/qcodes/parameters/multi_channel_instrument_parameter.py +++ b/src/qcodes/parameters/multi_channel_instrument_parameter.py @@ -1,5 +1,6 @@ from __future__ import annotations +import logging from typing import TYPE_CHECKING, Any, Generic, TypeVar from .multi_parameter import MultiParameter @@ -12,6 +13,7 @@ from .parameter_base import ParamRawDataType InstrumentModuleType = TypeVar("InstrumentModuleType", bound="InstrumentModule") +LOG = logging.getLogger(__name__) class MultiChannelInstrumentParameter(MultiParameter, Generic[InstrumentModuleType]): @@ -45,7 +47,7 @@ def get_raw(self) -> tuple[ParamRawDataType, ...]: """ return tuple(chan.parameters[self._param_name].get() for chan in self._channels) - def set_raw(self, value: ParamRawDataType) -> None: + def set_raw(self, value: ParamRawDataType | Sequence[ParamRawDataType]) -> None: """ Set all parameters to this value. @@ -53,8 +55,23 @@ def set_raw(self, value: ParamRawDataType) -> None: value: The value to set to. The type is given by the underlying parameter. """ - for chan in self._channels: - getattr(chan, self._param_name).set(value) + try: + for chan in self._channels: + getattr(chan, self._param_name).set(value) + except Exception as err: + try: + for chan, val in zip(self._channels, value, strict=True): + getattr(chan, self._param_name).set(val) + except (TypeError, ValueError): + note = ('Value should either be valid for a single parameter of the channel list ' + 'or a sequence of valid values of the same length as the list.') + try: + err.add_note(note) + except AttributeError: + # <3.11 + LOG.error(note) + finally: + raise @property def full_names(self) -> tuple[str, ...]: From 7df971cfc9bb7e1b63e2973d476c1676040847ac Mon Sep 17 00:00:00 2001 From: Tobias Hangleiter Date: Wed, 15 May 2024 14:53:06 +0200 Subject: [PATCH 2/8] adjust docstring --- src/qcodes/parameters/multi_channel_instrument_parameter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qcodes/parameters/multi_channel_instrument_parameter.py b/src/qcodes/parameters/multi_channel_instrument_parameter.py index c466cc3282a..d1900b9ac56 100644 --- a/src/qcodes/parameters/multi_channel_instrument_parameter.py +++ b/src/qcodes/parameters/multi_channel_instrument_parameter.py @@ -49,10 +49,10 @@ def get_raw(self) -> tuple[ParamRawDataType, ...]: def set_raw(self, value: ParamRawDataType | Sequence[ParamRawDataType]) -> None: """ - Set all parameters to this value. + Set all parameters to this/these value(s). Args: - value: The value to set to. The type is given by the + value: The value(s) to set to. The type is given by the underlying parameter. """ try: From 1adb32f4197ac233a139d270789c8ee3f756c36a Mon Sep 17 00:00:00 2001 From: Tobias Hangleiter Date: Sun, 19 May 2024 12:20:22 +0200 Subject: [PATCH 3/8] Make LOG private --- src/qcodes/parameters/multi_channel_instrument_parameter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qcodes/parameters/multi_channel_instrument_parameter.py b/src/qcodes/parameters/multi_channel_instrument_parameter.py index d1900b9ac56..fc40be8352e 100644 --- a/src/qcodes/parameters/multi_channel_instrument_parameter.py +++ b/src/qcodes/parameters/multi_channel_instrument_parameter.py @@ -13,7 +13,7 @@ from .parameter_base import ParamRawDataType InstrumentModuleType = TypeVar("InstrumentModuleType", bound="InstrumentModule") -LOG = logging.getLogger(__name__) +_LOG = logging.getLogger(__name__) class MultiChannelInstrumentParameter(MultiParameter, Generic[InstrumentModuleType]): @@ -69,7 +69,7 @@ def set_raw(self, value: ParamRawDataType | Sequence[ParamRawDataType]) -> None: err.add_note(note) except AttributeError: # <3.11 - LOG.error(note) + _LOG.error(note) finally: raise From 4f0a2039fd8428787fd209bf054b40c9b436cf8b Mon Sep 17 00:00:00 2001 From: Tobias Hangleiter Date: Sun, 19 May 2024 13:10:34 +0200 Subject: [PATCH 4/8] Darker --- src/qcodes/parameters/multi_channel_instrument_parameter.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/qcodes/parameters/multi_channel_instrument_parameter.py b/src/qcodes/parameters/multi_channel_instrument_parameter.py index fc40be8352e..38275d13620 100644 --- a/src/qcodes/parameters/multi_channel_instrument_parameter.py +++ b/src/qcodes/parameters/multi_channel_instrument_parameter.py @@ -63,8 +63,10 @@ def set_raw(self, value: ParamRawDataType | Sequence[ParamRawDataType]) -> None: for chan, val in zip(self._channels, value, strict=True): getattr(chan, self._param_name).set(val) except (TypeError, ValueError): - note = ('Value should either be valid for a single parameter of the channel list ' - 'or a sequence of valid values of the same length as the list.') + note = ( + "Value should either be valid for a single parameter of the channel list " + "or a sequence of valid values of the same length as the list." + ) try: err.add_note(note) except AttributeError: From b3185dac517d67e266e71ffd0ffdcf7204e4e434 Mon Sep 17 00:00:00 2001 From: Tobias Hangleiter Date: Tue, 21 May 2024 10:27:22 +0200 Subject: [PATCH 5/8] Fix type check --- .../parameters/multi_channel_instrument_parameter.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/qcodes/parameters/multi_channel_instrument_parameter.py b/src/qcodes/parameters/multi_channel_instrument_parameter.py index 38275d13620..e9f9594754d 100644 --- a/src/qcodes/parameters/multi_channel_instrument_parameter.py +++ b/src/qcodes/parameters/multi_channel_instrument_parameter.py @@ -1,6 +1,7 @@ from __future__ import annotations import logging +import sys from typing import TYPE_CHECKING, Any, Generic, TypeVar from .multi_parameter import MultiParameter @@ -67,13 +68,11 @@ def set_raw(self, value: ParamRawDataType | Sequence[ParamRawDataType]) -> None: "Value should either be valid for a single parameter of the channel list " "or a sequence of valid values of the same length as the list." ) - try: + if sys.version_info >= (3, 11): err.add_note(note) - except AttributeError: - # <3.11 + else: _LOG.error(note) - finally: - raise + raise @property def full_names(self) -> tuple[str, ...]: From b654c3492915576fe641f9b04c8b80f3c7986c75 Mon Sep 17 00:00:00 2001 From: Tobias Hangleiter Date: Tue, 21 May 2024 10:48:28 +0200 Subject: [PATCH 6/8] Catch more possible failure modes before setting --- .../parameters/multi_channel_instrument_parameter.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/qcodes/parameters/multi_channel_instrument_parameter.py b/src/qcodes/parameters/multi_channel_instrument_parameter.py index e9f9594754d..df0a47cb041 100644 --- a/src/qcodes/parameters/multi_channel_instrument_parameter.py +++ b/src/qcodes/parameters/multi_channel_instrument_parameter.py @@ -61,7 +61,11 @@ def set_raw(self, value: ParamRawDataType | Sequence[ParamRawDataType]) -> None: getattr(chan, self._param_name).set(value) except Exception as err: try: - for chan, val in zip(self._channels, value, strict=True): + # Catch wrong length of value before any setting is done + value_list = list(value) + if len(value_list) != len(self._channels): + raise ValueError + for chan, val in zip(self._channels, value_list): getattr(chan, self._param_name).set(val) except (TypeError, ValueError): note = ( @@ -72,7 +76,7 @@ def set_raw(self, value: ParamRawDataType | Sequence[ParamRawDataType]) -> None: err.add_note(note) else: _LOG.error(note) - raise + raise err from None @property def full_names(self) -> tuple[str, ...]: From 87d603b12945458a5cbc539fa9b4ba4165103e23 Mon Sep 17 00:00:00 2001 From: Tobias Hangleiter Date: Fri, 24 May 2024 16:13:50 +0200 Subject: [PATCH 7/8] Add test --- ...test_multi_channel_instrument_parameter.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/parameter/test_multi_channel_instrument_parameter.py diff --git a/tests/parameter/test_multi_channel_instrument_parameter.py b/tests/parameter/test_multi_channel_instrument_parameter.py new file mode 100644 index 00000000000..161bd3372c7 --- /dev/null +++ b/tests/parameter/test_multi_channel_instrument_parameter.py @@ -0,0 +1,53 @@ +import sys +from typing import TYPE_CHECKING + +import pytest + +from qcodes.instrument_drivers.mock_instruments import DummyChannelInstrument + +if TYPE_CHECKING: + from collections.abc import Generator + + +@pytest.fixture +def dummy_channel_instrument() -> "Generator[DummyChannelInstrument, None, None]": + instrument = DummyChannelInstrument(name="testdummy") + try: + yield instrument + finally: + instrument.close() + + +@pytest.fixture +def assert_raises_match() -> str: + if sys.version_info >= (3, 11): + return "Value should either be valid" + else: + return "" + + +def test_set_multi_channel_instrument_parameter( + dummy_channel_instrument: DummyChannelInstrument, assert_raises_match: str +): + """Tests :class:`MultiChannelInstrumentParameter` set method.""" + for name, param in dummy_channel_instrument.channels[0].parameters.items(): + if not param.settable: + continue + + channel_parameter = getattr(dummy_channel_instrument.channels, name) + + getval = channel_parameter.get() + + channel_parameter.set(getval[0]) + + # Assert channel parameter setters accept what the getter returns (PR #6073) + channel_parameter.set(getval) + + with pytest.raises(TypeError, match=assert_raises_match): + channel_parameter.set(getval[:-1]) + + with pytest.raises(TypeError, match=assert_raises_match): + channel_parameter.set(getval + (getval[-1],)) + + with pytest.raises(TypeError): + channel_parameter.set(object()) From 65069d25ea9ff6a3450a188a5e6cd0695e6244a1 Mon Sep 17 00:00:00 2001 From: Tobias Hangleiter Date: Fri, 24 May 2024 16:17:53 +0200 Subject: [PATCH 8/8] Add newsfragment --- docs/changes/newsfragments/6073.improved | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/changes/newsfragments/6073.improved diff --git a/docs/changes/newsfragments/6073.improved b/docs/changes/newsfragments/6073.improved new file mode 100644 index 00000000000..00934ea9a5e --- /dev/null +++ b/docs/changes/newsfragments/6073.improved @@ -0,0 +1 @@ +Accept sequences of values for setting `MultiChannelInstrumentParameter` s. Previously, the behavior was inconsistent since `param.set(param.get())` would error.