Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DelegateParameters inherit their source's settable/gettable/snapshot_value/unit/label #6648

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions docs/changes/newsfragments/6648.improved
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DelegateParameter now reads delegated properties from source rather than setting them at source change.
This resolves an issue when delegating from another delegate parameter may result in incorrect attributes.
93 changes: 61 additions & 32 deletions src/qcodes/parameters/delegate_parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,19 +156,6 @@ def __init__(
if "bind_to_instrument" not in kwargs.keys():
kwargs["bind_to_instrument"] = False

self._attr_inherit = {
"label": {"fixed": False, "value_when_without_source": name},
"unit": {"fixed": False, "value_when_without_source": ""},
}

for attr, attr_props in self._attr_inherit.items():
if attr in kwargs:
attr_props["fixed"] = True
else:
attr_props["fixed"] = False
source_attr = getattr(source, attr, attr_props["value_when_without_source"])
kwargs[attr] = kwargs.get(attr, source_attr)

for cmd in ("set_cmd", "get_cmd"):
if cmd in kwargs:
raise KeyError(
Expand All @@ -188,9 +175,14 @@ def __init__(
initial_cache_value = kwargs.pop("initial_cache_value", None)
self.source = source
super().__init__(name, *args, **kwargs)
# explicitly set the source properties as
# init will overwrite the ones set when assigning source
self._set_properties_from_source(source)
self.label = kwargs.get("label", None)
self.unit = kwargs.get("unit", None)

# Hack While we inherit the settable status from the parent parameter
# we do allow param.set_to to temporary override _settable in a
# context. Here _settable should always be true except when set_to
# i.e. _SetParamContext overrides it
self._settable = True

self.cache = self._DelegateCache(self)
if initial_cache_value is not None:
Expand All @@ -209,25 +201,62 @@ def source(self) -> Parameter | None:

@source.setter
def source(self, source: Parameter | None) -> None:
self._set_properties_from_source(source)
self._source: Parameter | None = source

def _set_properties_from_source(self, source: Parameter | None) -> None:
if source is None:
self._gettable = False
self._settable = False
self._snapshot_value = False
@property
def snapshot_value(self) -> bool:
if self.source is None:
return False
return self.source.snapshot_value

@property
def unit(self) -> str:
"""
The unit of measure. Read from source if not explicitly overwritten.
Set to None to disable overwrite.
"""
if self._unit_override is not None:
return self._unit_override
elif self.source is not None:
return self.source.unit
else:
self._gettable = source.gettable
self._settable = source.settable
self._snapshot_value = source._snapshot_value

for attr, attr_props in self._attr_inherit.items():
if not attr_props["fixed"]:
attr_val = getattr(
source, attr, attr_props["value_when_without_source"]
)
setattr(self, attr, attr_val)
return ""

@unit.setter
def unit(self, unit: str | None) -> None:
self._unit_override = unit

@property
def label(self) -> str:
"""
Label of the data used for plots etc.
Read from source if not explicitly overwritten.
Set to None to disable overwrite.
"""
if self._label_override is not None:
return self._label_override
elif self.source is not None:
return self.source.label
else:
return self.name

@label.setter
def label(self, label: str | None) -> None:
self._label_override = label

@property
def gettable(self) -> bool:
if self.source is None:
return False
return self.source.gettable

@property
def settable(self) -> bool:
if self._settable is False:
return False
if self.source is None:
return False
return self.source.settable

def get_raw(self) -> Any:
if self.source is None:
Expand Down
2 changes: 1 addition & 1 deletion src/qcodes/parameters/parameter_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ def snapshot_base(

state: dict[str, Any] = {"__class__": full_class(self), "full_name": str(self)}

if self._snapshot_value:
if self.snapshot_value:
has_get = self.gettable
allowed_to_call_get_when_snapshotting = (
self._snapshot_get and update is not False
Expand Down
65 changes: 63 additions & 2 deletions tests/parameter/test_delegate_parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ def test_gettable_settable_snapshotget_delegate_parameter(
delegate_param = DelegateParameter("delegate", source=source_param)
assert delegate_param.gettable is gettable
assert delegate_param.settable is settable
assert delegate_param._snapshot_value is snapshot_value
assert delegate_param.snapshot_value is snapshot_value


@pytest.mark.parametrize("snapshot_value", [True, False])
Expand All @@ -450,7 +450,7 @@ def test_gettable_settable_snapshotget_delegate_parameter_2(
delegate_param.source = source_param
assert delegate_param.gettable is gettable
assert delegate_param.settable is settable
assert delegate_param._snapshot_value is snapshot_value
assert delegate_param.snapshot_value is snapshot_value


def test_initial_value_and_none_source_raises() -> None:
Expand Down Expand Up @@ -624,3 +624,64 @@ def test_value_validation_with_offset_and_scale() -> None:
delegate_param.validate(1) # raw_value = 100
with pytest.raises(ValueError):
delegate_param.set(1)


def test_delegate_of_delegate_updates_settable_gettable():
gettable_settable_source_param = Parameter(
"source", set_cmd=None, get_cmd=None, vals=vals.Numbers(-5, 5)
)
not_gettable_source_param = Parameter(
"source", set_cmd=None, get_cmd=False, vals=vals.Numbers(-5, 5)
)
not_settable_source_param = Parameter(
"source", set_cmd=False, get_cmd=None, vals=vals.Numbers(-5, 5)
)
delegate_param_inner = DelegateParameter(
"delegate_inner", source=None, vals=vals.Numbers(-10, 10)
)
delegate_param_outer = DelegateParameter(
"delegate_outer", source=None, vals=vals.Numbers(-10, 10)
)
delegate_param_outer.source = delegate_param_inner
delegate_param_inner.source = gettable_settable_source_param

assert delegate_param_outer.gettable
assert delegate_param_outer.settable

delegate_param_inner.source = not_gettable_source_param

assert not delegate_param_outer.gettable
assert delegate_param_outer.settable

delegate_param_inner.source = not_settable_source_param

assert delegate_param_outer.gettable
assert not delegate_param_outer.settable


def test_delegate_parameter_context() -> None:
gettable_settable_source_param = Parameter(
"source", set_cmd=None, get_cmd=None, vals=vals.Numbers(-5, 5)
)

delegate_param = DelegateParameter(
"delegate_outer", source=None, vals=vals.Numbers(-10, 10)
)

delegate_param.source = gettable_settable_source_param

delegate_param(2)
assert delegate_param() == 2

with delegate_param.set_to(3):
assert delegate_param() == 3
with pytest.raises(NotImplementedError):
delegate_param(4)
assert delegate_param() == 3
assert delegate_param() == 2

with delegate_param.set_to(3, allow_changes=True):
assert delegate_param() == 3
delegate_param(4)
assert delegate_param() == 4
assert delegate_param() == 2
Loading