Source code for qcodes.parameters.delegate_parameter
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from .parameter import Parameter
if TYPE_CHECKING:
    from collections.abc import Sequence
    from datetime import datetime
    from qcodes.validators.validators import Validator
    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
        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)
        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:
            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._source: Parameter | None = source
    @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:
            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
[docs]
    def get_raw(self) -> Any:
        logger = self._get_logger()
        logger.debug(
            "Calling get on DelegateParameter %s with source %s",
            self.full_name,
            self.source,
        )
        if self.source is None:
            raise TypeError(
                "Cannot get the value of a DelegateParameter "
                "that delegates to a None source."
            )
        return self.source.get() 
[docs]
    def set_raw(self, value: Any) -> None:
        logger = self._get_logger()
        logger.debug(
            "Calling set on DelegateParameter %s with source %s",
            self.full_name,
            self.source,
        )
        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)) 
    @property
    def validators(self) -> tuple[Validator, ...]:
        """
        Tuple of all validators associated with the parameter. Note that this
        includes validators of the source parameter if source parameter is set
        and has any validators.
        :getter: All validators associated with the parameter.
        """
        source_validators: tuple[Validator, ...] = (
            self.source.validators if self.source is not None else ()
        )
        return tuple(self._vals) + source_validators