spectre-core 0.0.12__py3-none-any.whl → 0.0.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spectre_core/_file_io/__init__.py +1 -3
- spectre_core/_file_io/file_handlers.py +163 -58
- spectre_core/batches/__init__.py +10 -11
- spectre_core/batches/_base.py +170 -78
- spectre_core/batches/_batches.py +149 -99
- spectre_core/batches/_factory.py +56 -14
- spectre_core/batches/_register.py +23 -8
- spectre_core/batches/plugins/_batch_keys.py +16 -0
- spectre_core/batches/plugins/_callisto.py +183 -0
- spectre_core/batches/plugins/_iq_stream.py +354 -0
- spectre_core/capture_configs/__init__.py +17 -13
- spectre_core/capture_configs/_capture_config.py +93 -34
- spectre_core/capture_configs/_capture_modes.py +22 -0
- spectre_core/capture_configs/_capture_templates.py +207 -122
- spectre_core/capture_configs/_parameters.py +115 -42
- spectre_core/capture_configs/_pconstraints.py +86 -35
- spectre_core/capture_configs/_pnames.py +49 -0
- spectre_core/capture_configs/_ptemplates.py +389 -346
- spectre_core/capture_configs/_pvalidators.py +117 -73
- spectre_core/config/__init__.py +6 -8
- spectre_core/config/_paths.py +65 -25
- spectre_core/config/_time_formats.py +15 -10
- spectre_core/exceptions.py +2 -4
- spectre_core/jobs/__init__.py +14 -0
- spectre_core/jobs/_jobs.py +111 -0
- spectre_core/jobs/_workers.py +171 -0
- spectre_core/logs/__init__.py +17 -0
- spectre_core/logs/_configure.py +67 -0
- spectre_core/logs/_decorators.py +33 -0
- spectre_core/logs/_logs.py +228 -0
- spectre_core/logs/_process_types.py +14 -0
- spectre_core/plotting/__init__.py +4 -2
- spectre_core/plotting/_base.py +204 -102
- spectre_core/plotting/_format.py +17 -4
- spectre_core/plotting/_panel_names.py +18 -0
- spectre_core/plotting/_panel_stack.py +167 -53
- spectre_core/plotting/_panels.py +341 -141
- spectre_core/post_processing/__init__.py +8 -6
- spectre_core/post_processing/_base.py +70 -44
- spectre_core/post_processing/_factory.py +42 -12
- spectre_core/post_processing/_post_processor.py +24 -26
- spectre_core/post_processing/_register.py +22 -6
- spectre_core/post_processing/plugins/_event_handler_keys.py +16 -0
- spectre_core/post_processing/plugins/_fixed_center_frequency.py +129 -0
- spectre_core/post_processing/{library → plugins}/_swept_center_frequency.py +215 -143
- spectre_core/py.typed +0 -0
- spectre_core/receivers/__init__.py +10 -7
- spectre_core/receivers/_base.py +220 -69
- spectre_core/receivers/_factory.py +53 -7
- spectre_core/receivers/_register.py +30 -9
- spectre_core/receivers/_spec_names.py +26 -15
- spectre_core/receivers/plugins/__init__.py +0 -0
- spectre_core/receivers/plugins/_receiver_names.py +16 -0
- spectre_core/receivers/plugins/_rsp1a.py +59 -0
- spectre_core/receivers/plugins/_rspduo.py +67 -0
- spectre_core/receivers/plugins/_sdrplay_receiver.py +190 -0
- spectre_core/receivers/plugins/_test.py +218 -0
- spectre_core/receivers/plugins/gr/_base.py +80 -0
- spectre_core/receivers/{gr → plugins/gr}/_rsp1a.py +42 -52
- spectre_core/receivers/{gr → plugins/gr}/_rspduo.py +61 -74
- spectre_core/receivers/{gr → plugins/gr}/_test.py +33 -31
- spectre_core/spectrograms/__init__.py +5 -3
- spectre_core/spectrograms/_analytical.py +121 -66
- spectre_core/spectrograms/_array_operations.py +103 -36
- spectre_core/spectrograms/_spectrogram.py +380 -207
- spectre_core/spectrograms/_transform.py +197 -169
- spectre_core/wgetting/__init__.py +4 -2
- spectre_core/wgetting/_callisto.py +173 -118
- {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/METADATA +14 -7
- spectre_core-0.0.13.dist-info/RECORD +75 -0
- {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/WHEEL +1 -1
- spectre_core/batches/library/_callisto.py +0 -96
- spectre_core/batches/library/_fixed_center_frequency.py +0 -133
- spectre_core/batches/library/_swept_center_frequency.py +0 -105
- spectre_core/logging/__init__.py +0 -11
- spectre_core/logging/_configure.py +0 -35
- spectre_core/logging/_decorators.py +0 -19
- spectre_core/logging/_log_handlers.py +0 -176
- spectre_core/post_processing/library/_fixed_center_frequency.py +0 -114
- spectre_core/receivers/gr/_base.py +0 -33
- spectre_core/receivers/library/_rsp1a.py +0 -61
- spectre_core/receivers/library/_rspduo.py +0 -69
- spectre_core/receivers/library/_sdrplay_receiver.py +0 -185
- spectre_core/receivers/library/_test.py +0 -221
- spectre_core-0.0.12.dist-info/RECORD +0 -64
- /spectre_core/receivers/{gr → plugins/gr}/__init__.py +0 -0
- {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/LICENSE +0 -0
- {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/top_level.txt +0 -0
@@ -3,168 +3,247 @@
|
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
5
|
from copy import deepcopy
|
6
|
-
from typing import Any
|
7
|
-
from dataclasses import dataclass
|
6
|
+
from typing import Any, Iterator
|
8
7
|
|
8
|
+
from ._capture_modes import CaptureMode
|
9
9
|
from ._parameters import Parameter, Parameters
|
10
|
-
from ._pconstraints import
|
11
|
-
from ._ptemplates import
|
12
|
-
|
13
|
-
PNames,
|
14
|
-
get_base_ptemplate
|
15
|
-
)
|
10
|
+
from ._pconstraints import BasePConstraint
|
11
|
+
from ._ptemplates import PTemplate, get_base_ptemplate
|
12
|
+
from ._ptemplates import PName
|
16
13
|
|
17
14
|
class CaptureTemplate:
|
18
|
-
"""A managed collection of
|
19
|
-
|
20
|
-
|
15
|
+
"""A managed collection of parameter templates. Strictly defines what parameters are required
|
16
|
+
in a capture config, and the values each parameter can take.
|
17
|
+
"""
|
18
|
+
def __init__(
|
19
|
+
self
|
20
|
+
) -> None:
|
21
|
+
"""Initialise a `CaptureTemplate` instance.
|
22
|
+
"""
|
23
|
+
self._ptemplates: dict[PName, PTemplate] = {}
|
21
24
|
|
22
25
|
|
23
26
|
@property
|
24
|
-
def name_list(
|
25
|
-
|
27
|
+
def name_list(
|
28
|
+
self
|
29
|
+
) -> list[PName]:
|
30
|
+
"""The names of all required parameters in the capture template."""
|
26
31
|
return list(self._ptemplates.keys())
|
27
32
|
|
28
33
|
|
29
|
-
def add_ptemplate(
|
30
|
-
|
31
|
-
|
34
|
+
def add_ptemplate(
|
35
|
+
self,
|
36
|
+
ptemplate: PTemplate
|
37
|
+
) -> None:
|
38
|
+
"""Add a parameter template to the capture template.
|
39
|
+
|
40
|
+
:param ptemplate: Describes a required parameter for this capture template.
|
41
|
+
"""
|
32
42
|
self._ptemplates[ptemplate.name] = ptemplate
|
33
43
|
|
34
44
|
|
35
|
-
def get_ptemplate(
|
36
|
-
|
37
|
-
|
45
|
+
def get_ptemplate(
|
46
|
+
self,
|
47
|
+
parameter_name: PName
|
48
|
+
) -> PTemplate:
|
49
|
+
"""Get the parameter template corresponding to the parameter with the name `parameter_name`.
|
50
|
+
|
51
|
+
:param parameter_name: The name of the parameter.
|
52
|
+
:return: The corresponding `PTemplate` instance.
|
53
|
+
:raises ValueError: If the parameter name is not found in the template.
|
54
|
+
"""
|
38
55
|
if parameter_name not in self._ptemplates:
|
39
56
|
raise ValueError(f"Parameter with name '{parameter_name}' is not found in the template. "
|
40
57
|
f"Expected one of {self.name_list}")
|
41
58
|
return self._ptemplates[parameter_name]
|
42
59
|
|
43
60
|
|
44
|
-
def __apply_parameter_template(
|
45
|
-
|
46
|
-
|
61
|
+
def __apply_parameter_template(
|
62
|
+
self,
|
63
|
+
parameter: Parameter
|
64
|
+
) -> None:
|
65
|
+
"""Apply the corresponding parameter template to the input parameter.
|
66
|
+
|
67
|
+
As a side effect, the value of the input parameter will be type cast
|
68
|
+
according to the template.
|
69
|
+
"""
|
47
70
|
ptemplate = self.get_ptemplate(parameter.name)
|
48
71
|
parameter.value = ptemplate.apply_template(parameter.value)
|
49
72
|
|
50
73
|
|
51
|
-
def __apply_parameter_templates(
|
52
|
-
|
53
|
-
|
74
|
+
def __apply_parameter_templates(
|
75
|
+
self,
|
76
|
+
parameters: Parameters
|
77
|
+
) -> None:
|
78
|
+
"""Apply the corresponding parameter template to each of the input parameters."""
|
54
79
|
for parameter in parameters:
|
55
80
|
self.__apply_parameter_template(parameter)
|
56
81
|
|
57
82
|
|
58
|
-
def __fill_missing_with_defaults(
|
59
|
-
|
60
|
-
|
83
|
+
def __fill_missing_with_defaults(
|
84
|
+
self,
|
85
|
+
parameters: Parameters
|
86
|
+
) -> None:
|
87
|
+
"""Add default parameters to `parameters` for any missing entries.
|
88
|
+
|
89
|
+
Missing parameters are identified by comparing `parameters` against the
|
90
|
+
current capture template. Defaults are derived from the corresponding
|
91
|
+
parameter templates.
|
92
|
+
"""
|
61
93
|
for ptemplate in self:
|
62
94
|
if ptemplate.name not in parameters.name_list:
|
95
|
+
# no args for `make_parameter` implies the parameter with the default value will be used.
|
63
96
|
parameter = ptemplate.make_parameter()
|
64
97
|
parameters.add_parameter(parameter.name,
|
65
98
|
parameter.value)
|
66
99
|
|
67
100
|
|
68
|
-
def apply_template(
|
69
|
-
|
70
|
-
|
71
|
-
|
101
|
+
def apply_template(
|
102
|
+
self,
|
103
|
+
parameters: Parameters
|
104
|
+
) -> Parameters:
|
105
|
+
"""Apply the capture template to the input parameters. This involves:
|
106
|
+
|
107
|
+
- Adding default parameters if they are missing with respect to this template.
|
108
|
+
- Type casting the value of each input parameter according to the corresponding parameter template.
|
109
|
+
- Validating the value of each input parameter against any corresponding pconstraints.
|
110
|
+
|
111
|
+
:param parameters: The parameters to apply this capture template to.
|
112
|
+
:return: A `Parameters` instance compliant with this template.
|
113
|
+
"""
|
72
114
|
self.__fill_missing_with_defaults(parameters)
|
115
|
+
self.__apply_parameter_templates(parameters)
|
73
116
|
return parameters
|
74
117
|
|
75
118
|
|
76
|
-
def __iter__(
|
77
|
-
|
119
|
+
def __iter__(
|
120
|
+
self
|
121
|
+
) -> Iterator[PTemplate]:
|
122
|
+
"""Iterate over stored ptemplates."""
|
78
123
|
yield from self._ptemplates.values()
|
79
124
|
|
80
125
|
|
81
|
-
def set_default(
|
82
|
-
|
83
|
-
|
84
|
-
|
126
|
+
def set_default(
|
127
|
+
self,
|
128
|
+
parameter_name: PName,
|
129
|
+
default: Any
|
130
|
+
) -> None:
|
131
|
+
"""Set the default of an existing parameter template.
|
132
|
+
|
133
|
+
:param parameter_name: The name of the parameter template to be updated.
|
134
|
+
:param default: The new default value.
|
135
|
+
"""
|
85
136
|
self.get_ptemplate(parameter_name).default = default
|
86
137
|
|
87
138
|
|
88
|
-
def set_defaults(
|
89
|
-
|
90
|
-
|
139
|
+
def set_defaults(
|
140
|
+
self,
|
141
|
+
*ptuples: tuple[PName, Any]
|
142
|
+
) -> None:
|
143
|
+
"""Update the defaults of multiple parameter templates.
|
144
|
+
|
145
|
+
:param ptuples: Tuples of the form (`parameter_name`, `new_default`) to update defaults.
|
146
|
+
"""
|
91
147
|
for (parameter_name, default) in ptuples:
|
92
148
|
self.set_default(parameter_name, default)
|
93
149
|
|
94
150
|
|
95
|
-
def enforce_default(
|
96
|
-
|
97
|
-
|
151
|
+
def enforce_default(
|
152
|
+
self,
|
153
|
+
parameter_name: PName
|
154
|
+
) -> None:
|
155
|
+
"""Set the `enforce_default` attribute of an existing parameter template to True.
|
156
|
+
|
157
|
+
:param parameter_name: The name of the parameter template to enforce its default value.
|
158
|
+
"""
|
98
159
|
self.get_ptemplate(parameter_name).enforce_default = True
|
99
160
|
|
100
161
|
|
101
|
-
def enforce_defaults(
|
102
|
-
|
103
|
-
|
162
|
+
def enforce_defaults(
|
163
|
+
self,
|
164
|
+
*parameter_names: PName
|
165
|
+
) -> None:
|
166
|
+
"""Set the `enforce_default` attribute of multiple existing parameter templates to True.
|
167
|
+
|
168
|
+
:param parameter_names: The names of the parameter templates to enforce their default values.
|
169
|
+
"""
|
104
170
|
for name in parameter_names:
|
105
171
|
self.enforce_default(name)
|
106
172
|
|
107
173
|
|
108
|
-
def add_pconstraint(
|
109
|
-
|
110
|
-
|
111
|
-
|
174
|
+
def add_pconstraint(
|
175
|
+
self,
|
176
|
+
parameter_name: PName,
|
177
|
+
pconstraints: list[BasePConstraint]
|
178
|
+
) -> None:
|
179
|
+
"""Add one or more `PConstraint` instances to an existing parameter template.
|
180
|
+
|
181
|
+
:param parameter_name: The name of the parameter template to add constraints to.
|
182
|
+
:param pconstraints: A list of `PConstraint` instances to be added.
|
183
|
+
"""
|
112
184
|
for pconstraint in pconstraints:
|
113
185
|
self.get_ptemplate(parameter_name).add_pconstraint(pconstraint)
|
114
186
|
|
115
187
|
|
116
|
-
def to_dict(
|
117
|
-
|
188
|
+
def to_dict(
|
189
|
+
self
|
190
|
+
) -> dict[str, dict[str, str]]:
|
191
|
+
"""Convert the current instance to a serialisable dictionary.
|
118
192
|
|
193
|
+
:return: A dictionary representation of this capture template, where all values
|
194
|
+
are formatted strings.
|
195
|
+
"""
|
196
|
+
return {ptemplate.name.value: ptemplate.to_dict() for ptemplate in self}
|
197
|
+
|
198
|
+
|
199
|
+
|
200
|
+
def make_base_capture_template(
|
201
|
+
*pnames: PName
|
202
|
+
) -> CaptureTemplate:
|
203
|
+
"""Make a capture template composed entirely of base `PTemplate` instances.
|
119
204
|
|
120
|
-
|
121
|
-
|
205
|
+
:param pnames: The names of parameters to include in the capture template.
|
206
|
+
:return: A capture template composed of base parameter templates.
|
207
|
+
"""
|
122
208
|
capture_template = CaptureTemplate()
|
123
|
-
for
|
124
|
-
capture_template.add_ptemplate( get_base_ptemplate(
|
209
|
+
for pname in pnames:
|
210
|
+
capture_template.add_ptemplate( get_base_ptemplate(pname) )
|
125
211
|
return capture_template
|
126
212
|
|
127
213
|
|
128
|
-
@dataclass(frozen=True)
|
129
|
-
class CaptureModes:
|
130
|
-
"""Pre-defined capture types"""
|
131
|
-
FIXED_CENTER_FREQUENCY: str = "fixed-center-frequency"
|
132
|
-
SWEPT_CENTER_FREQUENCY: str = "swept-center-frequency"
|
133
|
-
|
134
|
-
|
135
214
|
def _make_fixed_frequency_capture_template(
|
136
215
|
) -> CaptureTemplate:
|
137
216
|
"""The absolute minimum required parameters for any fixed frequency capture template."""
|
138
217
|
capture_template = make_base_capture_template(
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
218
|
+
PName.BATCH_SIZE,
|
219
|
+
PName.CENTER_FREQUENCY,
|
220
|
+
PName.BATCH_KEY,
|
221
|
+
PName.EVENT_HANDLER_KEY,
|
222
|
+
PName.FREQUENCY_RESOLUTION,
|
223
|
+
PName.INSTRUMENT,
|
224
|
+
PName.OBS_ALT,
|
225
|
+
PName.OBS_LAT,
|
226
|
+
PName.OBS_LON,
|
227
|
+
PName.OBJECT,
|
228
|
+
PName.ORIGIN,
|
229
|
+
PName.SAMPLE_RATE,
|
230
|
+
PName.TELESCOPE,
|
231
|
+
PName.TIME_RANGE,
|
232
|
+
PName.TIME_RESOLUTION,
|
233
|
+
PName.WATCH_EXTENSION,
|
234
|
+
PName.WINDOW_HOP,
|
235
|
+
PName.WINDOW_SIZE,
|
236
|
+
PName.WINDOW_TYPE,
|
158
237
|
)
|
159
238
|
capture_template.set_defaults(
|
160
|
-
(
|
161
|
-
(
|
162
|
-
(
|
239
|
+
(PName.EVENT_HANDLER_KEY, "fixed_center_frequency"),
|
240
|
+
(PName.BATCH_KEY, "iq_stream"),
|
241
|
+
(PName.WATCH_EXTENSION, "bin")
|
163
242
|
)
|
164
243
|
capture_template.enforce_defaults(
|
165
|
-
|
166
|
-
|
167
|
-
|
244
|
+
PName.EVENT_HANDLER_KEY,
|
245
|
+
PName.BATCH_KEY,
|
246
|
+
PName.WATCH_EXTENSION
|
168
247
|
)
|
169
248
|
return capture_template
|
170
249
|
|
@@ -172,50 +251,56 @@ def _make_swept_frequency_capture_template(
|
|
172
251
|
) -> CaptureTemplate:
|
173
252
|
"""The absolute minimum required parameters for any swept frequency capture template."""
|
174
253
|
capture_template = make_base_capture_template(
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
254
|
+
PName.BATCH_SIZE,
|
255
|
+
PName.BATCH_KEY,
|
256
|
+
PName.EVENT_HANDLER_KEY,
|
257
|
+
PName.FREQUENCY_RESOLUTION,
|
258
|
+
PName.FREQUENCY_STEP,
|
259
|
+
PName.INSTRUMENT,
|
260
|
+
PName.MAX_FREQUENCY,
|
261
|
+
PName.MIN_FREQUENCY,
|
262
|
+
PName.OBS_ALT,
|
263
|
+
PName.OBS_LAT,
|
264
|
+
PName.OBS_LON,
|
265
|
+
PName.OBJECT,
|
266
|
+
PName.ORIGIN,
|
267
|
+
PName.SAMPLE_RATE,
|
268
|
+
PName.SAMPLES_PER_STEP,
|
269
|
+
PName.TELESCOPE,
|
270
|
+
PName.TIME_RANGE,
|
271
|
+
PName.TIME_RESOLUTION,
|
272
|
+
PName.WATCH_EXTENSION,
|
273
|
+
PName.WINDOW_HOP,
|
274
|
+
PName.WINDOW_SIZE,
|
275
|
+
PName.WINDOW_TYPE)
|
197
276
|
capture_template.set_defaults(
|
198
|
-
(
|
199
|
-
(
|
200
|
-
(
|
277
|
+
(PName.EVENT_HANDLER_KEY, "swept_center_frequency"),
|
278
|
+
(PName.BATCH_KEY, "iq_stream"),
|
279
|
+
(PName.WATCH_EXTENSION, "bin")
|
201
280
|
)
|
202
281
|
capture_template.enforce_defaults(
|
203
|
-
|
204
|
-
|
205
|
-
|
282
|
+
PName.EVENT_HANDLER_KEY,
|
283
|
+
PName.BATCH_KEY,
|
284
|
+
PName.WATCH_EXTENSION
|
206
285
|
)
|
207
286
|
return capture_template
|
208
287
|
|
209
288
|
|
210
|
-
_base_capture_templates = {
|
211
|
-
|
212
|
-
|
289
|
+
_base_capture_templates: dict[CaptureMode, CaptureTemplate] = {
|
290
|
+
CaptureMode.FIXED_CENTER_FREQUENCY: _make_fixed_frequency_capture_template(),
|
291
|
+
CaptureMode.SWEPT_CENTER_FREQUENCY: _make_swept_frequency_capture_template()
|
213
292
|
}
|
214
293
|
|
294
|
+
|
215
295
|
def get_base_capture_template(
|
216
|
-
|
296
|
+
capture_mode: CaptureMode
|
217
297
|
) -> CaptureTemplate:
|
218
|
-
"""
|
298
|
+
"""Get a pre-defined capture template, to be configured according to the specific use-case.
|
299
|
+
|
300
|
+
:param capture_mode: The mode used to retrieve the capture template.
|
301
|
+
:return: A deep copy of the template for the specified mode.
|
302
|
+
:raises KeyError: If no capture template is found for the given mode.
|
303
|
+
"""
|
219
304
|
if capture_mode not in _base_capture_templates:
|
220
305
|
raise KeyError(f"No capture template found for the capture mode '{capture_mode}'. "
|
221
306
|
f"Expected one of {list(_base_capture_templates.keys())}")
|
@@ -2,85 +2,146 @@
|
|
2
2
|
# This file is part of SPECTRE
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
|
-
from typing import Any, Optional, TypeVar
|
5
|
+
from typing import Any, Optional, TypeVar, Generic, Iterator, overload, cast
|
6
6
|
|
7
|
-
|
7
|
+
from ._pnames import PName
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
# value type
|
10
|
+
VT = TypeVar('VT')
|
11
|
+
|
12
|
+
class Parameter(Generic[VT]):
|
13
|
+
"""A simple container for a named value."""
|
14
|
+
def __init__(
|
15
|
+
self,
|
16
|
+
name: PName,
|
17
|
+
value: Optional[VT] = None
|
18
|
+
) -> None:
|
19
|
+
"""Initialise a `Parameter` instance.
|
20
|
+
|
21
|
+
:param name: The name of the parameter.
|
22
|
+
:param value: The value of the parameter. Defaults to None.
|
23
|
+
"""
|
14
24
|
self._name = name
|
15
|
-
self._value: Optional[
|
25
|
+
self._value: Optional[VT] = value
|
16
26
|
|
17
27
|
|
18
28
|
@property
|
19
|
-
def name(
|
29
|
+
def name(
|
30
|
+
self
|
31
|
+
) -> PName:
|
20
32
|
"""The parameter name."""
|
21
33
|
return self._name
|
22
34
|
|
23
35
|
|
24
36
|
@property
|
25
|
-
def value(
|
37
|
+
def value(
|
38
|
+
self
|
39
|
+
) -> Optional[VT]:
|
26
40
|
"""The parameter value."""
|
27
41
|
return self._value
|
28
42
|
|
29
43
|
|
30
44
|
@value.setter
|
31
|
-
def value(
|
32
|
-
|
45
|
+
def value(
|
46
|
+
self,
|
47
|
+
v: Optional[VT]
|
48
|
+
) -> None:
|
49
|
+
"""Update the parameter value.
|
50
|
+
|
51
|
+
:param v: The new value to set for the parameter.
|
52
|
+
"""
|
33
53
|
self._value = v
|
34
54
|
|
35
55
|
|
36
56
|
class Parameters:
|
37
|
-
"""A collection of parameters."""
|
38
|
-
def __init__(
|
39
|
-
self
|
57
|
+
"""A managed collection of parameters."""
|
58
|
+
def __init__(
|
59
|
+
self
|
60
|
+
) -> None:
|
61
|
+
"""Initialise a `Parameters` instance."""
|
62
|
+
self._parameters: dict[PName, Parameter] = {}
|
63
|
+
|
40
64
|
|
41
|
-
|
42
65
|
@property
|
43
|
-
def name_list(
|
66
|
+
def name_list(
|
67
|
+
self
|
68
|
+
) -> list[PName]:
|
44
69
|
"""List the names of stored parameters."""
|
45
70
|
return list(self._parameters.keys())
|
46
71
|
|
47
72
|
|
48
|
-
def add_parameter(
|
49
|
-
|
50
|
-
|
51
|
-
|
73
|
+
def add_parameter(
|
74
|
+
self,
|
75
|
+
name: PName,
|
76
|
+
value: Optional[VT]
|
77
|
+
) -> None:
|
78
|
+
"""Add a `Parameter` instance to this `Parameters` instance with the input name and value.
|
79
|
+
|
80
|
+
:param name: The name of the parameter.
|
81
|
+
:param value: The value of the parameter.
|
82
|
+
:raises KeyError: If a parameter already exists under the input name.
|
83
|
+
"""
|
52
84
|
if name in self._parameters:
|
53
|
-
raise
|
54
|
-
|
85
|
+
raise KeyError(f"Cannot add a parameter with name '{name}', "
|
86
|
+
f"since a parameter already exists with that name. ")
|
55
87
|
self._parameters[name] = Parameter(name, value)
|
56
88
|
|
57
89
|
|
58
|
-
def get_parameter(
|
59
|
-
|
60
|
-
|
90
|
+
def get_parameter(
|
91
|
+
self,
|
92
|
+
name: PName
|
93
|
+
) -> Parameter:
|
94
|
+
"""Get the stored `Parameter` instance corresponding to the input name.
|
95
|
+
|
96
|
+
:param name: The name of the parameter.
|
97
|
+
:raises KeyError: If a parameter with the input name does not exist.
|
98
|
+
:return: A `Parameter` instance with the input name, if it exists.
|
99
|
+
"""
|
61
100
|
if name not in self._parameters:
|
62
101
|
raise KeyError(f"Parameter with name '{name}' does not exist. "
|
63
102
|
f"Expected one of {self.name_list}")
|
64
103
|
return self._parameters[name]
|
65
104
|
|
66
105
|
|
67
|
-
def get_parameter_value(
|
68
|
-
|
69
|
-
|
106
|
+
def get_parameter_value(
|
107
|
+
self,
|
108
|
+
name: PName
|
109
|
+
) -> Optional[VT]:
|
110
|
+
"""Get the value of the parameter with the corresponding name.
|
111
|
+
|
112
|
+
:param name: The name of the parameter.
|
113
|
+
:return: The value of the parameter with the input name.
|
114
|
+
"""
|
70
115
|
return self.get_parameter(name).value
|
71
116
|
|
72
117
|
|
73
|
-
def __iter__(
|
74
|
-
|
118
|
+
def __iter__(
|
119
|
+
self
|
120
|
+
) -> Iterator[Parameter]:
|
121
|
+
"""Iterate over stored parameters."""
|
75
122
|
yield from self._parameters.values()
|
76
123
|
|
77
124
|
|
78
|
-
def to_dict(
|
79
|
-
|
80
|
-
|
125
|
+
def to_dict(
|
126
|
+
self
|
127
|
+
) -> dict[str, Optional[Any]]:
|
128
|
+
"""Convert the `Parameters` instance to a serialisable dictionary.
|
129
|
+
|
130
|
+
:return: A dictionary representation of the stored parameters.
|
131
|
+
"""
|
132
|
+
return {p.name.value: p.value for p in self}
|
81
133
|
|
82
|
-
|
83
|
-
|
134
|
+
|
135
|
+
def _parse_string_parameter(
|
136
|
+
string_parameter: str
|
137
|
+
) -> list[str]:
|
138
|
+
"""Parse string of the form `a=b` into a list of the form `[a, b]`.
|
139
|
+
|
140
|
+
:param string_parameter: A string representation of a capture config parameter.
|
141
|
+
:raises ValueError: If the input parameter is not of the form `a=b`.
|
142
|
+
:return: The parsed components of the input string parameter, using the `=` character as a separator.
|
143
|
+
The return list will always contain two elements.
|
144
|
+
"""
|
84
145
|
if not string_parameter or '=' not in string_parameter:
|
85
146
|
raise ValueError(f"Invalid format: '{string_parameter}'. Expected 'KEY=VALUE'.")
|
86
147
|
if string_parameter.startswith('=') or string_parameter.endswith('='):
|
@@ -89,9 +150,15 @@ def _parse_string_parameter(string_parameter: str) -> list[str]:
|
|
89
150
|
string_parameter = string_parameter.strip()
|
90
151
|
return string_parameter.split('=', 1)
|
91
152
|
|
92
|
-
|
93
|
-
def parse_string_parameters(
|
94
|
-
|
153
|
+
|
154
|
+
def parse_string_parameters(
|
155
|
+
string_parameters: list[str]
|
156
|
+
) -> dict[str, str]:
|
157
|
+
"""Parses a list of strings of the form `a=b` into a dictionary mapping each `a` to each `b`.
|
158
|
+
|
159
|
+
:param string_parameters: A list of strings, where each element is of the form `a=b`.
|
160
|
+
:return: A dictionary mapping each `a` to each `b`, after parsing each element.
|
161
|
+
"""
|
95
162
|
d = {}
|
96
163
|
for string_parameter in string_parameters:
|
97
164
|
k, v = _parse_string_parameter(string_parameter)
|
@@ -99,9 +166,15 @@ def parse_string_parameters(string_parameters: list[str]) -> dict[str, str]:
|
|
99
166
|
return d
|
100
167
|
|
101
168
|
|
102
|
-
def make_parameters(
|
103
|
-
|
169
|
+
def make_parameters(
|
170
|
+
d: dict[str, Any]
|
171
|
+
) -> Parameters:
|
172
|
+
"""Create a `Parameters` instance from the given dictionary. Each key is interpreted as a valid `PName`.
|
173
|
+
|
174
|
+
:param d: A dictionary where keys represent parameter names and values represent their corresponding values.
|
175
|
+
:return: An instance of `Parameters` with each key-value pair in `d` added as a parameter.
|
176
|
+
"""
|
104
177
|
parameters = Parameters()
|
105
178
|
for k, v in d.items():
|
106
|
-
parameters.add_parameter(k, v)
|
179
|
+
parameters.add_parameter(PName(k), v)
|
107
180
|
return parameters
|