diff --git a/docs/examples/Parameters/Parameters.ipynb b/docs/examples/Parameters/Parameters.ipynb index 50f77eb1c934..f52a6238d97c 100644 --- a/docs/examples/Parameters/Parameters.ipynb +++ b/docs/examples/Parameters/Parameters.ipynb @@ -260,12 +260,12 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "my_delegate_param = DelegateParameter(\n", - " \"my_delegated_parameter\", dac.ch2, scale=1 / 1000, unit=\"mV\"\n", + " \"my_delegated_parameter\", source=dac.ch2, scale=1 / 1000, unit=\"mV\"\n", ")" ] }, diff --git a/src/qcodes/parameters/array_parameter.py b/src/qcodes/parameters/array_parameter.py index 34cca09fc812..fd6fc446f088 100644 --- a/src/qcodes/parameters/array_parameter.py +++ b/src/qcodes/parameters/array_parameter.py @@ -2,8 +2,7 @@ import collections.abc import os -import warnings -from typing import TYPE_CHECKING, Any, ClassVar +from typing import TYPE_CHECKING, Any import numpy as np @@ -15,8 +14,6 @@ has_loop = False from typing import Generic -from qcodes.utils import QCoDeSDeprecationWarning - from .parameter_base import InstrumentTypeVar_co, ParameterBase, ParameterDataTypeVar from .sequence_helpers import is_sequence_of @@ -43,9 +40,6 @@ ) -_SHAPE_UNSET: Any = object() - - class ArrayParameter( ParameterBase[ParameterDataTypeVar, InstrumentTypeVar_co], Generic[ParameterDataTypeVar, InstrumentTypeVar_co], @@ -135,27 +129,11 @@ class ArrayParameter( """ - _DEPRECATED_POSITIONAL_ARGS: ClassVar[tuple[str, ...]] = ( - "shape", - "instrument", - "label", - "unit", - "setpoints", - "setpoint_names", - "setpoint_labels", - "setpoint_units", - "docstring", - "snapshot_get", - "snapshot_value", - "snapshot_exclude", - "metadata", - ) - def __init__( self, name: str, - *args: Any, - shape: Sequence[int] = _SHAPE_UNSET, + *, + shape: Sequence[int], # mypy seems to be confused here. The bound and default for InstrumentTypeVar_co # contains None but mypy will not allow it as a default as of v 1.19.0 instrument: InstrumentTypeVar_co = None, # type: ignore[assignment] @@ -172,90 +150,6 @@ def __init__( metadata: Mapping[Any, Any] | None = None, **kwargs: Any, ) -> None: - if args: - # TODO: After QCoDeS 0.57 remove the args argument and delete this code block. - # we hardcode the class since mypy does not support __class__ and - # self / self.__class__ / type(self) in class bodies does not give - # exactly this class but the type of a subclass - positional_names = ArrayParameter._DEPRECATED_POSITIONAL_ARGS - if len(args) > len(positional_names): - raise TypeError( - f"{type(self).__name__}.__init__() takes at most " - f"{len(positional_names) + 2} positional arguments " - f"({len(args) + 2} given)" - ) - - _defaults: dict[str, Any] = { - "shape": _SHAPE_UNSET, - "instrument": None, - "label": None, - "unit": None, - "setpoints": None, - "setpoint_names": None, - "setpoint_labels": None, - "setpoint_units": None, - "docstring": None, - "snapshot_get": True, - "snapshot_value": False, - "snapshot_exclude": False, - "metadata": None, - } - - _kwarg_vals: dict[str, Any] = { - "shape": shape, - "instrument": instrument, - "label": label, - "unit": unit, - "setpoints": setpoints, - "setpoint_names": setpoint_names, - "setpoint_labels": setpoint_labels, - "setpoint_units": setpoint_units, - "docstring": docstring, - "snapshot_get": snapshot_get, - "snapshot_value": snapshot_value, - "snapshot_exclude": snapshot_exclude, - "metadata": metadata, - } - - for i in range(len(args)): - arg_name = positional_names[i] - if _kwarg_vals[arg_name] is not _defaults[arg_name]: - raise TypeError( - f"{type(self).__name__}.__init__() got multiple " - f"values for argument '{arg_name}'" - ) - - positional_arg_names = positional_names[: len(args)] - names_str = ", ".join(f"'{n}'" for n in positional_arg_names) - warnings.warn( - f"Passing {names_str} as positional argument(s) to " - f"{type(self).__name__} is deprecated. " - f"Please pass them as keyword arguments.", - QCoDeSDeprecationWarning, - stacklevel=2, - ) - - _pos = dict(zip(positional_names, args)) - shape = _pos.get("shape", shape) - instrument = _pos.get("instrument", instrument) - label = _pos.get("label", label) - unit = _pos.get("unit", unit) - setpoints = _pos.get("setpoints", setpoints) - setpoint_names = _pos.get("setpoint_names", setpoint_names) - setpoint_labels = _pos.get("setpoint_labels", setpoint_labels) - setpoint_units = _pos.get("setpoint_units", setpoint_units) - docstring = _pos.get("docstring", docstring) - snapshot_get = _pos.get("snapshot_get", snapshot_get) - snapshot_value = _pos.get("snapshot_value", snapshot_value) - snapshot_exclude = _pos.get("snapshot_exclude", snapshot_exclude) - metadata = _pos.get("metadata", metadata) - - if shape is _SHAPE_UNSET: - raise TypeError( - f"{type(self).__name__}.__init__() missing required " - f"keyword argument: 'shape'" - ) - super().__init__( name, instrument=instrument, diff --git a/src/qcodes/parameters/delegate_parameter.py b/src/qcodes/parameters/delegate_parameter.py index 0a409a6335bf..ff7da3a25bc6 100644 --- a/src/qcodes/parameters/delegate_parameter.py +++ b/src/qcodes/parameters/delegate_parameter.py @@ -1,12 +1,9 @@ from __future__ import annotations -import warnings -from typing import TYPE_CHECKING, Any, ClassVar, Generic +from typing import TYPE_CHECKING, Any, Generic from typing_extensions import TypeVar -from qcodes.utils import QCoDeSDeprecationWarning - from .parameter import Parameter from .parameter_base import InstrumentTypeVar_co, ParameterDataTypeVar @@ -75,11 +72,6 @@ class DelegateParameter( """ - _DEPRECATED_POSITIONAL_ARGS: ClassVar[tuple[str, ...]] = ( - "source", - *Parameter._DEPRECATED_POSITIONAL_ARGS, - ) - class _DelegateCache( Generic[_local_ParameterDataTypeVar, _local_InstrumentTypeVar_co] ): @@ -184,58 +176,10 @@ def __call__(self) -> _local_ParameterDataTypeVar: def __init__( self, name: str, - *args: Any, - source: Parameter | None = _SOURCE_UNSET, + *, + source: Parameter | None, **kwargs: Any, ): - if args: - # TODO: After QCoDeS 0.57 remove the args argument and delete this code block. - positional_names = DelegateParameter._DEPRECATED_POSITIONAL_ARGS - if len(args) > len(positional_names): - raise TypeError( - f"{type(self).__name__}.__init__() takes at most " - f"{len(positional_names) + 2} positional arguments " - f"({len(args) + 2} given)" - ) - - for i in range(len(args)): - arg_name = positional_names[i] - if arg_name == "source": - if source is not _SOURCE_UNSET: - raise TypeError( - f"{type(self).__name__}.__init__() got multiple " - f"values for argument '{arg_name}'" - ) - elif arg_name in kwargs: - raise TypeError( - f"{type(self).__name__}.__init__() got multiple " - f"values for argument '{arg_name}'" - ) - - positional_arg_names = positional_names[: len(args)] - names_str = ", ".join(f"'{n}'" for n in positional_arg_names) - warnings.warn( - f"Passing {names_str} as positional argument(s) to " - f"{type(self).__name__} is deprecated. " - "Please pass them as keyword arguments.", - QCoDeSDeprecationWarning, - stacklevel=2, - ) - - positional_values = dict(zip(positional_names, args)) - if "source" in positional_values: - source = positional_values["source"] - - for arg_name in positional_names[1:]: - if arg_name in positional_values: - kwargs[arg_name] = positional_values[arg_name] - - if source is _SOURCE_UNSET: - raise TypeError( - f"{type(self).__name__}.__init__() missing required keyword " - "argument: 'source'" - ) - if "bind_to_instrument" not in kwargs.keys(): kwargs["bind_to_instrument"] = False diff --git a/src/qcodes/parameters/group_parameter.py b/src/qcodes/parameters/group_parameter.py index 894c0613f83b..7ef57810a8b9 100644 --- a/src/qcodes/parameters/group_parameter.py +++ b/src/qcodes/parameters/group_parameter.py @@ -6,11 +6,8 @@ from __future__ import annotations -import warnings from collections import OrderedDict -from typing import TYPE_CHECKING, Any, ClassVar - -from qcodes.utils import QCoDeSDeprecationWarning +from typing import TYPE_CHECKING, Any from .parameter import Parameter @@ -51,64 +48,14 @@ class GroupParameter(Parameter): """ - _DEPRECATED_POSITIONAL_ARGS: ClassVar[tuple[str, ...]] = ( - "instrument", - "initial_value", - ) - def __init__( self, name: str, - *args: Any, + *, instrument: InstrumentBase | None = None, initial_value: float | str | None = None, **kwargs: Any, ) -> None: - if args: - # TODO: After QCoDeS 0.57 remove the args argument and delete this code block. - # we hardcode the class since mypy does not support __class__ and - # self / self.__class__ / type(self) in class bodies does not give - # exactly this class but the type of a subclass - positional_names = GroupParameter._DEPRECATED_POSITIONAL_ARGS - if len(args) > len(positional_names): - raise TypeError( - f"{type(self).__name__}.__init__() takes at most " - f"{len(positional_names) + 2} positional arguments " - f"({len(args) + 2} given)" - ) - - _defaults: dict[str, Any] = { - "instrument": None, - "initial_value": None, - } - - _kwarg_vals: dict[str, Any] = { - "instrument": instrument, - "initial_value": initial_value, - } - - for i in range(len(args)): - arg_name = positional_names[i] - if _kwarg_vals[arg_name] is not _defaults[arg_name]: - raise TypeError( - f"{type(self).__name__}.__init__() got multiple " - f"values for argument '{arg_name}'" - ) - - positional_arg_names = positional_names[: len(args)] - names_str = ", ".join(f"'{n}'" for n in positional_arg_names) - warnings.warn( - f"Passing {names_str} as positional argument(s) to " - f"{type(self).__name__} is deprecated. " - f"Please pass them as keyword arguments.", - QCoDeSDeprecationWarning, - stacklevel=2, - ) - - _pos = dict(zip(positional_names, args)) - instrument = _pos.get("instrument", instrument) - initial_value = _pos.get("initial_value", initial_value) - if "set_cmd" in kwargs or "get_cmd" in kwargs: raise ValueError( "A GroupParameter does not use 'set_cmd' or 'get_cmd' kwarg" diff --git a/src/qcodes/parameters/grouped_parameter.py b/src/qcodes/parameters/grouped_parameter.py index 453b00a9e3d7..91e20c232778 100644 --- a/src/qcodes/parameters/grouped_parameter.py +++ b/src/qcodes/parameters/grouped_parameter.py @@ -1,11 +1,8 @@ from __future__ import annotations import logging -import warnings from collections import OrderedDict, namedtuple -from typing import TYPE_CHECKING, Any, ClassVar - -from qcodes.utils import QCoDeSDeprecationWarning +from typing import TYPE_CHECKING, Any from .delegate_parameter import DelegateParameter from .group_parameter import Group, GroupParameter @@ -168,77 +165,15 @@ class GroupedParameter(ParameterBase): """ - _DEPRECATED_POSITIONAL_ARGS: ClassVar[tuple[str, ...]] = ( - "group", - "unit", - "label", - ) - - _GROUP_UNSET: Any = object() - def __init__( self, name: str, - *args: Any, - group: DelegateGroup = _GROUP_UNSET, + *, + group: DelegateGroup, unit: str | None = None, label: str | None = None, **kwargs: Any, ): - if args: - # TODO: After QCoDeS 0.57 remove the args argument and delete this code block. - # we hardcode the class since mypy does not support __class__ and - # self / self.__class__ / type(self) in class bodies does not give - # exactly this class but the type of a subclass - positional_names = GroupedParameter._DEPRECATED_POSITIONAL_ARGS - if len(args) > len(positional_names): - raise TypeError( - f"{type(self).__name__}.__init__() takes at most " - f"{len(positional_names) + 2} positional arguments " - f"({len(args) + 2} given)" - ) - - _defaults: dict[str, Any] = { - "group": self._GROUP_UNSET, - "unit": None, - "label": None, - } - - _kwarg_vals: dict[str, Any] = { - "group": group, - "unit": unit, - "label": label, - } - - for i in range(len(args)): - arg_name = positional_names[i] - if _kwarg_vals[arg_name] is not _defaults[arg_name]: - raise TypeError( - f"{type(self).__name__}.__init__() got multiple " - f"values for argument '{arg_name}'" - ) - - positional_arg_names = positional_names[: len(args)] - names_str = ", ".join(f"'{n}'" for n in positional_arg_names) - warnings.warn( - f"Passing {names_str} as positional argument(s) to " - f"{type(self).__name__} is deprecated. " - f"Please pass them as keyword arguments.", - QCoDeSDeprecationWarning, - stacklevel=2, - ) - - _pos = dict(zip(positional_names, args)) - group = _pos.get("group", group) - unit = _pos.get("unit", unit) - label = _pos.get("label", label) - - if group is self._GROUP_UNSET: - raise TypeError( - f"{type(self).__name__}.__init__() missing required " - f"keyword argument: 'group'" - ) - super().__init__(name, **kwargs) self.label = name if label is None else label self.unit = unit if unit is not None else "" diff --git a/src/qcodes/parameters/multi_channel_instrument_parameter.py b/src/qcodes/parameters/multi_channel_instrument_parameter.py index b4a8f3af1d47..d7055857480c 100644 --- a/src/qcodes/parameters/multi_channel_instrument_parameter.py +++ b/src/qcodes/parameters/multi_channel_instrument_parameter.py @@ -1,10 +1,7 @@ from __future__ import annotations import logging -import warnings -from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar - -from qcodes.utils import QCoDeSDeprecationWarning +from typing import TYPE_CHECKING, Any, Generic, TypeVar from .multi_parameter import MultiParameter @@ -33,79 +30,14 @@ class MultiChannelInstrumentParameter(MultiParameter, Generic[InstrumentModuleTy """ - _DEPRECATED_POSITIONAL_ARGS: ClassVar[tuple[str, ...]] = ( - "channels", - "param_name", - ) - - _CHANNELS_UNSET: Any = object() - _PARAM_NAME_UNSET: Any = object() - def __init__( self, - *args: Any, - channels: Sequence[InstrumentModuleType] = _CHANNELS_UNSET, - param_name: str = _PARAM_NAME_UNSET, + *, + channels: Sequence[InstrumentModuleType], + param_name: str, **kwargs: Any, ) -> None: - extra_args: tuple[Any, ...] = () - if args: - # TODO: After QCoDeS 0.57 remove the args argument and delete this code block. - # we hardcode the class since mypy does not support __class__ and - # self / self.__class__ / type(self) in class bodies does not give - # exactly this class but the type of a subclass - positional_names = ( - MultiChannelInstrumentParameter._DEPRECATED_POSITIONAL_ARGS - ) - n_own = min(len(args), len(positional_names)) - - _defaults: dict[str, Any] = { - "channels": self._CHANNELS_UNSET, - "param_name": self._PARAM_NAME_UNSET, - } - - _kwarg_vals: dict[str, Any] = { - "channels": channels, - "param_name": param_name, - } - - for i in range(n_own): - arg_name = positional_names[i] - if _kwarg_vals[arg_name] is not _defaults[arg_name]: - raise TypeError( - f"{type(self).__name__}.__init__() got multiple " - f"values for argument '{arg_name}'" - ) - - positional_arg_names = positional_names[:n_own] - names_str = ", ".join(f"'{n}'" for n in positional_arg_names) - warnings.warn( - f"Passing {names_str} as positional argument(s) to " - f"{type(self).__name__} is deprecated. " - f"Please pass them as keyword arguments.", - QCoDeSDeprecationWarning, - stacklevel=2, - ) - - _pos = dict(zip(positional_names, args)) - channels = _pos.get("channels", channels) - param_name = _pos.get("param_name", param_name) - - # Any args beyond our own positional args are forwarded to super - extra_args = args[n_own:] - - if channels is self._CHANNELS_UNSET: - raise TypeError( - f"{type(self).__name__}.__init__() missing required " - f"keyword argument: 'channels'" - ) - if param_name is self._PARAM_NAME_UNSET: - raise TypeError( - f"{type(self).__name__}.__init__() missing required " - f"keyword argument: 'param_name'" - ) - - super().__init__(*extra_args, **kwargs) + super().__init__(**kwargs) self._channels = channels self._param_name = param_name diff --git a/src/qcodes/parameters/multi_parameter.py b/src/qcodes/parameters/multi_parameter.py index 613995f5fe0e..48c4976eafcc 100644 --- a/src/qcodes/parameters/multi_parameter.py +++ b/src/qcodes/parameters/multi_parameter.py @@ -1,14 +1,11 @@ from __future__ import annotations import os -import warnings from collections.abc import Iterator, Mapping, Sequence -from typing import Any, ClassVar, Generic +from typing import Any, Generic import numpy as np -from qcodes.utils import QCoDeSDeprecationWarning - from .parameter_base import InstrumentTypeVar_co, ParameterBase, ParameterDataTypeVar from .sequence_helpers import is_sequence_of @@ -139,32 +136,12 @@ class MultiParameter( """ - _NAMES_UNSET: Any = object() - _SHAPES_UNSET: Any = object() - - _DEPRECATED_POSITIONAL_ARGS: ClassVar[tuple[str, ...]] = ( - "names", - "shapes", - "instrument", - "labels", - "units", - "setpoints", - "setpoint_names", - "setpoint_labels", - "setpoint_units", - "docstring", - "snapshot_get", - "snapshot_value", - "snapshot_exclude", - "metadata", - ) - def __init__( self, name: str, - *args: Any, - names: Sequence[str] = _NAMES_UNSET, - shapes: Sequence[Sequence[int]] = _SHAPES_UNSET, + *, + names: Sequence[str], + shapes: Sequence[Sequence[int]], # mypy seems to be confused here. The bound and default for InstrumentTypeVar_co # contains None but mypy will not allow it as a default as of v 1.19.0 instrument: InstrumentTypeVar_co = None, # type: ignore[assignment] @@ -181,98 +158,6 @@ def __init__( metadata: Mapping[Any, Any] | None = None, **kwargs: Any, ) -> None: - if args: - # TODO: After QCoDeS 0.57 remove the args argument and delete this code block. - # we hardcode the class since mypy does not support __class__ and - # self / self.__class__ / type(self) in class bodies does not give - # exactly this class but the type of a subclass - positional_names = MultiParameter._DEPRECATED_POSITIONAL_ARGS - if len(args) > len(positional_names): - raise TypeError( - f"{type(self).__name__}.__init__() takes at most " - f"{len(positional_names) + 2} positional arguments " - f"({len(args) + 2} given)" - ) - - _defaults: dict[str, Any] = { - "names": self._NAMES_UNSET, - "shapes": self._SHAPES_UNSET, - "instrument": None, - "labels": None, - "units": None, - "setpoints": None, - "setpoint_names": None, - "setpoint_labels": None, - "setpoint_units": None, - "docstring": None, - "snapshot_get": True, - "snapshot_value": False, - "snapshot_exclude": False, - "metadata": None, - } - - _kwarg_vals: dict[str, Any] = { - "names": names, - "shapes": shapes, - "instrument": instrument, - "labels": labels, - "units": units, - "setpoints": setpoints, - "setpoint_names": setpoint_names, - "setpoint_labels": setpoint_labels, - "setpoint_units": setpoint_units, - "docstring": docstring, - "snapshot_get": snapshot_get, - "snapshot_value": snapshot_value, - "snapshot_exclude": snapshot_exclude, - "metadata": metadata, - } - - for i in range(len(args)): - arg_name = positional_names[i] - if _kwarg_vals[arg_name] is not _defaults[arg_name]: - raise TypeError( - f"{type(self).__name__}.__init__() got multiple " - f"values for argument '{arg_name}'" - ) - - positional_arg_names = positional_names[: len(args)] - names_str = ", ".join(f"'{n}'" for n in positional_arg_names) - warnings.warn( - f"Passing {names_str} as positional argument(s) to " - f"{type(self).__name__} is deprecated. " - f"Please pass them as keyword arguments.", - QCoDeSDeprecationWarning, - stacklevel=2, - ) - - _pos = dict(zip(positional_names, args)) - names = _pos.get("names", names) - shapes = _pos.get("shapes", shapes) - instrument = _pos.get("instrument", instrument) - labels = _pos.get("labels", labels) - units = _pos.get("units", units) - setpoints = _pos.get("setpoints", setpoints) - setpoint_names = _pos.get("setpoint_names", setpoint_names) - setpoint_labels = _pos.get("setpoint_labels", setpoint_labels) - setpoint_units = _pos.get("setpoint_units", setpoint_units) - docstring = _pos.get("docstring", docstring) - snapshot_get = _pos.get("snapshot_get", snapshot_get) - snapshot_value = _pos.get("snapshot_value", snapshot_value) - snapshot_exclude = _pos.get("snapshot_exclude", snapshot_exclude) - metadata = _pos.get("metadata", metadata) - - if names is self._NAMES_UNSET: - raise TypeError( - f"{type(self).__name__}.__init__() missing required " - f"keyword argument: 'names'" - ) - if shapes is self._SHAPES_UNSET: - raise TypeError( - f"{type(self).__name__}.__init__() missing required " - f"keyword argument: 'shapes'" - ) - super().__init__( name, instrument=instrument, diff --git a/src/qcodes/parameters/parameter.py b/src/qcodes/parameters/parameter.py index 12928f61c56f..4e83de5f9d3d 100644 --- a/src/qcodes/parameters/parameter.py +++ b/src/qcodes/parameters/parameter.py @@ -5,11 +5,8 @@ import logging import os -import warnings from types import MethodType -from typing import TYPE_CHECKING, Any, ClassVar, Generic, Literal - -from qcodes.utils import QCoDeSDeprecationWarning +from typing import TYPE_CHECKING, Any, Generic, Literal from .command import Command from .parameter_base import ( @@ -180,27 +177,10 @@ class Parameter( """ - # Ordered list of keyword argument names (after 'name') for - # backwards-compatible positional argument handling. - # TODO: remove with handling of args below after QCoDeS 0.57 - _DEPRECATED_POSITIONAL_ARGS: ClassVar[tuple[str, ...]] = ( - "instrument", - "label", - "unit", - "get_cmd", - "set_cmd", - "initial_value", - "max_val_age", - "vals", - "docstring", - "initial_cache_value", - "bind_to_instrument", - ) - def __init__( self, name: str, - *args: Any, + *, # mypy seems to be confused here. The bound and default for InstrumentTypeVar_co # contains None but mypy will not allow None as a default as of v 1.19.0 instrument: InstrumentTypeVar_co = None, # type: ignore[assignment] @@ -216,85 +196,6 @@ def __init__( bind_to_instrument: bool = True, **kwargs: Any, ) -> None: - if args: - # TODO: After QCoDeS 0.57 remove the args argument and delete this code block. - # we hardcode the class since mypy does not support __class__ and - # self / self.__class__ / type(self) in class bodies does not give - # exactly this class but the type of a subclass - positional_names = Parameter._DEPRECATED_POSITIONAL_ARGS - if len(args) > len(positional_names): - raise TypeError( - f"{type(self).__name__}.__init__() takes at most " - f"{len(positional_names) + 2} positional arguments " - f"({len(args) + 2} given)" - ) - - _defaults: dict[str, Any] = { - "instrument": None, - "label": None, - "unit": None, - "get_cmd": None, - "set_cmd": False, - "initial_value": None, - "max_val_age": None, - "vals": None, - "docstring": None, - "initial_cache_value": None, - "bind_to_instrument": True, - } - - # Snapshot keyword values before any reassignment so we can - # detect duplicates (keyword value differs from its default). - _kwarg_vals: dict[str, Any] = { - "instrument": instrument, - "label": label, - "unit": unit, - "get_cmd": get_cmd, - "set_cmd": set_cmd, - "initial_value": initial_value, - "max_val_age": max_val_age, - "vals": vals, - "docstring": docstring, - "initial_cache_value": initial_cache_value, - "bind_to_instrument": bind_to_instrument, - } - - # Check for duplicate arguments (passed both positionally and - # as keyword). We detect this by checking whether the keyword - # value differs from its default for each positionally-supplied - # argument. - for i in range(len(args)): - arg_name = positional_names[i] - if _kwarg_vals[arg_name] is not _defaults[arg_name]: - raise TypeError( - f"{type(self).__name__}.__init__() got multiple " - f"values for argument '{arg_name}'" - ) - - positional_arg_names = positional_names[: len(args)] - names_str = ", ".join(f"'{n}'" for n in positional_arg_names) - warnings.warn( - f"Passing {names_str} as positional argument(s) to " - f"{type(self).__name__} is deprecated. " - f"Please pass them as keyword arguments.", - QCoDeSDeprecationWarning, - stacklevel=2, - ) - - # Apply positional values to the keyword parameter variables. - _pos = dict(zip(positional_names, args)) - instrument = _pos.get("instrument", instrument) - label = _pos.get("label", label) - unit = _pos.get("unit", unit) - get_cmd = _pos.get("get_cmd", get_cmd) - set_cmd = _pos.get("set_cmd", set_cmd) - initial_value = _pos.get("initial_value", initial_value) - max_val_age = _pos.get("max_val_age", max_val_age) - vals = _pos.get("vals", vals) - docstring = _pos.get("docstring", docstring) - initial_cache_value = _pos.get("initial_cache_value", initial_cache_value) - bind_to_instrument = _pos.get("bind_to_instrument", bind_to_instrument) - def _get_manual_parameter(self: Parameter) -> ParamRawDataType: if self.root_instrument is not None: mylogger: InstrumentLoggerAdapter | logging.Logger = ( diff --git a/src/qcodes/parameters/parameter_base.py b/src/qcodes/parameters/parameter_base.py index 1b76f56fe38a..64d076848d9a 100644 --- a/src/qcodes/parameters/parameter_base.py +++ b/src/qcodes/parameters/parameter_base.py @@ -17,7 +17,6 @@ from qcodes.parameters import ParamSpecBase from qcodes.utils import ( DelegateAttributes, - QCoDeSDeprecationWarning, full_class, qcodes_abstractmethod, ) @@ -231,35 +230,10 @@ class ParameterBase( Callable[[ParameterBase, ParamDataType], None] | None ] = None - # Ordered list of keyword argument names (after 'name') for - # backwards-compatible positional argument handling. - # TODO: remove with handling of args below after QCoDeS 0.57 - _DEPRECATED_POSITIONAL_ARGS: ClassVar[tuple[str, ...]] = ( - "instrument", - "snapshot_get", - "metadata", - "step", - "scale", - "offset", - "inter_delay", - "post_delay", - "val_mapping", - "get_parser", - "set_parser", - "snapshot_value", - "snapshot_exclude", - "max_val_age", - "vals", - "abstract", - "bind_to_instrument", - "register_name", - "on_set_callback", - ) - def __init__( self, name: str, - *args: Any, + *, # mypy seems to be confused here. The bound and default for InstrumentTypeVar_co # contains None but mypy will not allow None as a default as of v 1.19.0 instrument: InstrumentTypeVar_co = None, # type: ignore[assignment] @@ -283,109 +257,6 @@ def __init__( on_set_callback: Callable[[ParameterBase, ParameterDataTypeVar], None] | None = None, ) -> None: - if args: - # TODO: After QCoDeS 0.57 remove the args argument and delete this code block. - # we hardcode the class since mypy does not support __class__ and - # self / self.__class__ / type(self) in class bodies does not give - # exactly this class but the type of a subclass - positional_names = ParameterBase._DEPRECATED_POSITIONAL_ARGS - if len(args) > len(positional_names): - raise TypeError( - f"{type(self).__name__}.__init__() takes at most " - f"{len(positional_names) + 2} positional arguments " - f"({len(args) + 2} given)" - ) - - _defaults: dict[str, Any] = { - "instrument": None, - "snapshot_get": True, - "metadata": None, - "step": None, - "scale": None, - "offset": None, - "inter_delay": 0, - "post_delay": 0, - "val_mapping": None, - "get_parser": None, - "set_parser": None, - "snapshot_value": True, - "snapshot_exclude": False, - "max_val_age": None, - "vals": None, - "abstract": False, - "bind_to_instrument": True, - "register_name": None, - "on_set_callback": None, - } - - # Snapshot keyword values before any reassignment so we can - # detect duplicates (keyword value differs from its default). - _kwarg_vals: dict[str, Any] = { - "instrument": instrument, - "snapshot_get": snapshot_get, - "metadata": metadata, - "step": step, - "scale": scale, - "offset": offset, - "inter_delay": inter_delay, - "post_delay": post_delay, - "val_mapping": val_mapping, - "get_parser": get_parser, - "set_parser": set_parser, - "snapshot_value": snapshot_value, - "snapshot_exclude": snapshot_exclude, - "max_val_age": max_val_age, - "vals": vals, - "abstract": abstract, - "bind_to_instrument": bind_to_instrument, - "register_name": register_name, - "on_set_callback": on_set_callback, - } - - # Check for duplicate arguments (passed both positionally and - # as keyword). We detect this by checking whether the keyword - # value differs from its default for each positionally-supplied - # argument. - for i in range(len(args)): - arg_name = positional_names[i] - if _kwarg_vals[arg_name] is not _defaults[arg_name]: - raise TypeError( - f"{type(self).__name__}.__init__() got multiple " - f"values for argument '{arg_name}'" - ) - - positional_arg_names = positional_names[: len(args)] - names_str = ", ".join(f"'{n}'" for n in positional_arg_names) - warnings.warn( - f"Passing {names_str} as positional argument(s) to " - f"{type(self).__name__} is deprecated. " - f"Please pass them as keyword arguments.", - QCoDeSDeprecationWarning, - stacklevel=2, - ) - - # Apply positional values to the keyword parameter variables. - _pos = dict(zip(positional_names, args)) - instrument = _pos.get("instrument", instrument) - snapshot_get = _pos.get("snapshot_get", snapshot_get) - metadata = _pos.get("metadata", metadata) - step = _pos.get("step", step) - scale = _pos.get("scale", scale) - offset = _pos.get("offset", offset) - inter_delay = _pos.get("inter_delay", inter_delay) - post_delay = _pos.get("post_delay", post_delay) - val_mapping = _pos.get("val_mapping", val_mapping) - get_parser = _pos.get("get_parser", get_parser) - set_parser = _pos.get("set_parser", set_parser) - snapshot_value = _pos.get("snapshot_value", snapshot_value) - snapshot_exclude = _pos.get("snapshot_exclude", snapshot_exclude) - max_val_age = _pos.get("max_val_age", max_val_age) - vals = _pos.get("vals", vals) - abstract = _pos.get("abstract", abstract) - bind_to_instrument = _pos.get("bind_to_instrument", bind_to_instrument) - register_name = _pos.get("register_name", register_name) - on_set_callback = _pos.get("on_set_callback", on_set_callback) - super().__init__(metadata) if not str(name).isidentifier(): raise ValueError( diff --git a/src/qcodes/parameters/specialized_parameters.py b/src/qcodes/parameters/specialized_parameters.py index d55868467d04..7fb50b4daff9 100644 --- a/src/qcodes/parameters/specialized_parameters.py +++ b/src/qcodes/parameters/specialized_parameters.py @@ -6,11 +6,9 @@ from __future__ import annotations -import warnings from time import perf_counter -from typing import TYPE_CHECKING, Any, ClassVar, Literal +from typing import TYPE_CHECKING, Any, Literal -from qcodes.utils import QCoDeSDeprecationWarning from qcodes.validators import Strings, Validator from .parameter import Parameter @@ -33,48 +31,7 @@ class ElapsedTimeParameter(Parameter): """ - _DEPRECATED_POSITIONAL_ARGS: ClassVar[tuple[str, ...]] = ("label",) - - def __init__( - self, name: str, *args: Any, label: str = "Elapsed time", **kwargs: Any - ): - if args: - # TODO: After QCoDeS 0.57 remove the args argument and delete this code block. - # we hardcode the class since mypy does not support __class__ and - # self / self.__class__ / type(self) in class bodies does not give - # exactly this class but the type of a subclass - positional_names = ElapsedTimeParameter._DEPRECATED_POSITIONAL_ARGS - if len(args) > len(positional_names): - raise TypeError( - f"{type(self).__name__}.__init__() takes at most " - f"{len(positional_names) + 2} positional arguments " - f"({len(args) + 2} given)" - ) - - _defaults: dict[str, Any] = {"label": "Elapsed time"} - _kwarg_vals: dict[str, Any] = {"label": label} - - for i in range(len(args)): - arg_name = positional_names[i] - if _kwarg_vals[arg_name] != _defaults[arg_name]: - raise TypeError( - f"{type(self).__name__}.__init__() got multiple " - f"values for argument '{arg_name}'" - ) - - positional_arg_names = positional_names[: len(args)] - names_str = ", ".join(f"'{n}'" for n in positional_arg_names) - warnings.warn( - f"Passing {names_str} as positional argument(s) to " - f"{type(self).__name__} is deprecated. " - f"Please pass them as keyword arguments.", - QCoDeSDeprecationWarning, - stacklevel=2, - ) - - _pos = dict(zip(positional_names, args)) - label = _pos.get("label", label) - + def __init__(self, name: str, *, label: str = "Elapsed time", **kwargs: Any): hardcoded_kwargs = ["unit", "get_cmd", "set_cmd"] for hck in hardcoded_kwargs: @@ -118,22 +75,10 @@ class InstrumentRefParameter(Parameter): """ - _DEPRECATED_POSITIONAL_ARGS: ClassVar[tuple[str, ...]] = ( - "instrument", - "label", - "unit", - "get_cmd", - "set_cmd", - "initial_value", - "max_val_age", - "vals", - "docstring", - ) - def __init__( self, name: str, - *args: Any, + *, instrument: InstrumentBase | None = None, label: str | None = None, unit: str | None = None, @@ -145,72 +90,6 @@ def __init__( docstring: str | None = None, **kwargs: Any, ) -> None: - if args: - # TODO: After QCoDeS 0.57 remove the args argument and delete this code block. - # we hardcode the class since mypy does not support __class__ and - # self / self.__class__ / type(self) in class bodies does not give - # exactly this class but the type of a subclass - positional_names = InstrumentRefParameter._DEPRECATED_POSITIONAL_ARGS - if len(args) > len(positional_names): - raise TypeError( - f"{type(self).__name__}.__init__() takes at most " - f"{len(positional_names) + 2} positional arguments " - f"({len(args) + 2} given)" - ) - - _defaults: dict[str, Any] = { - "instrument": None, - "label": None, - "unit": None, - "get_cmd": None, - "set_cmd": None, - "initial_value": None, - "max_val_age": None, - "vals": None, - "docstring": None, - } - - _kwarg_vals: dict[str, Any] = { - "instrument": instrument, - "label": label, - "unit": unit, - "get_cmd": get_cmd, - "set_cmd": set_cmd, - "initial_value": initial_value, - "max_val_age": max_val_age, - "vals": vals, - "docstring": docstring, - } - - for i in range(len(args)): - arg_name = positional_names[i] - if _kwarg_vals[arg_name] is not _defaults[arg_name]: - raise TypeError( - f"{type(self).__name__}.__init__() got multiple " - f"values for argument '{arg_name}'" - ) - - positional_arg_names = positional_names[: len(args)] - names_str = ", ".join(f"'{n}'" for n in positional_arg_names) - warnings.warn( - f"Passing {names_str} as positional argument(s) to " - f"{type(self).__name__} is deprecated. " - f"Please pass them as keyword arguments.", - QCoDeSDeprecationWarning, - stacklevel=2, - ) - - _pos = dict(zip(positional_names, args)) - instrument = _pos.get("instrument", instrument) - label = _pos.get("label", label) - unit = _pos.get("unit", unit) - get_cmd = _pos.get("get_cmd", get_cmd) - set_cmd = _pos.get("set_cmd", set_cmd) - initial_value = _pos.get("initial_value", initial_value) - max_val_age = _pos.get("max_val_age", max_val_age) - vals = _pos.get("vals", vals) - docstring = _pos.get("docstring", docstring) - if vals is None: vals = Strings() if set_cmd is not None: diff --git a/tests/parameter/test_args_deprecated.py b/tests/parameter/test_args_deprecated.py deleted file mode 100644 index c7184cb4d4ba..000000000000 --- a/tests/parameter/test_args_deprecated.py +++ /dev/null @@ -1,496 +0,0 @@ -"""Tests that passing positional arguments (beyond ``name``) to -ParameterBase, Parameter, and DelegateParameter triggers a -QCoDeSDeprecationWarning. -""" - -from __future__ import annotations - -from typing import Any - -import pytest - -from qcodes.instrument import Instrument -from qcodes.parameters import ( - ArrayParameter, - DelegateParameter, - ElapsedTimeParameter, - GroupParameter, - InstrumentRefParameter, - MultiParameter, - Parameter, - ParameterBase, -) -from qcodes.parameters.grouped_parameter import ( - DelegateGroup, - DelegateGroupParameter, - GroupedParameter, -) -from qcodes.parameters.multi_channel_instrument_parameter import ( - MultiChannelInstrumentParameter, -) -from qcodes.utils import QCoDeSDeprecationWarning - - -class _MockInstrument(Instrument): - """A minimal instrument for testing.""" - - def __init__(self, name: str = "mock"): - super().__init__(name) - - -@pytest.fixture -def mock_instrument() -> Any: - inst = _MockInstrument("dup_test") - yield inst - inst.close() - - -# Minimal concrete subclass of ParameterBase for testing -class _ConcreteParameterBase(ParameterBase): - def get_raw(self) -> Any: - return 0 - - -class TestParameterBasePositionalArgs: - """ParameterBase should warn when arguments after ``name`` are positional.""" - - def test_single_positional_arg_warns(self) -> None: - with pytest.warns( - QCoDeSDeprecationWarning, - match="Passing 'instrument' as positional argument", - ): - _ConcreteParameterBase("test", None) - - def test_multiple_positional_args_warn(self) -> None: - with pytest.warns( - QCoDeSDeprecationWarning, - match="'instrument', 'snapshot_get'", - ): - _ConcreteParameterBase("test", None, True) - - def test_keyword_args_do_not_warn(self) -> None: - # No warning should be raised when all args are keyword-only - p = _ConcreteParameterBase("test", instrument=None, snapshot_get=True) - assert p.name == "test" - - def test_duplicate_positional_and_keyword_raises( - self, mock_instrument: _MockInstrument - ) -> None: - with pytest.raises( - TypeError, - match="got multiple values for argument 'instrument'", - ): - _ConcreteParameterBase("test", None, instrument=mock_instrument) - - def test_too_many_positional_args_raises(self) -> None: - # More positional args than defined parameter names - too_many = (None,) * 25 - with pytest.raises(TypeError, match="takes at most"): - _ConcreteParameterBase("test", *too_many) - - -class TestParameterPositionalArgs: - """Parameter should warn when arguments after ``name`` are positional.""" - - def test_single_positional_arg_warns(self) -> None: - with pytest.warns( - QCoDeSDeprecationWarning, - match="Passing 'instrument' as positional argument", - ): - Parameter("test", None, set_cmd=None) - - def test_multiple_positional_args_warn(self) -> None: - with pytest.warns( - QCoDeSDeprecationWarning, - match="'instrument', 'label'", - ): - Parameter("test", None, "my label", set_cmd=None) - - def test_keyword_args_do_not_warn(self) -> None: - p = Parameter("test", instrument=None, label="my label", set_cmd=None) - assert p.name == "test" - assert p.label == "my label" - - def test_duplicate_positional_and_keyword_raises( - self, mock_instrument: _MockInstrument - ) -> None: - with pytest.raises( - TypeError, - match="got multiple values for argument 'instrument'", - ): - Parameter("test", None, instrument=mock_instrument) - - def test_too_many_positional_args_raises(self) -> None: - too_many = (None,) * 15 - with pytest.raises(TypeError, match="takes at most"): - Parameter("test", *too_many) - - -class TestDelegateParameterPositionalArgs: - """DelegateParameter should warn when arguments after ``name`` are positional.""" - - def test_single_positional_arg_warns(self) -> None: - source = Parameter("source", set_cmd=None) - with pytest.warns( - QCoDeSDeprecationWarning, - match="Passing 'source' as positional argument", - ): - DelegateParameter("test", source) - - def test_multiple_positional_args_warn(self) -> None: - source = Parameter("source", set_cmd=None) - with pytest.warns( - QCoDeSDeprecationWarning, - match="'source', 'instrument'", - ): - DelegateParameter("test", source, None) - - def test_keyword_args_do_not_warn(self) -> None: - source = Parameter("source", set_cmd=None) - p = DelegateParameter("test", source=source, instrument=None) - assert p.name == "test" - - def test_duplicate_positional_and_keyword_raises(self) -> None: - source = Parameter("source", set_cmd=None) - with pytest.raises( - TypeError, - match="got multiple values for argument 'source'", - ): - DelegateParameter("test", source, source=source) - - def test_too_many_positional_args_raises(self) -> None: - too_many = (None,) * 20 - with pytest.raises(TypeError, match="takes at most"): - DelegateParameter("test", *too_many) - - def test_missing_source_raises(self) -> None: - with pytest.raises( - TypeError, - match="missing required keyword argument: 'source'", - ): - DelegateParameter("test") - - -# Minimal concrete subclass of ArrayParameter for testing -class _ConcreteArrayParameter(ArrayParameter): - def get_raw(self) -> Any: - return [0] - - -class TestArrayParameterPositionalArgs: - """ArrayParameter should warn when arguments after ``name`` are positional.""" - - def test_single_positional_arg_warns(self) -> None: - with pytest.warns( - QCoDeSDeprecationWarning, - match="Passing 'shape' as positional argument", - ): - _ConcreteArrayParameter("test", (3,)) - - def test_multiple_positional_args_warn(self) -> None: - with pytest.warns( - QCoDeSDeprecationWarning, - match="'shape', 'instrument'", - ): - _ConcreteArrayParameter("test", (3,), None) - - def test_keyword_args_do_not_warn(self) -> None: - p = _ConcreteArrayParameter("test", shape=(3,), instrument=None) - assert p.name == "test" - assert p.shape == (3,) - - def test_duplicate_positional_and_keyword_raises(self) -> None: - with pytest.raises( - TypeError, - match="got multiple values for argument 'shape'", - ): - _ConcreteArrayParameter("test", (3,), shape=(3,)) - - def test_too_many_positional_args_raises(self) -> None: - too_many = (None,) * 20 - with pytest.raises(TypeError, match="takes at most"): - _ConcreteArrayParameter("test", *too_many) - - def test_missing_shape_raises(self) -> None: - with pytest.raises( - TypeError, - match="missing required keyword argument: 'shape'", - ): - _ConcreteArrayParameter("test") - - -class TestGroupParameterPositionalArgs: - """GroupParameter should warn when arguments after ``name`` are positional.""" - - def test_single_positional_arg_warns(self) -> None: - with pytest.warns( - QCoDeSDeprecationWarning, - match="Passing 'instrument' as positional argument", - ): - GroupParameter("test", None) - - def test_multiple_positional_args_warn(self) -> None: - with pytest.warns( - QCoDeSDeprecationWarning, - match="'instrument', 'initial_value'", - ): - GroupParameter("test", None, None) - - def test_keyword_args_do_not_warn(self) -> None: - p = GroupParameter("test", instrument=None, initial_value=None) - assert p.name == "test" - - def test_duplicate_positional_and_keyword_raises( - self, mock_instrument: _MockInstrument - ) -> None: - with pytest.raises( - TypeError, - match="got multiple values for argument 'instrument'", - ): - GroupParameter("test", None, instrument=mock_instrument) - - def test_too_many_positional_args_raises(self) -> None: - too_many = (None,) * 5 - with pytest.raises(TypeError, match="takes at most"): - GroupParameter("test", *too_many) - - -def _make_delegate_group() -> DelegateGroup: - """Helper to create a minimal DelegateGroup for testing.""" - source = Parameter("source", set_cmd=None, get_cmd=None) - dp = DelegateGroupParameter("dp", source=source) - return DelegateGroup("grp", parameters=[dp]) - - -class TestGroupedParameterPositionalArgs: - """GroupedParameter should warn when arguments after ``name`` are positional.""" - - def test_single_positional_arg_warns(self) -> None: - grp = _make_delegate_group() - with pytest.warns( - QCoDeSDeprecationWarning, - match="Passing 'group' as positional argument", - ): - GroupedParameter("test", grp) - - def test_multiple_positional_args_warn(self) -> None: - grp = _make_delegate_group() - with pytest.warns( - QCoDeSDeprecationWarning, - match="'group', 'unit'", - ): - GroupedParameter("test", grp, "V") - - def test_keyword_args_do_not_warn(self) -> None: - grp = _make_delegate_group() - p = GroupedParameter("test", group=grp, unit="V") - assert p.name == "test" - assert p.unit == "V" - - def test_duplicate_positional_and_keyword_raises(self) -> None: - grp = _make_delegate_group() - with pytest.raises( - TypeError, - match="got multiple values for argument 'group'", - ): - GroupedParameter("test", grp, group=grp) - - def test_too_many_positional_args_raises(self) -> None: - too_many = (None,) * 5 - with pytest.raises(TypeError, match="takes at most"): - GroupedParameter("test", *too_many) - - def test_missing_group_raises(self) -> None: - with pytest.raises( - TypeError, - match="missing required keyword argument: 'group'", - ): - GroupedParameter("test") - - -# Minimal concrete subclass of MultiParameter for testing -class _ConcreteMultiParameter(MultiParameter): - def get_raw(self) -> Any: - return (0,) - - -class TestMultiParameterPositionalArgs: - """MultiParameter should warn when arguments after ``name`` are positional.""" - - def test_single_positional_arg_warns(self) -> None: - with pytest.warns( - QCoDeSDeprecationWarning, - match="Passing 'names' as positional argument", - ): - _ConcreteMultiParameter("test", ("a",), shapes=((),)) - - def test_multiple_positional_args_warn(self) -> None: - with pytest.warns( - QCoDeSDeprecationWarning, - match="'names', 'shapes'", - ): - _ConcreteMultiParameter("test", ("a",), ((),)) - - def test_keyword_args_do_not_warn(self) -> None: - p = _ConcreteMultiParameter("test", names=("a",), shapes=((),)) - assert p.name == "test" - assert p.names == ("a",) - - def test_duplicate_positional_and_keyword_raises(self) -> None: - with pytest.raises( - TypeError, - match="got multiple values for argument 'names'", - ): - _ConcreteMultiParameter("test", ("a",), names=("a",)) - - def test_too_many_positional_args_raises(self) -> None: - too_many = (None,) * 20 - with pytest.raises(TypeError, match="takes at most"): - _ConcreteMultiParameter("test", *too_many) - - def test_missing_names_raises(self) -> None: - with pytest.raises( - TypeError, - match="missing required keyword argument: 'names'", - ): - _ConcreteMultiParameter("test", shapes=((),)) - - def test_missing_shapes_raises(self) -> None: - with pytest.raises( - TypeError, - match="missing required keyword argument: 'shapes'", - ): - _ConcreteMultiParameter("test", names=("a",)) - - -class TestMultiChannelInstrumentParameterPositionalArgs: - """MultiChannelInstrumentParameter should warn when args are positional.""" - - def test_single_positional_arg_warns(self) -> None: - with pytest.warns( - QCoDeSDeprecationWarning, - match="Passing 'channels' as positional argument", - ): - MultiChannelInstrumentParameter( - [], param_name="x", name="test", names=("a",), shapes=((),) - ) - - def test_multiple_positional_args_warn(self) -> None: - with pytest.warns( - QCoDeSDeprecationWarning, - match="'channels', 'param_name'", - ): - MultiChannelInstrumentParameter( - [], "x", name="test", names=("a",), shapes=((),) - ) - - def test_keyword_args_do_not_warn(self) -> None: - p = MultiChannelInstrumentParameter( - channels=[], param_name="x", name="test", names=("a",), shapes=((),) - ) - assert p.name == "test" - - def test_duplicate_positional_and_keyword_raises(self) -> None: - with pytest.raises( - TypeError, - match="got multiple values for argument 'channels'", - ): - MultiChannelInstrumentParameter( - [], channels=[], param_name="x", name="test", names=("a",), shapes=((),) - ) - - def test_extra_positional_args_forwarded_to_super(self) -> None: - """Args beyond channels/param_name are forwarded to MultiParameter.""" - with pytest.warns(QCoDeSDeprecationWarning) as records: - p = MultiChannelInstrumentParameter([], "x", "test", ("a",), ((),)) - # First warning for channels/param_name, second for names/shapes - messages = [str(r.message) for r in records] - assert any("'channels', 'param_name'" in m for m in messages) - assert any("'names', 'shapes'" in m for m in messages) - assert p._channels == [] - assert p._param_name == "x" - assert p.name == "test" - assert p.names == ("a",) - assert p.shapes == ((),) - - def test_missing_channels_raises(self) -> None: - with pytest.raises( - TypeError, - match="missing required keyword argument: 'channels'", - ): - MultiChannelInstrumentParameter( - param_name="x", name="test", names=("a",), shapes=((),) - ) - - def test_missing_param_name_raises(self) -> None: - with pytest.raises( - TypeError, - match="missing required keyword argument: 'param_name'", - ): - MultiChannelInstrumentParameter( - channels=[], name="test", names=("a",), shapes=((),) - ) - - -class TestElapsedTimeParameterPositionalArgs: - """ElapsedTimeParameter should warn when arguments after ``name`` are positional.""" - - def test_single_positional_arg_warns(self) -> None: - with pytest.warns( - QCoDeSDeprecationWarning, - match="Passing 'label' as positional argument", - ): - ElapsedTimeParameter("test", "My label") - - def test_keyword_args_do_not_warn(self) -> None: - p = ElapsedTimeParameter("test", label="My label") - assert p.name == "test" - assert p.label == "My label" - - def test_duplicate_positional_and_keyword_raises(self) -> None: - with pytest.raises( - TypeError, - match="got multiple values for argument 'label'", - ): - ElapsedTimeParameter("test", "label1", label="label2") - - def test_too_many_positional_args_raises(self) -> None: - with pytest.raises(TypeError, match="takes at most"): - ElapsedTimeParameter("test", "a", "b") - - -class TestInstrumentRefParameterPositionalArgs: - """InstrumentRefParameter should warn when arguments after ``name`` are positional.""" - - def test_single_positional_arg_warns(self) -> None: - with pytest.warns( - QCoDeSDeprecationWarning, - match="Passing 'instrument' as positional argument", - ): - InstrumentRefParameter("test", None) - - def test_multiple_positional_args_warn(self) -> None: - with pytest.warns( - QCoDeSDeprecationWarning, - match="'instrument', 'label'", - ): - InstrumentRefParameter("test", None, "my label") - - def test_keyword_args_do_not_warn(self) -> None: - p = InstrumentRefParameter("test", instrument=None, label="my label") - assert p.name == "test" - assert p.label == "my label" - - def test_duplicate_positional_and_keyword_raises( - self, mock_instrument: _MockInstrument - ) -> None: - with pytest.raises( - TypeError, - match="got multiple values for argument 'instrument'", - ): - InstrumentRefParameter("test", None, instrument=mock_instrument) - - def test_too_many_positional_args_raises(self) -> None: - too_many = (None,) * 15 - with pytest.raises(TypeError, match="takes at most"): - InstrumentRefParameter("test", *too_many) diff --git a/tests/parameter/test_keyword_only_args.py b/tests/parameter/test_keyword_only_args.py new file mode 100644 index 000000000000..3ae82f64cdb2 --- /dev/null +++ b/tests/parameter/test_keyword_only_args.py @@ -0,0 +1,204 @@ +"""Tests that passing positional arguments (beyond ``name``) to +parameter classes is rejected now that *args support has been removed. +""" + +from __future__ import annotations + +from typing import Any + +import pytest + +from qcodes.parameters import ( + ArrayParameter, + ElapsedTimeParameter, + GroupParameter, + InstrumentRefParameter, + MultiParameter, + Parameter, + ParameterBase, +) +from qcodes.parameters.grouped_parameter import ( + DelegateGroup, + DelegateGroupParameter, + GroupedParameter, +) +from qcodes.parameters.multi_channel_instrument_parameter import ( + MultiChannelInstrumentParameter, +) + + +# Minimal concrete subclass of ParameterBase for testing +class _ConcreteParameterBase(ParameterBase): + def get_raw(self) -> Any: + return 0 + + +class TestParameterBaseKeywordOnly: + """ParameterBase requires keyword-only arguments after ``name``.""" + + def test_keyword_args_work(self) -> None: + p = _ConcreteParameterBase("test", instrument=None, snapshot_get=True) + assert p.name == "test" + + def test_positional_args_rejected(self) -> None: + with pytest.raises(TypeError): + _ConcreteParameterBase("test", None) # type: ignore[misc] + + +class TestParameterKeywordOnly: + """Parameter requires keyword-only arguments after ``name``.""" + + def test_keyword_args_work(self) -> None: + p = Parameter("test", instrument=None, label="my label", set_cmd=None) + assert p.name == "test" + assert p.label == "my label" + + def test_positional_args_rejected(self) -> None: + with pytest.raises(TypeError): + Parameter("test", None, set_cmd=None) # type: ignore[misc] + + +# Minimal concrete subclass of ArrayParameter for testing +class _ConcreteArrayParameter(ArrayParameter): + def get_raw(self) -> Any: + return [0] + + +class TestArrayParameterKeywordOnly: + """ArrayParameter requires keyword-only arguments after ``name``.""" + + def test_keyword_args_work(self) -> None: + p = _ConcreteArrayParameter("test", shape=(3,), instrument=None) + assert p.name == "test" + assert p.shape == (3,) + + def test_positional_args_rejected(self) -> None: + with pytest.raises(TypeError): + _ConcreteArrayParameter("test", (3,)) # type: ignore[misc] + + def test_missing_shape_raises(self) -> None: + with pytest.raises(TypeError): + _ConcreteArrayParameter("test") # type: ignore[misc] + + +class TestGroupParameterKeywordOnly: + """GroupParameter requires keyword-only arguments after ``name``.""" + + def test_keyword_args_work(self) -> None: + p = GroupParameter("test", instrument=None, initial_value=None) + assert p.name == "test" + + def test_positional_args_rejected(self) -> None: + with pytest.raises(TypeError): + GroupParameter("test", None) # type: ignore[misc] + + +def _make_delegate_group() -> DelegateGroup: + """Helper to create a minimal DelegateGroup for testing.""" + source = Parameter("source", set_cmd=None, get_cmd=None) + dp = DelegateGroupParameter("dp", source) + return DelegateGroup("grp", parameters=[dp]) + + +class TestGroupedParameterKeywordOnly: + """GroupedParameter requires keyword-only arguments after ``name``.""" + + def test_keyword_args_work(self) -> None: + grp = _make_delegate_group() + p = GroupedParameter("test", group=grp, unit="V") + assert p.name == "test" + assert p.unit == "V" + + def test_positional_args_rejected(self) -> None: + grp = _make_delegate_group() + with pytest.raises(TypeError): + GroupedParameter("test", grp) # type: ignore[misc] + + def test_missing_group_raises(self) -> None: + with pytest.raises(TypeError): + GroupedParameter("test") # type: ignore[misc] + + +# Minimal concrete subclass of MultiParameter for testing +class _ConcreteMultiParameter(MultiParameter): + def get_raw(self) -> Any: + return (0,) + + +class TestMultiParameterKeywordOnly: + """MultiParameter requires keyword-only arguments after ``name``.""" + + def test_keyword_args_work(self) -> None: + p = _ConcreteMultiParameter("test", names=("a",), shapes=((),)) + assert p.name == "test" + assert p.names == ("a",) + + def test_positional_args_rejected(self) -> None: + with pytest.raises(TypeError): + _ConcreteMultiParameter("test", ("a",), ((),)) # type: ignore[misc] + + def test_missing_names_raises(self) -> None: + with pytest.raises(TypeError): + _ConcreteMultiParameter("test", shapes=((),)) # type: ignore[misc] + + def test_missing_shapes_raises(self) -> None: + with pytest.raises(TypeError): + _ConcreteMultiParameter("test", names=("a",)) # type: ignore[misc] + + +class TestMultiChannelInstrumentParameterKeywordOnly: + """MultiChannelInstrumentParameter requires keyword-only arguments.""" + + def test_keyword_args_work(self) -> None: + p = MultiChannelInstrumentParameter( + channels=[], param_name="x", name="test", names=("a",), shapes=((),) + ) + assert p.name == "test" + + def test_positional_args_rejected(self) -> None: + with pytest.raises(TypeError): + MultiChannelInstrumentParameter( + [], # pyright: ignore[reportCallIssue] + "x", + name="test", + names=("a",), + shapes=((),), + ) + + def test_missing_channels_raises(self) -> None: + with pytest.raises(TypeError): + MultiChannelInstrumentParameter( # type: ignore[call-arg] + param_name="x", name="test", names=("a",), shapes=((),) + ) + + def test_missing_param_name_raises(self) -> None: + with pytest.raises(TypeError): + MultiChannelInstrumentParameter( # type: ignore[call-arg] + channels=[], name="test", names=("a",), shapes=((),) + ) + + +class TestElapsedTimeParameterKeywordOnly: + """ElapsedTimeParameter requires keyword-only arguments after ``name``.""" + + def test_keyword_args_work(self) -> None: + p = ElapsedTimeParameter("test", label="My label") + assert p.name == "test" + assert p.label == "My label" + + def test_positional_args_rejected(self) -> None: + with pytest.raises(TypeError): + ElapsedTimeParameter("test", "My label") # type: ignore[misc] + + +class TestInstrumentRefParameterKeywordOnly: + """InstrumentRefParameter requires keyword-only arguments after ``name``.""" + + def test_keyword_args_work(self) -> None: + p = InstrumentRefParameter("test", instrument=None, label="my label") + assert p.name == "test" + assert p.label == "my label" + + def test_positional_args_rejected(self) -> None: + with pytest.raises(TypeError): + InstrumentRefParameter("test", None) # type: ignore[misc]