From b862017a2352f2c70d37aa45caf9cd1e6b27f5ce Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Sat, 3 Feb 2024 10:55:12 +0100 Subject: [PATCH 01/21] enable standard mode for pyright --- pyproject.toml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6a1e7ef154d..c21e03eb19d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -209,13 +209,8 @@ ignore = [ reportMissingTypeStubs = true reportDeprecated = true stubPath = "typings/stubs" -# we would like to move this to at least standard -# eventually. From 1.1.339 onwards standard is the default -# for now we enable all of standard except for -# incompatibleMethodOverride which we have a lot of -typeCheckingMode = "standard" -reportIncompatibleMethodOverride = false +typeCheckingMode = "standard" [tool.pytest.ini_options] minversion = "7.2" From 398727f7eda2217c59d3ad08d5d375adf52f6430 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Sat, 3 Feb 2024 10:56:45 +0100 Subject: [PATCH 02/21] InstrumentModule/Channel rename args to match ABC This is in principle a breaking change but seems very unlikely that these inputs would be passed by kwarg --- src/qcodes/instrument/channel.py | 38 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/qcodes/instrument/channel.py b/src/qcodes/instrument/channel.py index 3a55b2d218f..8943d7d8f51 100644 --- a/src/qcodes/instrument/channel.py +++ b/src/qcodes/instrument/channel.py @@ -302,7 +302,7 @@ def name_parts(self) -> list[str]: return name_parts def index( - self, obj: InstrumentModuleType, start: int = 0, stop: int = sys.maxsize + self, value: InstrumentModuleType, start: int = 0, stop: int = sys.maxsize ) -> int: """ Return the index of the given object @@ -312,15 +312,15 @@ def index( start: Index to start searching from. stop: Index to stop searching at. """ - return self._channels.index(obj, start, stop) + return self._channels.index(value, start, stop) - def count(self, obj: InstrumentModuleType) -> int: + def count(self, value: InstrumentModuleType) -> int: """Returns number of instances of the given object in the list Args: obj: The object to find in the ChannelTuple. """ - return self._channels.count(obj) + return self._channels.count(value) def get_channel_by_name(self: T, *names: str) -> InstrumentModuleType | T: """ @@ -625,7 +625,7 @@ def __setitem__( channel.short_name: channel for channel in self._channels } - def append(self, obj: InstrumentModuleType) -> None: + def append(self, value: InstrumentModuleType) -> None: """ Append a Channel to this list. Requires that the ChannelList is not locked and that the channel is of the same type as the ones in the list. @@ -635,14 +635,14 @@ def append(self, obj: InstrumentModuleType) -> None: """ if self._locked: raise AttributeError("Cannot append to a locked channel list") - if not isinstance(obj, self._chan_type): + if not isinstance(value, self._chan_type): raise TypeError( f"All items in a channel list must be of the same " - f"type. Adding {type(obj).__name__} to a " + f"type. Adding {type(value).__name__} to a " f"list of {self._chan_type.__name__}." ) - self._channel_mapping[obj.short_name] = obj - self._channels.append(obj) + self._channel_mapping[value.short_name] = value + self._channels.append(value) def clear(self) -> None: """ @@ -654,7 +654,7 @@ def clear(self) -> None: self._channels.clear() self._channel_mapping.clear() - def remove(self, obj: InstrumentModuleType) -> None: + def remove(self, value: InstrumentModuleType) -> None: """ Removes obj from ChannelList if not locked. @@ -664,10 +664,10 @@ def remove(self, obj: InstrumentModuleType) -> None: if self._locked: raise AttributeError("Cannot remove from a locked channel list") else: - self._channels.remove(obj) - self._channel_mapping.pop(obj.short_name) + self._channels.remove(value) + self._channel_mapping.pop(value.short_name) - def extend(self, objects: Iterable[InstrumentModuleType]) -> None: + def extend(self, values: Iterable[InstrumentModuleType]) -> None: """ Insert an iterable of objects into the list of channels. @@ -679,13 +679,13 @@ def extend(self, objects: Iterable[InstrumentModuleType]) -> None: # below so copy it into a tuple just in case. if self._locked: raise AttributeError("Cannot extend a locked channel list") - objects_tuple = tuple(objects) + objects_tuple = tuple(values) if not all(isinstance(obj, self._chan_type) for obj in objects_tuple): raise TypeError("All items in a channel list must be of the same type.") self._channels.extend(objects_tuple) self._channel_mapping.update({obj.short_name: obj for obj in objects_tuple}) - def insert(self, index: int, obj: InstrumentModuleType) -> None: + def insert(self, index: int, value: InstrumentModuleType) -> None: """ Insert an object into the ChannelList at a specific index. @@ -695,13 +695,13 @@ def insert(self, index: int, obj: InstrumentModuleType) -> None: """ if self._locked: raise AttributeError("Cannot insert into a locked channel list") - if not isinstance(obj, self._chan_type): + if not isinstance(value, self._chan_type): raise TypeError( f"All items in a channel list must be of the same " - f"type. Adding {type(obj).__name__} to a list of {self._chan_type.__name__}." + f"type. Adding {type(value).__name__} to a list of {self._chan_type.__name__}." ) - self._channels.insert(index, obj) - self._channel_mapping[obj.short_name] = obj + self._channels.insert(index, value) + self._channel_mapping[value.short_name] = value def get_validator(self) -> ChannelTupleValidator: """ From 85add0c6a5dd0f1f6be793f8576151bae4077bbd Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Sat, 3 Feb 2024 10:58:11 +0100 Subject: [PATCH 03/21] NumpyJSONEncoder update argument name to match parrent class This is a breaking change but seems very unlikly to be called by directly --- src/qcodes/utils/json_utils.py | 42 +++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/qcodes/utils/json_utils.py b/src/qcodes/utils/json_utils.py index f8bde2a95c6..45147667544 100644 --- a/src/qcodes/utils/json_utils.py +++ b/src/qcodes/utils/json_utils.py @@ -13,7 +13,7 @@ class NumpyJSONEncoder(json.JSONEncoder): ``default`` method for the description of all conversions. """ - def default(self, obj: Any) -> Any: + def default(self, o: Any) -> Any: """ List of conversions that this encoder performs: @@ -41,50 +41,50 @@ def default(self, obj: Any) -> Any: """ import uncertainties # type: ignore[import-untyped] - if isinstance(obj, np.generic) and not isinstance(obj, np.complexfloating): + if isinstance(o, np.generic) and not isinstance(o, np.complexfloating): # for numpy scalars - return obj.item() - elif isinstance(obj, np.ndarray): + return o.item() + elif isinstance(o, np.ndarray): # for numpy arrays - return obj.tolist() - elif isinstance(obj, numbers.Complex) and not isinstance(obj, numbers.Real): + return o.tolist() + elif isinstance(o, numbers.Complex) and not isinstance(o, numbers.Real): return { "__dtype__": "complex", - "re": float(obj.real), - "im": float(obj.imag), + "re": float(o.real), + "im": float(o.imag), } - elif isinstance(obj, uncertainties.UFloat): + elif isinstance(o, uncertainties.UFloat): return { "__dtype__": "UFloat", - "nominal_value": float(obj.nominal_value), - "std_dev": float(obj.std_dev), + "nominal_value": float(o.nominal_value), + "std_dev": float(o.std_dev), } - elif hasattr(obj, "_JSONEncoder"): + elif hasattr(o, "_JSONEncoder"): # Use object's custom JSON encoder - jsosencode = getattr(obj, "_JSONEncoder") + jsosencode = getattr(o, "_JSONEncoder") return jsosencode() else: try: - s = super().default(obj) + s = super().default(o) except TypeError: # json does not support dumping UserDict but # we can dump the dict stored internally in the # UserDict - if isinstance(obj, collections.UserDict): - return obj.data + if isinstance(o, collections.UserDict): + return o.data # See if the object supports the pickle protocol. # If so, we should be able to use that to serialize. # __getnewargs__ will return bytes for a bytes object # causing an infinte recursion, so we do not # try to pickle bytes or bytearrays - if hasattr(obj, "__getnewargs__") and not isinstance( - obj, (bytes, bytearray) + if hasattr(o, "__getnewargs__") and not isinstance( + o, (bytes, bytearray) ): return { - "__class__": type(obj).__name__, - "__args__": getattr(obj, "__getnewargs__")(), + "__class__": type(o).__name__, + "__args__": getattr(o, "__getnewargs__")(), } else: # we cannot convert the object to JSON, just take a string - s = str(obj) + s = str(o) return s From cfeece64e8d1b043c784eeb05b0e92d1af4c54f7 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Sat, 3 Feb 2024 10:59:32 +0100 Subject: [PATCH 04/21] rename stub method / unused args to match parrent class These are breaking changes but since the arguments had no effect unlikely to be an issue --- src/qcodes/instrument_drivers/Keysight/Infiniium.py | 4 ++-- src/qcodes/instrument_drivers/Keysight/N52xx.py | 2 +- src/qcodes/instrument_drivers/QDev/QDac_channels.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/qcodes/instrument_drivers/Keysight/Infiniium.py b/src/qcodes/instrument_drivers/Keysight/Infiniium.py index 327a2abb59c..cef27f21930 100644 --- a/src/qcodes/instrument_drivers/Keysight/Infiniium.py +++ b/src/qcodes/instrument_drivers/Keysight/Infiniium.py @@ -135,7 +135,7 @@ def setpoints(self) -> Sequence[ParameterBase]: raise RuntimeError("Invalid type for parent instrument.") @setpoints.setter - def setpoints(self, val: Any) -> None: + def setpoints(self, setpoints: Any) -> None: """ Stub to allow initialization. Ignore any set attempts on setpoint as we figure it out on the fly. @@ -157,7 +157,7 @@ def unit(self) -> str: return "''" @unit.setter - def unit(self, val: Any) -> None: + def unit(self, unit: Any) -> None: """ Stub to allow initialization. """ diff --git a/src/qcodes/instrument_drivers/Keysight/N52xx.py b/src/qcodes/instrument_drivers/Keysight/N52xx.py index c8cd6e810a6..dfdde9c9cc4 100644 --- a/src/qcodes/instrument_drivers/Keysight/N52xx.py +++ b/src/qcodes/instrument_drivers/Keysight/N52xx.py @@ -97,7 +97,7 @@ def setpoints(self) -> Sequence[ParameterBase]: raise NotImplementedError(f"Axis for type {sweep_type} not implemented yet") @setpoints.setter - def setpoints(self, val: Any) -> None: + def setpoints(self, setpoints: Any) -> None: """ Stub to allow initialization. Ignore any set attempts on setpoint as we figure it out on the fly. diff --git a/src/qcodes/instrument_drivers/QDev/QDac_channels.py b/src/qcodes/instrument_drivers/QDev/QDac_channels.py index bbd5ab2a2e2..2609d1659c7 100644 --- a/src/qcodes/instrument_drivers/QDev/QDac_channels.py +++ b/src/qcodes/instrument_drivers/QDev/QDac_channels.py @@ -687,9 +687,9 @@ def _wait_and_clear(self, delay: float = 0.5) -> None: time.sleep(delay) self.visa_handle.clear() - def connect_message(self, - idn_part: str = "IDN", - being_time: Optional[float] = None) -> None: + def connect_message( + self, idn_param: str = "IDN", begin_time: Optional[float] = None + ) -> None: """ Override of the standard Instrument class connect_message. Usually, the response to `*IDN?` is printed. Here, the From 43502d947565abb3a8e0f6fbc9c9287f78773dc6 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Sat, 3 Feb 2024 11:00:07 +0100 Subject: [PATCH 05/21] Silence warnings about incorrect specilization --- .../instrument_drivers/Keysight/Keysight_N9030B.py | 9 +++++++-- src/qcodes/instrument_drivers/Keysight/KtM960x.py | 7 ++++++- .../Keysight/keysightb1500/KeysightB1500_base.py | 12 +++++++++--- .../KeysightB1500_sampling_measurement.py | 12 +++++++++--- .../Keysight/keysightb1500/KeysightB1520A.py | 12 +++++++++--- .../Keysight/private/Keysight_344xxA_submodules.py | 6 ++++-- 6 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/qcodes/instrument_drivers/Keysight/Keysight_N9030B.py b/src/qcodes/instrument_drivers/Keysight/Keysight_N9030B.py index 7f713f7c307..afbf73203cc 100644 --- a/src/qcodes/instrument_drivers/Keysight/Keysight_N9030B.py +++ b/src/qcodes/instrument_drivers/Keysight/Keysight_N9030B.py @@ -41,10 +41,15 @@ def get_raw(self) -> ParamRawDataType: class Trace(ParameterWithSetpoints): def __init__(self, number: int, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - self.instrument: ( + # the parameter classes should ideally be generic in instrument + # and root instrument classes so we can specialize here. + # for now we have to ignore a type error from pyright + self.instrument: ( # pyright: ignore[reportIncompatibleMethodOverride] KeysightN9030BSpectrumAnalyzerMode | KeysightN9030BPhaseNoiseMode ) - self.root_instrument: KeysightN9030B + self.root_instrument: ( # pyright: ignore[reportIncompatibleMethodOverride] + KeysightN9030B + ) self.number = number diff --git a/src/qcodes/instrument_drivers/Keysight/KtM960x.py b/src/qcodes/instrument_drivers/Keysight/KtM960x.py index dd326510fc1..e93e4468373 100644 --- a/src/qcodes/instrument_drivers/Keysight/KtM960x.py +++ b/src/qcodes/instrument_drivers/Keysight/KtM960x.py @@ -23,7 +23,12 @@ def __init__(self, name: str, instrument: "KeysightM960x") -> None: instrument=instrument, labels="Measurement Data", docstring="param that returns measurement values") - self.instrument: "KeysightM960x" + # the parameter classes should ideally be generic in instrument + # and root instrument classes so we can specialize here. + # for now we have to ignore a type error from pyright + self.instrument: ( # pyright: ignore[reportIncompatibleMethodOverride] + "KeysightM960x" + ) def get_raw(self) -> tuple[ParamRawDataType, ...]: return self.instrument._measure() diff --git a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_base.py b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_base.py index bdfd59ac8a2..814d5aeb225 100644 --- a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_base.py +++ b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_base.py @@ -461,9 +461,15 @@ def __init__(self, name: str, instrument: KeysightB1517A, **kwargs: Any): setpoint_units=(('V',),) * 2, instrument=instrument, **kwargs) - - self.instrument: KeysightB1517A - self.root_instrument: KeysightB1500 + # the parameter classes should ideally be generic in instrument + # and root instrument classes so we can specialize here. + # for now we have to ignore a type error from pyright + self.instrument: ( # pyright: ignore[reportIncompatibleMethodOverride] + KeysightB1517A + ) + self.root_instrument: ( # pyright: ignore[reportIncompatibleMethodOverride] + KeysightB1500 + ) self.param1 = _FMTResponse(None, None, None, None) self.param2 = _FMTResponse(None, None, None, None) diff --git a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_sampling_measurement.py b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_sampling_measurement.py index a911350e12c..21c626bc8ca 100644 --- a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_sampling_measurement.py +++ b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_sampling_measurement.py @@ -36,9 +36,15 @@ class SamplingMeasurement(ParameterWithSetpoints): def __init__(self, name: str, **kwargs: Any): super().__init__(name, **kwargs) - - self.instrument: "KeysightB1517A" - self.root_instrument: "KeysightB1500" + # the parameter classes should ideally be generic in instrument + # and root instrument classes so we can specialize here. + # for now we have to ignore a type error from pyright + self.instrument: ( # pyright: ignore[reportIncompatibleMethodOverride] + "KeysightB1517A" + ) + self.root_instrument: ( # pyright: ignore[reportIncompatibleMethodOverride] + "KeysightB1500" + ) self.data = _FMTResponse(None, None, None, None) diff --git a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1520A.py b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1520A.py index a4be1cd45b3..3dc7def8d0f 100644 --- a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1520A.py +++ b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1520A.py @@ -884,9 +884,15 @@ def __init__(self, name: str, instrument: KeysightB1520A, **kwargs: Any): setpoint_units=(('V',),) * 2, instrument=instrument, **kwargs) - - self.instrument: "KeysightB1520A" - self.root_instrument: "KeysightB1500" + # the parameter classes should ideally be generic in instrument + # and root instrument classes so we can specialize here. + # for now we have to ignore a type error from pyright + self.instrument: ( # pyright: ignore[reportIncompatibleMethodOverride] + "KeysightB1520A" + ) + self.root_instrument: ( # pyright: ignore[reportIncompatibleMethodOverride] + "KeysightB1500" + ) self.update_name_label_unit_from_impedance_model() diff --git a/src/qcodes/instrument_drivers/Keysight/private/Keysight_344xxA_submodules.py b/src/qcodes/instrument_drivers/Keysight/private/Keysight_344xxA_submodules.py index 46fc76eec6d..a00c5c000b3 100644 --- a/src/qcodes/instrument_drivers/Keysight/private/Keysight_344xxA_submodules.py +++ b/src/qcodes/instrument_drivers/Keysight/private/Keysight_344xxA_submodules.py @@ -320,8 +320,10 @@ class TimeTrace(ParameterWithSetpoints): """ def __init__(self, name: str, instrument: Instrument, **kwargs: Any): - - self.instrument: Instrument # needed for mypy + # the parameter classes should ideally be generic in instrument + # and root instrument classes so we can specialize here. + # for now we have to ignore a type error from pyright + self.instrument: Instrument # pyright: ignore[reportIncompatibleMethodOverride] super().__init__(name=name, instrument=instrument, **kwargs) # the extra time needed to avoid timeouts during acquisition From d4c7e81673498fdc33c66bb6a97959919343e551 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Sat, 3 Feb 2024 11:00:43 +0100 Subject: [PATCH 06/21] Alazar rename to match parent class --- .../AlazarTech/ATS_acquisition_controllers.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py b/src/qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py index 25bd40b20b4..51fa8b9071a 100644 --- a/src/qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py +++ b/src/qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py @@ -95,16 +95,14 @@ def pre_acquire(self) -> None: pass def handle_buffer( - self, - data: np.ndarray, - buffer_number: Optional[int] = None + self, buffer: np.ndarray, buffer_number: Optional[int] = None ) -> None: """ See AcquisitionController :return: """ assert self.buffer is not None - self.buffer += data + self.buffer += buffer def post_acquire(self) -> float: """ From fac811f88270b55f235c4df59070ac7b281187c7 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Sat, 3 Feb 2024 11:01:08 +0100 Subject: [PATCH 07/21] alazar ignore a warning about one argument made explicit --- src/qcodes/instrument_drivers/AlazarTech/dll_wrapper.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/qcodes/instrument_drivers/AlazarTech/dll_wrapper.py b/src/qcodes/instrument_drivers/AlazarTech/dll_wrapper.py index 9e53c700d8c..0cd914cbaf4 100644 --- a/src/qcodes/instrument_drivers/AlazarTech/dll_wrapper.py +++ b/src/qcodes/instrument_drivers/AlazarTech/dll_wrapper.py @@ -104,7 +104,9 @@ class DllWrapperMeta(type): # Only allow a single instance per DLL path. _instances: WeakValueDictionary[str, Any] = WeakValueDictionary() - def __call__(cls, dll_path: str, *args: Any, **kwargs: Any) -> Any: + def __call__( # pyright: ignore[reportIncompatibleMethodOverride] + cls, dll_path: str, *args: Any, **kwargs: Any + ) -> Any: api = cls._instances.get(dll_path, None) if api is not None: logger.debug( From ac545c654339468037b392aa041681e3c7005a77 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Sat, 3 Feb 2024 11:06:57 +0100 Subject: [PATCH 08/21] make get_raw a proper method on the instrument (expect when using Command --- .../instrument_drivers/oxford/MercuryiPS_VISA.py | 14 +++++++------- src/qcodes/parameters/parameter.py | 7 ++++--- src/qcodes/parameters/parameter_base.py | 8 ++++---- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/qcodes/instrument_drivers/oxford/MercuryiPS_VISA.py b/src/qcodes/instrument_drivers/oxford/MercuryiPS_VISA.py index d1680fe3824..ce9b2cc58c8 100644 --- a/src/qcodes/instrument_drivers/oxford/MercuryiPS_VISA.py +++ b/src/qcodes/instrument_drivers/oxford/MercuryiPS_VISA.py @@ -256,10 +256,6 @@ def __init__(self, name: str, address: str, visalib: Optional[str] = None, super().__init__(name, address, terminator='\n', visalib=visalib, **kwargs) - - # to ensure a correct snapshot, we must wrap the get function - self.IDN.get = self.IDN._wrap_get(self._idn_getter) - self.firmware = self.IDN()['firmware'] # TODO: Query instrument to ensure which PSUs are actually present @@ -401,7 +397,7 @@ def _set_target_field(self, field: FieldVector) -> None: for coord in 'xyz': self._set_target(coord, field[coord]) - def _idn_getter(self) -> dict[str, str]: + def get_idn(self) -> dict[str, str | None]: """ Parse the raw non-SCPI compliant IDN string into an IDN dict @@ -411,8 +407,12 @@ def _idn_getter(self) -> dict[str, str]: raw_idn_string = self.ask('*IDN?') resps = raw_idn_string.split(':') - idn_dict = {'model': resps[2], 'vendor': resps[1], - 'serial': resps[3], 'firmware': resps[4]} + idn_dict: dict[str, str | None] = { + "model": resps[2], + "vendor": resps[1], + "serial": resps[3], + "firmware": resps[4], + } return idn_dict diff --git a/src/qcodes/parameters/parameter.py b/src/qcodes/parameters/parameter.py index 81b563bf798..8aba2f9ddd0 100644 --- a/src/qcodes/parameters/parameter.py +++ b/src/qcodes/parameters/parameter.py @@ -5,6 +5,7 @@ import logging import os +from types import MethodType from typing import TYPE_CHECKING, Any, Callable, Literal from .command import Command @@ -180,7 +181,7 @@ def __init__( bind_to_instrument: bool = True, **kwargs: Any, ) -> None: - def _get_manual_parameter() -> ParamRawDataType: + def _get_manual_parameter(self: Parameter) -> ParamRawDataType: if self.root_instrument is not None: mylogger: InstrumentLoggerAdapter | logging.Logger = ( self.root_instrument.log @@ -263,7 +264,7 @@ def _set_manual_parameter(x: ParamRawDataType) -> ParamRawDataType: ) elif not self.gettable and get_cmd is not False: if get_cmd is None: - self.get_raw: Callable[[], Any] = _get_manual_parameter + self.get_raw = MethodType(_get_manual_parameter, self) else: if isinstance(get_cmd, str) and instrument is None: raise TypeError( @@ -280,7 +281,7 @@ def _set_manual_parameter(x: ParamRawDataType) -> ParamRawDataType: exec_str=exec_str_ask, ) self._gettable = True - self.get = self._wrap_get(self.get_raw) + self.get = self._wrap_get() if self.settable and set_cmd not in (None, False): raise TypeError( diff --git a/src/qcodes/parameters/parameter_base.py b/src/qcodes/parameters/parameter_base.py index de8299c7f33..4bd813a8d49 100644 --- a/src/qcodes/parameters/parameter_base.py +++ b/src/qcodes/parameters/parameter_base.py @@ -257,7 +257,7 @@ def __init__( ) self._gettable = False if implements_get_raw: - self.get = self._wrap_get(self.get_raw) + self.get = self._wrap_get() self._gettable = True elif hasattr(self, "get"): raise RuntimeError( @@ -646,9 +646,9 @@ def _from_raw_value_to_value(self, raw_value: ParamRawDataType) -> ParamDataType return value def _wrap_get( - self, get_function: Callable[..., ParamDataType] + self, ) -> Callable[..., ParamDataType]: - @wraps(get_function) + @wraps(self.get_raw) def get_wrapper(*args: Any, **kwargs: Any) -> ParamDataType: if not self.gettable: raise TypeError("Trying to get a parameter that is not gettable.") @@ -658,7 +658,7 @@ def get_wrapper(*args: Any, **kwargs: Any) -> ParamDataType: ) try: # There might be cases where a .get also has args/kwargs - raw_value = get_function(*args, **kwargs) + raw_value = self.get_raw(*args, **kwargs) value = self._from_raw_value_to_value(raw_value) From c401df387d825508670b77b3b6147a8bf24df3ef Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Sat, 3 Feb 2024 11:07:37 +0100 Subject: [PATCH 09/21] fix incompatibe override in test fixtures --- src/qcodes/tests/dataset/conftest.py | 8 +++++--- tests/dataset/conftest.py | 9 +++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/qcodes/tests/dataset/conftest.py b/src/qcodes/tests/dataset/conftest.py index 9057aa9bc65..ff8e107afc6 100644 --- a/src/qcodes/tests/dataset/conftest.py +++ b/src/qcodes/tests/dataset/conftest.py @@ -575,7 +575,7 @@ def SpectrumAnalyzer(): different types """ - class Spectrum(ArrayParameter): + class BaseSpectrum(ArrayParameter): def __init__(self, name, instrument, **kwargs): super().__init__( @@ -593,6 +593,8 @@ def __init__(self, name, instrument, **kwargs): self.start = 0 self.stop = 2e6 + class Spectrum(BaseSpectrum): + def get_raw(self): # This is how it should be: the setpoints are generated at the # time we get. But that will of course not work with the old Loop @@ -630,13 +632,13 @@ def __init__(self, name, instrument, **kwargs): def get_raw(self): return np.random.randn(*self.npts) - class ListSpectrum(Spectrum): + class ListSpectrum(BaseSpectrum): def get_raw(self): output = super().get_raw() return list(output) - class TupleSpectrum(Spectrum): + class TupleSpectrum(BaseSpectrum): def get_raw(self): output = super().get_raw() diff --git a/tests/dataset/conftest.py b/tests/dataset/conftest.py index 9057aa9bc65..2c968b43151 100644 --- a/tests/dataset/conftest.py +++ b/tests/dataset/conftest.py @@ -575,7 +575,7 @@ def SpectrumAnalyzer(): different types """ - class Spectrum(ArrayParameter): + class BaseSpectrum(ArrayParameter): def __init__(self, name, instrument, **kwargs): super().__init__( @@ -588,11 +588,12 @@ def __init__(self, name, instrument, **kwargs): instrument=instrument, **kwargs, ) - self.npts = 100 self.start = 0 self.stop = 2e6 + class Spectrum(BaseSpectrum): + def get_raw(self): # This is how it should be: the setpoints are generated at the # time we get. But that will of course not work with the old Loop @@ -630,13 +631,13 @@ def __init__(self, name, instrument, **kwargs): def get_raw(self): return np.random.randn(*self.npts) - class ListSpectrum(Spectrum): + class ListSpectrum(BaseSpectrum): def get_raw(self): output = super().get_raw() return list(output) - class TupleSpectrum(Spectrum): + class TupleSpectrum(BaseSpectrum): def get_raw(self): output = super().get_raw() From a8f53d50903d52c903be7c387d822bf2220d9b1d Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Sat, 3 Feb 2024 11:11:02 +0100 Subject: [PATCH 10/21] silence mypy errors about overriding methods --- src/qcodes/parameters/parameter.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/qcodes/parameters/parameter.py b/src/qcodes/parameters/parameter.py index 8aba2f9ddd0..d3d31c615c3 100644 --- a/src/qcodes/parameters/parameter.py +++ b/src/qcodes/parameters/parameter.py @@ -264,7 +264,8 @@ def _set_manual_parameter(x: ParamRawDataType) -> ParamRawDataType: ) elif not self.gettable and get_cmd is not False: if get_cmd is None: - self.get_raw = MethodType(_get_manual_parameter, self) + # mypy does not allow setting a method + self.get_raw = MethodType(_get_manual_parameter, self) # type: ignore[method-assign] else: if isinstance(get_cmd, str) and instrument is None: raise TypeError( @@ -275,7 +276,8 @@ def _set_manual_parameter(x: ParamRawDataType) -> ParamRawDataType: exec_str_ask = getattr(instrument, "ask", None) if instrument else None - self.get_raw = Command( + # mypy does not allow setting a method + self.get_raw = Command( # type: ignore[method-assign] arg_count=0, cmd=get_cmd, exec_str=exec_str_ask, From 0df5ba91805f01e6d5e585a3ef726254fffc12fd Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Sat, 3 Feb 2024 11:13:52 +0100 Subject: [PATCH 11/21] add some todos --- src/qcodes/parameters/parameter.py | 6 +++++- src/qcodes/parameters/parameter_base.py | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/qcodes/parameters/parameter.py b/src/qcodes/parameters/parameter.py index d3d31c615c3..b965514cfc8 100644 --- a/src/qcodes/parameters/parameter.py +++ b/src/qcodes/parameters/parameter.py @@ -205,6 +205,8 @@ def _set_manual_parameter(x: ParamRawDataType) -> ParamRawDataType: mylogger.debug( "Setting raw value of parameter: %s to %s", self.full_name, x ) + # TODO this really should set the cache via raw value + # at the moment set_raw does nothing on a manual parameter return x if instrument is not None and bind_to_instrument: @@ -275,7 +277,7 @@ def _set_manual_parameter(x: ParamRawDataType) -> ParamRawDataType: ) exec_str_ask = getattr(instrument, "ask", None) if instrument else None - + # TODO this should be a method like above # mypy does not allow setting a method self.get_raw = Command( # type: ignore[method-assign] arg_count=0, @@ -292,6 +294,7 @@ def _set_manual_parameter(x: ParamRawDataType) -> ParamRawDataType: " set_raw is an error." ) elif not self.settable and set_cmd is not False: + # TODO We should also wrap this with a MethodType like get above if set_cmd is None: self.set_raw: Callable[..., Any] = _set_manual_parameter else: @@ -305,6 +308,7 @@ def _set_manual_parameter(x: ParamRawDataType) -> ParamRawDataType: exec_str_write = ( getattr(instrument, "write", None) if instrument else None ) + # TODO this should also be a method self.set_raw = Command( arg_count=1, cmd=set_cmd, exec_str=exec_str_write ) diff --git a/src/qcodes/parameters/parameter_base.py b/src/qcodes/parameters/parameter_base.py index 4bd813a8d49..b1834e0d076 100644 --- a/src/qcodes/parameters/parameter_base.py +++ b/src/qcodes/parameters/parameter_base.py @@ -675,6 +675,7 @@ def get_wrapper(*args: Any, **kwargs: Any) -> ParamDataType: return get_wrapper + # TODO should be updated like _get_wrapper def _wrap_set(self, set_function: Callable[..., None]) -> Callable[..., None]: @wraps(set_function) def set_wrapper(value: ParamDataType, **kwargs: Any) -> None: From 16639cfc1c76080ca4f3b07bac033f37ca0c79f6 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Sat, 3 Feb 2024 11:32:22 +0100 Subject: [PATCH 12/21] Fix issue in fixtures --- src/qcodes/tests/dataset/conftest.py | 12 +++++++----- tests/dataset/conftest.py | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/qcodes/tests/dataset/conftest.py b/src/qcodes/tests/dataset/conftest.py index ff8e107afc6..f638577d0ef 100644 --- a/src/qcodes/tests/dataset/conftest.py +++ b/src/qcodes/tests/dataset/conftest.py @@ -593,9 +593,7 @@ def __init__(self, name, instrument, **kwargs): self.start = 0 self.stop = 2e6 - class Spectrum(BaseSpectrum): - - def get_raw(self): + def get_data(self): # This is how it should be: the setpoints are generated at the # time we get. But that will of course not work with the old Loop self.setpoints = (tuple(np.linspace(self.start, self.stop, @@ -603,6 +601,10 @@ def get_raw(self): # not the best SA on the market; it just returns noise... return np.random.randn(self.npts) + class Spectrum(BaseSpectrum): + def get_raw(self): + return super().get_data() + class MultiDimSpectrum(ArrayParameter): def __init__(self, name, instrument, **kwargs): @@ -635,13 +637,13 @@ def get_raw(self): class ListSpectrum(BaseSpectrum): def get_raw(self): - output = super().get_raw() + output = super().get_data() return list(output) class TupleSpectrum(BaseSpectrum): def get_raw(self): - output = super().get_raw() + output = super().get_data() return tuple(output) SA = DummyInstrument('dummy_SA') diff --git a/tests/dataset/conftest.py b/tests/dataset/conftest.py index 2c968b43151..499b444de30 100644 --- a/tests/dataset/conftest.py +++ b/tests/dataset/conftest.py @@ -592,9 +592,7 @@ def __init__(self, name, instrument, **kwargs): self.start = 0 self.stop = 2e6 - class Spectrum(BaseSpectrum): - - def get_raw(self): + def get_data(self): # This is how it should be: the setpoints are generated at the # time we get. But that will of course not work with the old Loop self.setpoints = (tuple(np.linspace(self.start, self.stop, @@ -602,6 +600,10 @@ def get_raw(self): # not the best SA on the market; it just returns noise... return np.random.randn(self.npts) + class Spectrum(BaseSpectrum): + def get_raw(self): + return super().get_data() + class MultiDimSpectrum(ArrayParameter): def __init__(self, name, instrument, **kwargs): @@ -634,13 +636,13 @@ def get_raw(self): class ListSpectrum(BaseSpectrum): def get_raw(self): - output = super().get_raw() + output = super().get_data() return list(output) class TupleSpectrum(BaseSpectrum): def get_raw(self): - output = super().get_raw() + output = super().get_data() return tuple(output) SA = DummyInstrument('dummy_SA') From 7a30459ca96f92a2f3de24efa4028c9ac9512340 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Sat, 3 Feb 2024 11:34:08 +0100 Subject: [PATCH 13/21] restore python 3.9 compatibility --- .../oxford/MercuryiPS_VISA.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/qcodes/instrument_drivers/oxford/MercuryiPS_VISA.py b/src/qcodes/instrument_drivers/oxford/MercuryiPS_VISA.py index ce9b2cc58c8..0d88c43dc22 100644 --- a/src/qcodes/instrument_drivers/oxford/MercuryiPS_VISA.py +++ b/src/qcodes/instrument_drivers/oxford/MercuryiPS_VISA.py @@ -1,7 +1,9 @@ +from __future__ import annotations + import logging import time from functools import partial -from typing import Any, Callable, Optional, Union, cast +from typing import Any, Callable, cast import numpy as np import numpy.typing as npt @@ -190,7 +192,7 @@ def _param_getter(self, get_cmd: str) -> str: return resp - def _param_setter(self, set_cmd: str, value: Union[float, str]) -> None: + def _param_setter(self, set_cmd: str, value: float | str) -> None: """ General setter function for parameters @@ -219,11 +221,14 @@ class OxfordMercuryiPS(VisaInstrument): supply """ - def __init__(self, name: str, address: str, visalib: Optional[str] = None, - field_limits: Optional[Callable[[float, - float, - float], bool]] = None, - **kwargs: Any) -> None: + def __init__( + self, + name: str, + address: str, + visalib: str | None = None, + field_limits: Callable[[float, float, float], bool] | None = None, + **kwargs: Any, + ) -> None: """ Args: name: The name to give this instrument internally in QCoDeS @@ -347,7 +352,7 @@ def _set_ramp_rate(self, rate: FieldVector) -> None: self.GRPY.field_ramp_rate(rate.y) self.GRPZ.field_ramp_rate(rate.z) - def _get_measured(self, coordinates: list[str]) -> Union[float, list[float]]: + def _get_measured(self, coordinates: list[str]) -> float | list[float]: """ Get the measured value of a coordinate. Measures all three fields and computes whatever coordinate we asked for. From 646f1cda90124c441e9b1b65bbd0ae863d17516a Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Sun, 4 Feb 2024 07:32:57 +0100 Subject: [PATCH 14/21] make set / set_raw propper methods --- src/qcodes/parameters/parameter.py | 11 ++++++----- src/qcodes/parameters/parameter_base.py | 11 ++++++----- tests/parameter/test_get_latest.py | 2 +- tests/parameter/test_parameter_cache.py | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/qcodes/parameters/parameter.py b/src/qcodes/parameters/parameter.py index b965514cfc8..2410b6921bf 100644 --- a/src/qcodes/parameters/parameter.py +++ b/src/qcodes/parameters/parameter.py @@ -195,7 +195,9 @@ def _get_manual_parameter(self: Parameter) -> ParamRawDataType: ) return self.cache.raw_value - def _set_manual_parameter(x: ParamRawDataType) -> ParamRawDataType: + def _set_manual_parameter( + self: Parameter, x: ParamRawDataType + ) -> ParamRawDataType: if self.root_instrument is not None: mylogger: InstrumentLoggerAdapter | logging.Logger = ( self.root_instrument.log @@ -294,9 +296,8 @@ def _set_manual_parameter(x: ParamRawDataType) -> ParamRawDataType: " set_raw is an error." ) elif not self.settable and set_cmd is not False: - # TODO We should also wrap this with a MethodType like get above if set_cmd is None: - self.set_raw: Callable[..., Any] = _set_manual_parameter + self.set_raw = MethodType(_set_manual_parameter, self) # type: ignore[method-assign] else: if isinstance(set_cmd, str) and instrument is None: raise TypeError( @@ -309,11 +310,11 @@ def _set_manual_parameter(x: ParamRawDataType) -> ParamRawDataType: getattr(instrument, "write", None) if instrument else None ) # TODO this should also be a method - self.set_raw = Command( + self.set_raw = Command( # type: ignore[method-assign] arg_count=1, cmd=set_cmd, exec_str=exec_str_write ) self._settable = True - self.set = self._wrap_set(self.set_raw) + self.set = self._wrap_set() self._meta_attrs.extend(["label", "unit", "vals"]) diff --git a/src/qcodes/parameters/parameter_base.py b/src/qcodes/parameters/parameter_base.py index b1834e0d076..0cfaa89dde7 100644 --- a/src/qcodes/parameters/parameter_base.py +++ b/src/qcodes/parameters/parameter_base.py @@ -272,7 +272,7 @@ def __init__( ) self._settable: bool = False if implements_set_raw: - self.set = self._wrap_set(self.set_raw) + self.set = self._wrap_set() self._settable = True elif hasattr(self, "set"): raise RuntimeError( @@ -675,9 +675,10 @@ def get_wrapper(*args: Any, **kwargs: Any) -> ParamDataType: return get_wrapper - # TODO should be updated like _get_wrapper - def _wrap_set(self, set_function: Callable[..., None]) -> Callable[..., None]: - @wraps(set_function) + def _wrap_set( + self, + ) -> Callable[..., None]: + @wraps(self.set_raw) def set_wrapper(value: ParamDataType, **kwargs: Any) -> None: try: if not self.settable: @@ -710,7 +711,7 @@ def set_wrapper(value: ParamDataType, **kwargs: Any) -> None: # Start timer to measure execution time of set_function t0 = time.perf_counter() - set_function(raw_val_step, **kwargs) + self.set_raw(raw_val_step, **kwargs) # Update last set time (used for calculating delays) self._t_last_set = time.perf_counter() diff --git a/tests/parameter/test_get_latest.py b/tests/parameter/test_get_latest.py index bef50531bc4..f0763c8087f 100644 --- a/tests/parameter/test_get_latest.py +++ b/tests/parameter/test_get_latest.py @@ -170,7 +170,7 @@ class LocalParameter(ParameterBase): def __init__(self, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) self.set_raw = lambda x: x # type: ignore[method-assign] - self.set = self._wrap_set(self.set_raw) + self.set = self._wrap_set() localparameter = LocalParameter('test_param', None, diff --git a/tests/parameter/test_parameter_cache.py b/tests/parameter/test_parameter_cache.py index 918fa2793c9..bf473a8b8bb 100644 --- a/tests/parameter/test_parameter_cache.py +++ b/tests/parameter/test_parameter_cache.py @@ -230,7 +230,7 @@ class LocalParameter(ParameterBase): def __init__(self, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) self.set_raw = lambda x: x # type: ignore[method-assign] - self.set = self._wrap_set(self.set_raw) + self.set = self._wrap_set() local_parameter = LocalParameter('test_param', None, From 20decd43722437fa690f071f0dee55b43663a89a Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Sun, 4 Feb 2024 07:46:41 +0100 Subject: [PATCH 15/21] Ensure that set_raw sets the value of a manual parameter --- src/qcodes/parameters/parameter.py | 3 +-- tests/parameter/test_manual_parameter.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 tests/parameter/test_manual_parameter.py diff --git a/src/qcodes/parameters/parameter.py b/src/qcodes/parameters/parameter.py index 2410b6921bf..59d1fda32a2 100644 --- a/src/qcodes/parameters/parameter.py +++ b/src/qcodes/parameters/parameter.py @@ -207,8 +207,7 @@ def _set_manual_parameter( mylogger.debug( "Setting raw value of parameter: %s to %s", self.full_name, x ) - # TODO this really should set the cache via raw value - # at the moment set_raw does nothing on a manual parameter + self.cache._set_from_raw_value(x) return x if instrument is not None and bind_to_instrument: diff --git a/tests/parameter/test_manual_parameter.py b/tests/parameter/test_manual_parameter.py new file mode 100644 index 00000000000..48790f99b59 --- /dev/null +++ b/tests/parameter/test_manual_parameter.py @@ -0,0 +1,19 @@ +from qcodes.parameters import Parameter + + +def test_manual_parameter_set_get_raw() -> None: + myparam = Parameter("myparam", set_cmd=None, get_cmd=None) + + value = 23 + value2 = 132 + + assert myparam.get() is None + assert myparam.get_raw() is None + + myparam.set_raw(value) + assert myparam.get() == value + assert myparam.get_raw() == value + + myparam.set(value2) + assert myparam.get() == value2 + assert myparam.get_raw() == value2 From 5ce10eb463571d4a9aaeb1476aaad0539808652c Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Mon, 26 Feb 2024 11:03:04 +0100 Subject: [PATCH 16/21] typecheck with pyright 1.1.351 --- .github/workflows/pytest.yaml | 2 +- src/qcodes/dataset/legacy_import.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 5d9f2d68a3a..eb62a78d761 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -83,7 +83,7 @@ jobs: if: ${{ !matrix.min-version }} - uses: jakebailey/pyright-action@b8ffdb7aa4a15fab942303261611eaa541f3f8b0 # v2.2.1 with: - version: 1.1.349 + version: 1.1.351 if: ${{ !matrix.min-version }} - name: Run Mypy run: mypy -p qcodes diff --git a/src/qcodes/dataset/legacy_import.py b/src/qcodes/dataset/legacy_import.py index a07746f1521..fb7f48c7fd6 100644 --- a/src/qcodes/dataset/legacy_import.py +++ b/src/qcodes/dataset/legacy_import.py @@ -58,7 +58,9 @@ def store_array_to_database(datasaver: DataSaver, array: DataArray) -> int: datasaver.add_result((array.set_arrays[0].array_id, i), (array.array_id, array[index])) else: - raise NotImplementedError('The exporter only currently handles 1 and 2 Dimentional data') + raise NotImplementedError( + "The exporter only currently handles 1 and 2 Dimensional data" + ) return datasaver.run_id @@ -67,7 +69,9 @@ def store_array_to_database_alt(meas: Measurement, array: DataArray) -> int: dims = len(array.shape) assert array.array_id is not None if dims == 2: - outer_data = np.empty(array.shape[1]) + outer_data = np.empty( + array.shape[1] # pyright: ignore[reportGeneralTypeIssues] + ) with meas.run() as datasaver: for index1, i in enumerate(array.set_arrays[0]): outer_data[:] = i @@ -80,7 +84,9 @@ def store_array_to_database_alt(meas: Measurement, array: DataArray) -> int: datasaver.add_result((array.set_arrays[0].array_id, i), (array.array_id, array[index])) else: - raise NotImplementedError('The exporter only currently handles 1 and 2 Dimentional data') + raise NotImplementedError( + "The exporter only currently handles 1 and 2 Dimensional data" + ) return datasaver.run_id From 5129d95b66a49f45a5915a1a03232be5a2d7e28e Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Mon, 26 Feb 2024 11:38:15 +0100 Subject: [PATCH 17/21] update docstrings to reflect change --- src/qcodes/instrument/channel.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/qcodes/instrument/channel.py b/src/qcodes/instrument/channel.py index 8943d7d8f51..82cb6ce0b90 100644 --- a/src/qcodes/instrument/channel.py +++ b/src/qcodes/instrument/channel.py @@ -308,7 +308,7 @@ def index( Return the index of the given object Args: - obj: The object to find in the channel list. + value: The object to find in the channel list. start: Index to start searching from. stop: Index to stop searching at. """ @@ -318,7 +318,7 @@ def count(self, value: InstrumentModuleType) -> int: """Returns number of instances of the given object in the list Args: - obj: The object to find in the ChannelTuple. + value: The object to find in the ChannelTuple. """ return self._channels.count(value) @@ -631,7 +631,7 @@ def append(self, value: InstrumentModuleType) -> None: locked and that the channel is of the same type as the ones in the list. Args: - obj: New channel to add to the list. + value: New channel to add to the list. """ if self._locked: raise AttributeError("Cannot append to a locked channel list") @@ -659,7 +659,7 @@ def remove(self, value: InstrumentModuleType) -> None: Removes obj from ChannelList if not locked. Args: - obj: Channel to remove from the list. + value: Channel to remove from the list. """ if self._locked: raise AttributeError("Cannot remove from a locked channel list") @@ -691,7 +691,7 @@ def insert(self, index: int, value: InstrumentModuleType) -> None: Args: index: Index to insert object. - obj: Object of type chan_type to insert. + value: Object of type chan_type to insert. """ if self._locked: raise AttributeError("Cannot insert into a locked channel list") From f9fa4fff45df627a4612d2076a4e235e4d0dd0d4 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Mon, 26 Feb 2024 11:40:29 +0100 Subject: [PATCH 18/21] Clarify todo --- src/qcodes/parameters/parameter.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/qcodes/parameters/parameter.py b/src/qcodes/parameters/parameter.py index 59d1fda32a2..8ad522a8703 100644 --- a/src/qcodes/parameters/parameter.py +++ b/src/qcodes/parameters/parameter.py @@ -267,7 +267,7 @@ def _set_manual_parameter( ) elif not self.gettable and get_cmd is not False: if get_cmd is None: - # mypy does not allow setting a method + # ignore typeerror since mypy does not allow setting a method dynamically self.get_raw = MethodType(_get_manual_parameter, self) # type: ignore[method-assign] else: if isinstance(get_cmd, str) and instrument is None: @@ -278,8 +278,9 @@ def _set_manual_parameter( ) exec_str_ask = getattr(instrument, "ask", None) if instrument else None - # TODO this should be a method like above - # mypy does not allow setting a method + # TODO get_raw should also be a method here. This should probably be done by wrapping + # it with MethodType like above + # ignore typeerror since mypy does not allow setting a method dynamically self.get_raw = Command( # type: ignore[method-assign] arg_count=0, cmd=get_cmd, @@ -296,6 +297,7 @@ def _set_manual_parameter( ) elif not self.settable and set_cmd is not False: if set_cmd is None: + # ignore typeerror since mypy does not allow setting a method dynamically self.set_raw = MethodType(_set_manual_parameter, self) # type: ignore[method-assign] else: if isinstance(set_cmd, str) and instrument is None: @@ -308,7 +310,9 @@ def _set_manual_parameter( exec_str_write = ( getattr(instrument, "write", None) if instrument else None ) - # TODO this should also be a method + # TODO get_raw should also be a method here. This should probably be done by wrapping + # it with MethodType like above + # ignore typeerror since mypy does not allow setting a method dynamically self.set_raw = Command( # type: ignore[method-assign] arg_count=1, cmd=set_cmd, exec_str=exec_str_write ) From d9ec9168b314cb779349b086b5959412fbab1235 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Mon, 4 Mar 2024 09:55:12 +0100 Subject: [PATCH 19/21] revert changes to channel --- src/qcodes/instrument/channel.py | 48 ++++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/qcodes/instrument/channel.py b/src/qcodes/instrument/channel.py index 82cb6ce0b90..3a55b2d218f 100644 --- a/src/qcodes/instrument/channel.py +++ b/src/qcodes/instrument/channel.py @@ -302,25 +302,25 @@ def name_parts(self) -> list[str]: return name_parts def index( - self, value: InstrumentModuleType, start: int = 0, stop: int = sys.maxsize + self, obj: InstrumentModuleType, start: int = 0, stop: int = sys.maxsize ) -> int: """ Return the index of the given object Args: - value: The object to find in the channel list. + obj: The object to find in the channel list. start: Index to start searching from. stop: Index to stop searching at. """ - return self._channels.index(value, start, stop) + return self._channels.index(obj, start, stop) - def count(self, value: InstrumentModuleType) -> int: + def count(self, obj: InstrumentModuleType) -> int: """Returns number of instances of the given object in the list Args: - value: The object to find in the ChannelTuple. + obj: The object to find in the ChannelTuple. """ - return self._channels.count(value) + return self._channels.count(obj) def get_channel_by_name(self: T, *names: str) -> InstrumentModuleType | T: """ @@ -625,24 +625,24 @@ def __setitem__( channel.short_name: channel for channel in self._channels } - def append(self, value: InstrumentModuleType) -> None: + def append(self, obj: InstrumentModuleType) -> None: """ Append a Channel to this list. Requires that the ChannelList is not locked and that the channel is of the same type as the ones in the list. Args: - value: New channel to add to the list. + obj: New channel to add to the list. """ if self._locked: raise AttributeError("Cannot append to a locked channel list") - if not isinstance(value, self._chan_type): + if not isinstance(obj, self._chan_type): raise TypeError( f"All items in a channel list must be of the same " - f"type. Adding {type(value).__name__} to a " + f"type. Adding {type(obj).__name__} to a " f"list of {self._chan_type.__name__}." ) - self._channel_mapping[value.short_name] = value - self._channels.append(value) + self._channel_mapping[obj.short_name] = obj + self._channels.append(obj) def clear(self) -> None: """ @@ -654,20 +654,20 @@ def clear(self) -> None: self._channels.clear() self._channel_mapping.clear() - def remove(self, value: InstrumentModuleType) -> None: + def remove(self, obj: InstrumentModuleType) -> None: """ Removes obj from ChannelList if not locked. Args: - value: Channel to remove from the list. + obj: Channel to remove from the list. """ if self._locked: raise AttributeError("Cannot remove from a locked channel list") else: - self._channels.remove(value) - self._channel_mapping.pop(value.short_name) + self._channels.remove(obj) + self._channel_mapping.pop(obj.short_name) - def extend(self, values: Iterable[InstrumentModuleType]) -> None: + def extend(self, objects: Iterable[InstrumentModuleType]) -> None: """ Insert an iterable of objects into the list of channels. @@ -679,29 +679,29 @@ def extend(self, values: Iterable[InstrumentModuleType]) -> None: # below so copy it into a tuple just in case. if self._locked: raise AttributeError("Cannot extend a locked channel list") - objects_tuple = tuple(values) + objects_tuple = tuple(objects) if not all(isinstance(obj, self._chan_type) for obj in objects_tuple): raise TypeError("All items in a channel list must be of the same type.") self._channels.extend(objects_tuple) self._channel_mapping.update({obj.short_name: obj for obj in objects_tuple}) - def insert(self, index: int, value: InstrumentModuleType) -> None: + def insert(self, index: int, obj: InstrumentModuleType) -> None: """ Insert an object into the ChannelList at a specific index. Args: index: Index to insert object. - value: Object of type chan_type to insert. + obj: Object of type chan_type to insert. """ if self._locked: raise AttributeError("Cannot insert into a locked channel list") - if not isinstance(value, self._chan_type): + if not isinstance(obj, self._chan_type): raise TypeError( f"All items in a channel list must be of the same " - f"type. Adding {type(value).__name__} to a list of {self._chan_type.__name__}." + f"type. Adding {type(obj).__name__} to a list of {self._chan_type.__name__}." ) - self._channels.insert(index, value) - self._channel_mapping[value.short_name] = value + self._channels.insert(index, obj) + self._channel_mapping[obj.short_name] = obj def get_validator(self) -> ChannelTupleValidator: """ From c32477760d2dbfb5f32608bd4e25311f69fbbc78 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Mon, 4 Mar 2024 09:59:26 +0100 Subject: [PATCH 20/21] ignore override warnings in channel --- src/qcodes/instrument/channel.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/qcodes/instrument/channel.py b/src/qcodes/instrument/channel.py index 3a55b2d218f..6e18f034ae9 100644 --- a/src/qcodes/instrument/channel.py +++ b/src/qcodes/instrument/channel.py @@ -301,8 +301,13 @@ def name_parts(self) -> list[str]: name_parts.append(self.short_name) return name_parts - def index( - self, obj: InstrumentModuleType, start: int = 0, stop: int = sys.maxsize + # the parameter obj should be called value but that would + # be an incompatible change + def index( # pyright: ignore[reportIncompatibleMethodOverride] + self, + obj: InstrumentModuleType, + start: int = 0, + stop: int = sys.maxsize, ) -> int: """ Return the index of the given object @@ -314,7 +319,9 @@ def index( """ return self._channels.index(obj, start, stop) - def count(self, obj: InstrumentModuleType) -> int: + def count( # pyright: ignore[reportIncompatibleMethodOverride] + self, obj: InstrumentModuleType + ) -> int: """Returns number of instances of the given object in the list Args: @@ -625,7 +632,9 @@ def __setitem__( channel.short_name: channel for channel in self._channels } - def append(self, obj: InstrumentModuleType) -> None: + def append( # pyright: ignore[reportIncompatibleMethodOverride] + self, obj: InstrumentModuleType + ) -> None: """ Append a Channel to this list. Requires that the ChannelList is not locked and that the channel is of the same type as the ones in the list. @@ -654,7 +663,9 @@ def clear(self) -> None: self._channels.clear() self._channel_mapping.clear() - def remove(self, obj: InstrumentModuleType) -> None: + def remove( # pyright: ignore[reportIncompatibleMethodOverride] + self, obj: InstrumentModuleType + ) -> None: """ Removes obj from ChannelList if not locked. @@ -667,7 +678,9 @@ def remove(self, obj: InstrumentModuleType) -> None: self._channels.remove(obj) self._channel_mapping.pop(obj.short_name) - def extend(self, objects: Iterable[InstrumentModuleType]) -> None: + def extend( # pyright: ignore[reportIncompatibleMethodOverride] + self, objects: Iterable[InstrumentModuleType] + ) -> None: """ Insert an iterable of objects into the list of channels. @@ -685,7 +698,9 @@ def extend(self, objects: Iterable[InstrumentModuleType]) -> None: self._channels.extend(objects_tuple) self._channel_mapping.update({obj.short_name: obj for obj in objects_tuple}) - def insert(self, index: int, obj: InstrumentModuleType) -> None: + def insert( # pyright: ignore[reportIncompatibleMethodOverride] + self, index: int, obj: InstrumentModuleType + ) -> None: """ Insert an object into the ChannelList at a specific index. From b2a94b6ef1b84c2f365360b5ab94995f70c3e2b1 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Mon, 4 Mar 2024 10:11:40 +0100 Subject: [PATCH 21/21] add changes for 5721 --- docs/changes/newsfragments/5721.breaking | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 docs/changes/newsfragments/5721.breaking diff --git a/docs/changes/newsfragments/5721.breaking b/docs/changes/newsfragments/5721.breaking new file mode 100644 index 00000000000..bc120ed755e --- /dev/null +++ b/docs/changes/newsfragments/5721.breaking @@ -0,0 +1,9 @@ +QCoDeS is now type checked to ensure that subclasses are implemented in a way consistent with the parent class. +This has resulted in a number of changes to the API. The following is a list of the changes that have been made +to the API to make subclasses match their parent class. These changes are not expected to break any existing code, since they are +primarily in positional arguments or unused arguments. + +* The first argument to `NumpyJSONEncoder.default` has changed from `obj` to `o` to match the naming in the std library `json.JSONEncoder.default`. +* Unused args `idn_part` and `being_time` to `QDevQDac.connect_message` have been changed to `idn_param` and `begin_time` respectively to match the parent class. +* Unused arguments to stub methods `DSOTraceParam.setpoints`, `DSOTraceParam.unit` and `FormattedSweep.setpoints` have been changed to match the parent class. +* Alazar `DemodulationAcquisitionController.handle_buffer` the first argument has been renamed from `data` to `buffer` to match the parent class.