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.
Files changed (88) hide show
  1. spectre_core/_file_io/__init__.py +1 -3
  2. spectre_core/_file_io/file_handlers.py +163 -58
  3. spectre_core/batches/__init__.py +10 -11
  4. spectre_core/batches/_base.py +170 -78
  5. spectre_core/batches/_batches.py +149 -99
  6. spectre_core/batches/_factory.py +56 -14
  7. spectre_core/batches/_register.py +23 -8
  8. spectre_core/batches/plugins/_batch_keys.py +16 -0
  9. spectre_core/batches/plugins/_callisto.py +183 -0
  10. spectre_core/batches/plugins/_iq_stream.py +354 -0
  11. spectre_core/capture_configs/__init__.py +17 -13
  12. spectre_core/capture_configs/_capture_config.py +93 -34
  13. spectre_core/capture_configs/_capture_modes.py +22 -0
  14. spectre_core/capture_configs/_capture_templates.py +207 -122
  15. spectre_core/capture_configs/_parameters.py +115 -42
  16. spectre_core/capture_configs/_pconstraints.py +86 -35
  17. spectre_core/capture_configs/_pnames.py +49 -0
  18. spectre_core/capture_configs/_ptemplates.py +389 -346
  19. spectre_core/capture_configs/_pvalidators.py +117 -73
  20. spectre_core/config/__init__.py +6 -8
  21. spectre_core/config/_paths.py +65 -25
  22. spectre_core/config/_time_formats.py +15 -10
  23. spectre_core/exceptions.py +2 -4
  24. spectre_core/jobs/__init__.py +14 -0
  25. spectre_core/jobs/_jobs.py +111 -0
  26. spectre_core/jobs/_workers.py +171 -0
  27. spectre_core/logs/__init__.py +17 -0
  28. spectre_core/logs/_configure.py +67 -0
  29. spectre_core/logs/_decorators.py +33 -0
  30. spectre_core/logs/_logs.py +228 -0
  31. spectre_core/logs/_process_types.py +14 -0
  32. spectre_core/plotting/__init__.py +4 -2
  33. spectre_core/plotting/_base.py +204 -102
  34. spectre_core/plotting/_format.py +17 -4
  35. spectre_core/plotting/_panel_names.py +18 -0
  36. spectre_core/plotting/_panel_stack.py +167 -53
  37. spectre_core/plotting/_panels.py +341 -141
  38. spectre_core/post_processing/__init__.py +8 -6
  39. spectre_core/post_processing/_base.py +70 -44
  40. spectre_core/post_processing/_factory.py +42 -12
  41. spectre_core/post_processing/_post_processor.py +24 -26
  42. spectre_core/post_processing/_register.py +22 -6
  43. spectre_core/post_processing/plugins/_event_handler_keys.py +16 -0
  44. spectre_core/post_processing/plugins/_fixed_center_frequency.py +129 -0
  45. spectre_core/post_processing/{library → plugins}/_swept_center_frequency.py +215 -143
  46. spectre_core/py.typed +0 -0
  47. spectre_core/receivers/__init__.py +10 -7
  48. spectre_core/receivers/_base.py +220 -69
  49. spectre_core/receivers/_factory.py +53 -7
  50. spectre_core/receivers/_register.py +30 -9
  51. spectre_core/receivers/_spec_names.py +26 -15
  52. spectre_core/receivers/plugins/__init__.py +0 -0
  53. spectre_core/receivers/plugins/_receiver_names.py +16 -0
  54. spectre_core/receivers/plugins/_rsp1a.py +59 -0
  55. spectre_core/receivers/plugins/_rspduo.py +67 -0
  56. spectre_core/receivers/plugins/_sdrplay_receiver.py +190 -0
  57. spectre_core/receivers/plugins/_test.py +218 -0
  58. spectre_core/receivers/plugins/gr/_base.py +80 -0
  59. spectre_core/receivers/{gr → plugins/gr}/_rsp1a.py +42 -52
  60. spectre_core/receivers/{gr → plugins/gr}/_rspduo.py +61 -74
  61. spectre_core/receivers/{gr → plugins/gr}/_test.py +33 -31
  62. spectre_core/spectrograms/__init__.py +5 -3
  63. spectre_core/spectrograms/_analytical.py +121 -66
  64. spectre_core/spectrograms/_array_operations.py +103 -36
  65. spectre_core/spectrograms/_spectrogram.py +380 -207
  66. spectre_core/spectrograms/_transform.py +197 -169
  67. spectre_core/wgetting/__init__.py +4 -2
  68. spectre_core/wgetting/_callisto.py +173 -118
  69. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/METADATA +14 -7
  70. spectre_core-0.0.13.dist-info/RECORD +75 -0
  71. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/WHEEL +1 -1
  72. spectre_core/batches/library/_callisto.py +0 -96
  73. spectre_core/batches/library/_fixed_center_frequency.py +0 -133
  74. spectre_core/batches/library/_swept_center_frequency.py +0 -105
  75. spectre_core/logging/__init__.py +0 -11
  76. spectre_core/logging/_configure.py +0 -35
  77. spectre_core/logging/_decorators.py +0 -19
  78. spectre_core/logging/_log_handlers.py +0 -176
  79. spectre_core/post_processing/library/_fixed_center_frequency.py +0 -114
  80. spectre_core/receivers/gr/_base.py +0 -33
  81. spectre_core/receivers/library/_rsp1a.py +0 -61
  82. spectre_core/receivers/library/_rspduo.py +0 -69
  83. spectre_core/receivers/library/_sdrplay_receiver.py +0 -185
  84. spectre_core/receivers/library/_test.py +0 -221
  85. spectre_core-0.0.12.dist-info/RECORD +0 -64
  86. /spectre_core/receivers/{gr → plugins/gr}/__init__.py +0 -0
  87. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/LICENSE +0 -0
  88. {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 PConstraint
11
- from ._ptemplates import (
12
- PTemplate,
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 PTemplates"""
19
- def __init__(self):
20
- self._ptemplates: dict[str, PTemplate] = {}
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(self) -> list[str]:
25
- """List the names of all stored PTemplates."""
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(self,
30
- ptemplate: PTemplate) -> None:
31
- """Add a ptemplate to this capture template."""
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(self,
36
- parameter_name: str) -> PTemplate:
37
- """Get the ptemplate corresponding with the parameter name."""
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(self,
45
- parameter: Parameter):
46
- """Apply the corresponding parameter template to the input parameter"""
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(self,
52
- parameters: Parameters) -> None:
53
- """Apply the corresponding parameter template to all explictly specified parameters"""
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(self,
59
- parameters: Parameters) -> None:
60
- """For any missing parameters (as per the capture template), use the corresponding default value."""
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(self,
69
- parameters: Parameters) -> Parameters:
70
- """Validate parameters, fill missing with defaults, and return anew."""
71
- self.__apply_parameter_templates(parameters)
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__(self):
77
- """Iterate over stored ptemplates"""
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(self,
82
- parameter_name: str,
83
- default: Any) -> None:
84
- """Set the default of an existing ptemplate."""
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(self,
89
- *ptuples: tuple[str, Any]) -> None:
90
- """Update defaults for multiple ptemplates."""
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(self,
96
- parameter_name: str) -> None:
97
- """Enforce the default of an existing ptemplate"""
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(self,
102
- *parameter_names: str) -> None:
103
- """Enforce defaults for multiple parameter names."""
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(self,
109
- parameter_name: str,
110
- pconstraints: list[PConstraint]) -> None:
111
- """Add a pconstraint to an existing ptemplate"""
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(self) -> dict:
117
- return {ptemplate.name: ptemplate.to_dict() for ptemplate in self}
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
- def make_base_capture_template(*parameter_names: str):
121
- """Make a capture template, composed entirely of base ptemplates."""
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 name in parameter_names:
124
- capture_template.add_ptemplate( get_base_ptemplate(name) )
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
- PNames.BATCH_SIZE,
140
- PNames.CENTER_FREQUENCY,
141
- PNames.BATCH_KEY,
142
- PNames.EVENT_HANDLER_KEY,
143
- PNames.FREQUENCY_RESOLUTION,
144
- PNames.INSTRUMENT,
145
- PNames.OBS_ALT,
146
- PNames.OBS_LAT,
147
- PNames.OBS_LON,
148
- PNames.OBJECT,
149
- PNames.ORIGIN,
150
- PNames.SAMPLE_RATE,
151
- PNames.TELESCOPE,
152
- PNames.TIME_RANGE,
153
- PNames.TIME_RESOLUTION,
154
- PNames.WATCH_EXTENSION,
155
- PNames.WINDOW_HOP,
156
- PNames.WINDOW_SIZE,
157
- PNames.WINDOW_TYPE,
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
- (PNames.EVENT_HANDLER_KEY, CaptureModes.FIXED_CENTER_FREQUENCY),
161
- (PNames.BATCH_KEY, CaptureModes.FIXED_CENTER_FREQUENCY),
162
- (PNames.WATCH_EXTENSION, "bin")
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
- PNames.EVENT_HANDLER_KEY,
166
- PNames.BATCH_KEY,
167
- PNames.WATCH_EXTENSION
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
- PNames.BATCH_SIZE,
176
- PNames.BATCH_KEY,
177
- PNames.EVENT_HANDLER_KEY,
178
- PNames.FREQUENCY_RESOLUTION,
179
- PNames.FREQUENCY_STEP,
180
- PNames.INSTRUMENT,
181
- PNames.MAX_FREQUENCY,
182
- PNames.MIN_FREQUENCY,
183
- PNames.OBS_ALT,
184
- PNames.OBS_LAT,
185
- PNames.OBS_LON,
186
- PNames.OBJECT,
187
- PNames.ORIGIN,
188
- PNames.SAMPLE_RATE,
189
- PNames.SAMPLES_PER_STEP,
190
- PNames.TELESCOPE,
191
- PNames.TIME_RANGE,
192
- PNames.TIME_RESOLUTION,
193
- PNames.WATCH_EXTENSION,
194
- PNames.WINDOW_HOP,
195
- PNames.WINDOW_SIZE,
196
- PNames.WINDOW_TYPE)
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
- (PNames.EVENT_HANDLER_KEY, CaptureModes.SWEPT_CENTER_FREQUENCY),
199
- (PNames.BATCH_KEY, CaptureModes.SWEPT_CENTER_FREQUENCY),
200
- (PNames.WATCH_EXTENSION, "bin")
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
- PNames.EVENT_HANDLER_KEY,
204
- PNames.BATCH_KEY,
205
- PNames.WATCH_EXTENSION
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
- CaptureModes.FIXED_CENTER_FREQUENCY: _make_fixed_frequency_capture_template(),
212
- CaptureModes.SWEPT_CENTER_FREQUENCY: _make_swept_frequency_capture_template()
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
- capture_mode: str
296
+ capture_mode: CaptureMode
217
297
  ) -> CaptureTemplate:
218
- """Create a fresh deep copy of a pre-defined capture template"""
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
- T = TypeVar('T')
7
+ from ._pnames import PName
8
8
 
9
- class Parameter:
10
- """A named value."""
11
- def __init__(self,
12
- name: str,
13
- value: Optional[T] = None):
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[T] = value
25
+ self._value: Optional[VT] = value
16
26
 
17
27
 
18
28
  @property
19
- def name(self) -> str:
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(self) -> Optional[T]:
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(self, v: Optional[T]) -> None:
32
- """Update the parameter value."""
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__(self):
39
- self._parameters: dict[str, Parameter] = {}
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(self) -> list[str]:
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(self,
49
- name: str,
50
- value: Optional[T] = None) -> None:
51
- """Add a new parameter."""
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 ValueError(f"Cannot add a parameter with name '{name}', "
54
- f"since a parameter already exists with that name. ")
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(self,
59
- name: str) -> Parameter:
60
- """Get the parameter with the corresponding name."""
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(self,
68
- name: str) -> Optional[T]:
69
- """Get the value of the parameter with the corresponding name."""
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__(self):
74
- """Iterate over stored parameters"""
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(self) -> dict:
79
- """Convert the class instance to an equivalent dictionary representation"""
80
- return {p.name: p.value for p in self}
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
- def _parse_string_parameter(string_parameter: str) -> list[str]:
83
- """Parse string of the form 'a=b; into a list of the form [a, b]"""
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(string_parameters: list[str]) -> dict[str, str]:
94
- """Parses a list of strings of the form 'a=b'; into a dictionary mapping each 'a' to each 'b'"""
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(d: dict[str, Any]):
103
- """Make an instance of parameters based on the input dictionary"""
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