-
Notifications
You must be signed in to change notification settings - Fork 322
/
Copy pathdelegate_parameter.py
367 lines (312 loc) · 12.9 KB
/
delegate_parameter.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
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
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 root_source(self) -> Parameter | None:
"""
The root source parameter that this :class:`DelegateParameter` is bound to
or ``None`` if this :class:`DelegateParameter` is unbound. If
the source is it self a DelegateParameter it will recursively return that Parameter's
source until a non DelegateParameter is found. For a non DelegateParameter source
this behaves the same as ``self.source``
:getter: Returns the current source.
:setter: Sets the source of the first parameter in the tree that has a non-DelegateParameter source.
"""
if isinstance(self.source, DelegateParameter):
return self.source.root_source
else:
return self.source
@root_source.setter
def root_source(self, source: Parameter | None) -> None:
self.root_delegate.source = source
@property
def root_delegate(self) -> DelegateParameter:
"""
If this parameter is part of a chain of DelegateParameters return
the first Parameter in the chain that has a non DelegateParameter source
else return self.
"""
if not isinstance(self.source, DelegateParameter):
return self
else:
return self.source.root_delegate
@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
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()
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)
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
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