diff --git a/docs/changes/0.50.1.rst b/docs/changes/0.50.1.rst new file mode 100644 index 00000000000..78c416c9449 --- /dev/null +++ b/docs/changes/0.50.1.rst @@ -0,0 +1,8 @@ +QCoDeS 0.50.1 (2024-11-28) +========================== + +Improved: +--------- + +- Fix a regression introduced in 0.50.0 where a DelegateParameter initialized with a None source + would not correctly call get/set on the source parameter when this has been set. (:pr:`6671`) diff --git a/docs/changes/index.rst b/docs/changes/index.rst index ff80a60e478..47efc56f391 100644 --- a/docs/changes/index.rst +++ b/docs/changes/index.rst @@ -3,6 +3,7 @@ Changelogs .. toctree:: Unreleased + 0.50.1 <0.50.1> 0.50.0 <0.50.0> 0.49.0 <0.49.0> 0.48.0 <0.48.0> diff --git a/src/qcodes/parameters/parameter.py b/src/qcodes/parameters/parameter.py index 29565201fe9..327b038515d 100644 --- a/src/qcodes/parameters/parameter.py +++ b/src/qcodes/parameters/parameter.py @@ -245,7 +245,9 @@ def _set_manual_parameter( **kwargs, ) - no_instrument_get = not self.gettable and (get_cmd is None or get_cmd is False) + no_instrument_get = not self._implements_get_raw and ( + get_cmd is None or get_cmd is False + ) # TODO: a matching check should be in ParameterBase but # due to the current limited design the ParameterBase cannot # know if this subclass will supply a get_cmd @@ -261,13 +263,13 @@ def _set_manual_parameter( # in the scope of this class. # (previous call to `super().__init__` wraps existing # get_raw/set_raw into get/set methods) - if self.gettable and get_cmd not in (None, False): + if self._implements_get_raw and get_cmd not in (None, False): raise TypeError( "Supplying a not None or False `get_cmd` to a Parameter" " that already implements" " get_raw is an error." ) - elif not self.gettable and get_cmd is not False: + elif not self._implements_get_raw and get_cmd is not False: if get_cmd is None: # ignore typeerror since mypy does not allow setting a method dynamically self.get_raw = MethodType(_get_manual_parameter, self) # type: ignore[method-assign] @@ -293,13 +295,13 @@ def _set_manual_parameter( # this may be resolvable if Command above is correctly wrapped in MethodType self.get = self._wrap_get(self.get_raw) # type: ignore[arg-type] - if self.settable and set_cmd not in (None, False): + if self._implements_set_raw and set_cmd not in (None, False): raise TypeError( "Supplying a not None or False `set_cmd` to a Parameter" " that already implements" " set_raw is an error." ) - elif not self.settable and set_cmd is not False: + elif not self._implements_set_raw 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] diff --git a/src/qcodes/parameters/parameter_base.py b/src/qcodes/parameters/parameter_base.py index b4358caa3cf..730289a26d7 100644 --- a/src/qcodes/parameters/parameter_base.py +++ b/src/qcodes/parameters/parameter_base.py @@ -263,11 +263,8 @@ def __init__( self.get_latest = GetLatest(self) self.get: Callable[..., ParamDataType] - implements_get_raw = hasattr(self, "get_raw") and not getattr( - self.get_raw, "__qcodes_is_abstract_method__", False - ) self._gettable = False - if implements_get_raw: + if self._implements_get_raw: self.get = self._wrap_get(self.get_raw) self._gettable = True elif hasattr(self, "get"): @@ -278,11 +275,8 @@ def __init__( ) self.set: Callable[..., None] - implements_set_raw = hasattr(self, "set_raw") and not getattr( - self.set_raw, "__qcodes_is_abstract_method__", False - ) self._settable: bool = False - if implements_set_raw: + if self._implements_set_raw: self.set = self._wrap_set(self.set_raw) self._settable = True elif hasattr(self, "set"): @@ -347,6 +341,20 @@ def __init__( instrument.parameters[name] = self + @property + def _implements_get_raw(self) -> bool: + implements_get_raw = hasattr(self, "get_raw") and not getattr( + self.get_raw, "__qcodes_is_abstract_method__", False + ) + return implements_get_raw + + @property + def _implements_set_raw(self) -> bool: + implements_set_raw = hasattr(self, "set_raw") and not getattr( + self.set_raw, "__qcodes_is_abstract_method__", False + ) + return implements_set_raw + def _build__doc__(self) -> str | None: return self.__doc__ diff --git a/tests/parameter/test_delegate_parameter.py b/tests/parameter/test_delegate_parameter.py index 8cf89bd26e8..f0d66e0e3fe 100644 --- a/tests/parameter/test_delegate_parameter.py +++ b/tests/parameter/test_delegate_parameter.py @@ -271,6 +271,23 @@ def test_delegate_cache_pristine_if_not_set() -> None: assert gotten_delegate_cache is None +def test_delegate_get_instrument_val(numeric_val: int) -> None: + """ + Delegate should call its source to get value rather than just reading source cache + """ + initial_value = numeric_val + t = ObservableParam("observable_parameter", initial_value=initial_value) + # delegate has no source initially to make sure it's not gettable on initialization + d = DelegateParameter("delegate", source=None) + d.source = t + + new_instr_value = 3 + # Update instrument value without changing parameter cache + t.instr_val = new_instr_value + # This check fails if delegate only reads source cache + assert d() == new_instr_value + + def test_delegate_get_updates_cache( make_observable_parameter: Callable[..., ObservableParam], numeric_val: int ) -> None: