from __future__ import annotations
from collections.abc import Sequence
from datetime import datetime
from typing import Any
from .parameter import Parameter
from .parameter_base import ParamDataType, ParamRawDataType
[docs]class DelegateParameter(Parameter):
"""
The :class:`.DelegateParameter` wraps a given `source` :class:`Parameter`.
Setting/getting it results in a set/get of the source parameter with
the provided arguments.
The reason for using a :class:`DelegateParameter` instead of the
source parameter is to provide all the functionality of the Parameter
base class without overwriting properties of the source: for example to
set a different scaling factor and unit on the :class:`.DelegateParameter`
without changing those in the source parameter.
The :class:`DelegateParameter` supports changing the `source`
:class:`Parameter`. :py:attr:`~gettable`, :py:attr:`~settable` and
:py:attr:`snapshot_value` properties automatically follow the source
parameter. If source is set to ``None`` :py:attr:`~gettable` and
:py:attr:`~settable` will always be ``False``. It is therefore an error
to call get and set on a :class:`DelegateParameter` without a `source`.
Note that a parameter without a source can be snapshotted correctly.
:py:attr:`.unit` and :py:attr:`.label` can either be set when constructing
a :class:`DelegateParameter` or inherited from the source
:class:`Parameter`. If inherited they will automatically change when
changing the source. Otherwise they will remain fixed.
Note:
DelegateParameter only supports mappings between the
:class:`.DelegateParameter` and :class:`.Parameter` that are invertible
(e.g. a bijection). It is therefor not allowed to create a
:class:`.DelegateParameter` that performs non invertible
transforms in its ``get_raw`` method.
A DelegateParameter is not registered on the instrument by default.
You should pass ``bind_to_instrument=True`` if you want this to
be the case.
"""
class _DelegateCache:
def __init__(self, parameter: DelegateParameter):
self._parameter = parameter
self._marked_valid: bool = False
@property
def raw_value(self) -> ParamRawDataType:
"""
raw_value is an attribute that surfaces the raw value from the
cache. In the case of a :class:`DelegateParameter` it reflects
the value of the cache of the source.
Strictly speaking it should represent that value independent of
its validity according to the `max_val_age` but in fact it does
lose its validity when the maximum value age has been reached.
This bug will not be fixed since the `raw_value` property will be
removed soon.
"""
if self._parameter.source is None:
raise TypeError(
"Cannot get the raw value of a "
"DelegateParameter that delegates to None"
)
return self._parameter.source.cache.get(get_if_invalid=False)
@property
def max_val_age(self) -> float | None:
if self._parameter.source is None:
return None
return self._parameter.source.cache.max_val_age
@property
def timestamp(self) -> datetime | None:
if self._parameter.source is None:
return None
return self._parameter.source.cache.timestamp
@property
def valid(self) -> bool:
if self._parameter.source is None:
return False
source_cache = self._parameter.source.cache
return source_cache.valid
def invalidate(self) -> None:
if self._parameter.source is not None:
self._parameter.source.cache.invalidate()
def get(self, get_if_invalid: bool = True) -> ParamDataType:
if self._parameter.source is None:
raise TypeError(
"Cannot get the cache of a "
"DelegateParameter that delegates to None"
)
return self._parameter._from_raw_value_to_value(
self._parameter.source.cache.get(get_if_invalid=get_if_invalid)
)
def set(self, value: ParamDataType) -> None:
if self._parameter.source is None:
raise TypeError(
"Cannot set the cache of a DelegateParameter "
"that delegates to None"
)
self._parameter.validate(value)
self._parameter.source.cache.set(
self._parameter._from_value_to_raw_value(value)
)
def _set_from_raw_value(self, raw_value: ParamRawDataType) -> None:
if self._parameter.source is None:
raise TypeError(
"Cannot set the cache of a DelegateParameter "
"that delegates to None"
)
self._parameter.source.cache.set(raw_value)
def _update_with(
self,
*,
value: ParamDataType,
raw_value: ParamRawDataType,
timestamp: datetime | None = None,
) -> None:
"""
This method is needed for interface consistency with ``._Cache``
because it is used by ``ParameterBase`` in
``_wrap_get``/``_wrap_set``. Due to the fact that the source
parameter already maintains it's own cache and the cache of the
delegate parameter mirrors the cache of the source parameter by
design, this method is just a noop.
"""
pass
def __call__(self) -> ParamDataType:
return self.get(get_if_invalid=True)
def __init__(
self,
name: str,
source: Parameter | None,
*args: Any,
**kwargs: Any,
):
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(
f'It is not allowed to set "{cmd}" of a '
f"DelegateParameter because the one of the "
f"source parameter is supposed to be used."
)
if source is None and (
"initial_cache_value" in kwargs or "initial_value" in kwargs
):
raise KeyError(
"It is not allowed to supply 'initial_value'"
" or 'initial_cache_value' "
"without a source."
)
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.cache = self._DelegateCache(self)
if initial_cache_value is not None:
self.cache.set(initial_cache_value)
@property
def source(self) -> Parameter | None:
"""
The source parameter that this :class:`DelegateParameter` is bound to
or ``None`` if this :class:`DelegateParameter` is unbound.
:getter: Returns the current source.
:setter: Sets the source.
"""
return self._source
@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
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)
# pylint: disable=method-hidden
[docs] def get_raw(self) -> Any:
if self.source is None:
raise TypeError(
"Cannot get the value of a DelegateParameter "
"that delegates to a None source."
)
return self.source.get()
# pylint: disable=method-hidden
[docs] def set_raw(self, value: Any) -> None:
if self.source is None:
raise TypeError(
"Cannot set the value of a DelegateParameter "
"that delegates to a None source."
)
self.source(value)
[docs] def snapshot_base(
self,
update: bool | None = True,
params_to_skip_update: Sequence[str] | None = None,
) -> dict[Any, Any]:
snapshot = super().snapshot_base(
update=update, params_to_skip_update=params_to_skip_update
)
source_parameter_snapshot = (
None if self.source is None else self.source.snapshot(update=update)
)
snapshot.update({"source_parameter": source_parameter_snapshot})
return snapshot
[docs] def validate(self, value: ParamDataType) -> None:
"""
Validate the supplied value.
If it has a source parameter, validate the value as well with the source validator.
Args:
value: value to validate
Raises:
TypeError: If the value is of the wrong type.
ValueError: If the value is outside the bounds specified by the
validator.
"""
super().validate(value)
if self.source is not None:
self.source.validate(self._from_value_to_raw_value(value))