dkist-processing-common 12.0.0rc7__py3-none-any.whl → 12.1.0__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.
- dkist_processing_common/codecs/fits.py +27 -6
- dkist_processing_common/models/constants.py +2 -2
- dkist_processing_common/models/flower_pot.py +230 -9
- dkist_processing_common/parsers/average_bud.py +0 -2
- dkist_processing_common/parsers/cs_step.py +10 -10
- dkist_processing_common/parsers/id_bud.py +4 -9
- dkist_processing_common/parsers/lookup_bud.py +7 -11
- dkist_processing_common/parsers/near_bud.py +7 -12
- dkist_processing_common/parsers/retarder.py +9 -13
- dkist_processing_common/parsers/time.py +19 -55
- dkist_processing_common/parsers/unique_bud.py +7 -14
- dkist_processing_common/tasks/parse_l0_input_data.py +4 -2
- dkist_processing_common/tasks/transfer_input_data.py +1 -0
- dkist_processing_common/tests/test_codecs.py +57 -11
- dkist_processing_common/tests/test_flower_pot.py +147 -5
- dkist_processing_common/tests/test_parse_l0_input_data.py +28 -4
- dkist_processing_common/tests/test_stems.py +140 -193
- dkist_processing_common/tests/test_workflow_task_base.py +0 -11
- {dkist_processing_common-12.0.0rc7.dist-info → dkist_processing_common-12.1.0.dist-info}/METADATA +2 -2
- {dkist_processing_common-12.0.0rc7.dist-info → dkist_processing_common-12.1.0.dist-info}/RECORD +22 -23
- {dkist_processing_common-12.0.0rc7.dist-info → dkist_processing_common-12.1.0.dist-info}/WHEEL +1 -1
- changelog/278.misc.rst +0 -1
- {dkist_processing_common-12.0.0rc7.dist-info → dkist_processing_common-12.1.0.dist-info}/top_level.txt +0 -0
|
@@ -4,15 +4,15 @@ from datetime import datetime
|
|
|
4
4
|
from datetime import timezone
|
|
5
5
|
from enum import StrEnum
|
|
6
6
|
from typing import Callable
|
|
7
|
-
from typing import Hashable
|
|
8
7
|
from typing import Type
|
|
9
8
|
|
|
10
9
|
import numpy as np
|
|
11
10
|
|
|
12
11
|
from dkist_processing_common.models.constants import BudName
|
|
13
12
|
from dkist_processing_common.models.fits_access import MetadataKey
|
|
13
|
+
from dkist_processing_common.models.flower_pot import ListStem
|
|
14
|
+
from dkist_processing_common.models.flower_pot import SetStem
|
|
14
15
|
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
15
|
-
from dkist_processing_common.models.flower_pot import Stem
|
|
16
16
|
from dkist_processing_common.models.tags import EXP_TIME_ROUND_DIGITS
|
|
17
17
|
from dkist_processing_common.models.tags import StemName
|
|
18
18
|
from dkist_processing_common.models.task_name import TaskName
|
|
@@ -35,7 +35,7 @@ class ObsIpStartTimeBud(TaskUniqueBud):
|
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
class TaskDatetimeBudBase(
|
|
38
|
+
class TaskDatetimeBudBase(ListStem):
|
|
39
39
|
"""
|
|
40
40
|
Base class for making datetime-related buds.
|
|
41
41
|
|
|
@@ -59,8 +59,6 @@ class TaskDatetimeBudBase(Stem):
|
|
|
59
59
|
The function used to convert a header into an IP task type
|
|
60
60
|
"""
|
|
61
61
|
|
|
62
|
-
key_to_petal_dict: dict[str, float]
|
|
63
|
-
|
|
64
62
|
def __init__(
|
|
65
63
|
self,
|
|
66
64
|
stem_name: str,
|
|
@@ -101,20 +99,15 @@ class TaskDatetimeBudBase(Stem):
|
|
|
101
99
|
|
|
102
100
|
return SpilledDirt
|
|
103
101
|
|
|
104
|
-
def getter(self
|
|
102
|
+
def getter(self) -> tuple[float, ...]:
|
|
105
103
|
"""
|
|
106
104
|
Return a tuple of sorted times in unix seconds.
|
|
107
105
|
|
|
108
|
-
Parameters
|
|
109
|
-
----------
|
|
110
|
-
key
|
|
111
|
-
The input key
|
|
112
|
-
|
|
113
106
|
Returns
|
|
114
107
|
-------
|
|
115
108
|
A tuple that is sorted times in unix seconds
|
|
116
109
|
"""
|
|
117
|
-
return tuple(sorted(
|
|
110
|
+
return tuple(sorted(self.value_list))
|
|
118
111
|
|
|
119
112
|
|
|
120
113
|
class CadenceBudBase(TaskDatetimeBudBase):
|
|
@@ -134,20 +127,15 @@ class AverageCadenceBud(CadenceBudBase):
|
|
|
134
127
|
def __init__(self):
|
|
135
128
|
super().__init__(constant_name=BudName.average_cadence)
|
|
136
129
|
|
|
137
|
-
def getter(self
|
|
130
|
+
def getter(self) -> np.float64:
|
|
138
131
|
"""
|
|
139
132
|
Return the mean cadence between frames.
|
|
140
133
|
|
|
141
|
-
Parameters
|
|
142
|
-
----------
|
|
143
|
-
key
|
|
144
|
-
The input key
|
|
145
|
-
|
|
146
134
|
Returns
|
|
147
135
|
-------
|
|
148
136
|
The mean value of the cadences of the input frames
|
|
149
137
|
"""
|
|
150
|
-
return np.mean(np.diff(super().getter(
|
|
138
|
+
return np.mean(np.diff(super().getter()))
|
|
151
139
|
|
|
152
140
|
|
|
153
141
|
class MaximumCadenceBud(CadenceBudBase):
|
|
@@ -156,20 +144,15 @@ class MaximumCadenceBud(CadenceBudBase):
|
|
|
156
144
|
def __init__(self):
|
|
157
145
|
super().__init__(constant_name=BudName.maximum_cadence)
|
|
158
146
|
|
|
159
|
-
def getter(self
|
|
147
|
+
def getter(self) -> np.float64:
|
|
160
148
|
"""
|
|
161
149
|
Return the maximum cadence between frames.
|
|
162
150
|
|
|
163
|
-
Parameters
|
|
164
|
-
----------
|
|
165
|
-
key
|
|
166
|
-
The input key
|
|
167
|
-
|
|
168
151
|
Returns
|
|
169
152
|
-------
|
|
170
153
|
The maximum cadence between frames
|
|
171
154
|
"""
|
|
172
|
-
return np.max(np.diff(super().getter(
|
|
155
|
+
return np.max(np.diff(super().getter()))
|
|
173
156
|
|
|
174
157
|
|
|
175
158
|
class MinimumCadenceBud(CadenceBudBase):
|
|
@@ -178,20 +161,15 @@ class MinimumCadenceBud(CadenceBudBase):
|
|
|
178
161
|
def __init__(self):
|
|
179
162
|
super().__init__(constant_name=BudName.minimum_cadence)
|
|
180
163
|
|
|
181
|
-
def getter(self
|
|
164
|
+
def getter(self) -> np.float64:
|
|
182
165
|
"""
|
|
183
166
|
Return the minimum cadence between frames.
|
|
184
167
|
|
|
185
|
-
Parameters
|
|
186
|
-
----------
|
|
187
|
-
key
|
|
188
|
-
The input key
|
|
189
|
-
|
|
190
168
|
Returns
|
|
191
169
|
-------
|
|
192
170
|
The minimum cadence between frames
|
|
193
171
|
"""
|
|
194
|
-
return np.min(np.diff(super().getter(
|
|
172
|
+
return np.min(np.diff(super().getter()))
|
|
195
173
|
|
|
196
174
|
|
|
197
175
|
class VarianceCadenceBud(CadenceBudBase):
|
|
@@ -200,19 +178,15 @@ class VarianceCadenceBud(CadenceBudBase):
|
|
|
200
178
|
def __init__(self):
|
|
201
179
|
super().__init__(constant_name=BudName.variance_cadence)
|
|
202
180
|
|
|
203
|
-
def getter(self
|
|
181
|
+
def getter(self) -> np.float64:
|
|
204
182
|
"""
|
|
205
183
|
Return the cadence variance between frames.
|
|
206
184
|
|
|
207
|
-
Parameters
|
|
208
|
-
----------
|
|
209
|
-
key
|
|
210
|
-
The input key
|
|
211
185
|
Returns
|
|
212
186
|
-------
|
|
213
187
|
Return the variance of the cadences over the input frames
|
|
214
188
|
"""
|
|
215
|
-
return np.var(np.diff(super().getter(
|
|
189
|
+
return np.var(np.diff(super().getter()))
|
|
216
190
|
|
|
217
191
|
|
|
218
192
|
class TaskDateBeginBud(TaskDatetimeBudBase):
|
|
@@ -231,19 +205,16 @@ class TaskDateBeginBud(TaskDatetimeBudBase):
|
|
|
231
205
|
task_type_parsing_function=task_type_parsing_function,
|
|
232
206
|
)
|
|
233
207
|
|
|
234
|
-
def getter(self
|
|
208
|
+
def getter(self) -> str:
|
|
235
209
|
"""
|
|
236
210
|
Return the earliest date begin for the ip task type converted from unix seconds to datetime string.
|
|
237
211
|
|
|
238
|
-
Parameters
|
|
239
|
-
----------
|
|
240
|
-
key
|
|
241
|
-
The input key
|
|
242
212
|
Returns
|
|
243
213
|
-------
|
|
244
214
|
Return the minimum date begin as a datetime string
|
|
245
215
|
"""
|
|
246
|
-
|
|
216
|
+
# super().getter() returns a sorted list
|
|
217
|
+
min_time = super().getter()[0]
|
|
247
218
|
min_time_dt = datetime.fromtimestamp(min_time, tz=timezone.utc)
|
|
248
219
|
return min_time_dt.strftime("%Y-%m-%dT%H:%M:%S.%f")
|
|
249
220
|
|
|
@@ -286,7 +257,7 @@ class ReadoutExpTimeFlower(RoundTimeFlowerBase):
|
|
|
286
257
|
)
|
|
287
258
|
|
|
288
259
|
|
|
289
|
-
class TaskRoundTimeBudBase(
|
|
260
|
+
class TaskRoundTimeBudBase(SetStem):
|
|
290
261
|
"""
|
|
291
262
|
Base class for making buds that need a set of rounded times for computing for specific task types.
|
|
292
263
|
|
|
@@ -310,8 +281,6 @@ class TaskRoundTimeBudBase(Stem):
|
|
|
310
281
|
The function used to convert a header into an IP task type
|
|
311
282
|
"""
|
|
312
283
|
|
|
313
|
-
key_to_petal_dict: dict[str, float]
|
|
314
|
-
|
|
315
284
|
def __init__(
|
|
316
285
|
self,
|
|
317
286
|
stem_name: str,
|
|
@@ -349,20 +318,15 @@ class TaskRoundTimeBudBase(Stem):
|
|
|
349
318
|
|
|
350
319
|
return SpilledDirt
|
|
351
320
|
|
|
352
|
-
def getter(self
|
|
321
|
+
def getter(self) -> tuple[float, ...]:
|
|
353
322
|
"""
|
|
354
323
|
Return a tuple of the sorted unique values found.
|
|
355
324
|
|
|
356
|
-
Parameters
|
|
357
|
-
----------
|
|
358
|
-
key
|
|
359
|
-
The input key
|
|
360
|
-
|
|
361
325
|
Returns
|
|
362
326
|
-------
|
|
363
327
|
A tuple that is the sorted set of unique times
|
|
364
328
|
"""
|
|
365
|
-
return tuple(sorted(
|
|
329
|
+
return tuple(sorted(self.value_set))
|
|
366
330
|
|
|
367
331
|
|
|
368
332
|
class TaskExposureTimesBud(TaskRoundTimeBudBase):
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
from enum import StrEnum
|
|
4
4
|
from typing import Callable
|
|
5
5
|
|
|
6
|
+
from dkist_processing_common.models.flower_pot import SetStem
|
|
6
7
|
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
7
|
-
from dkist_processing_common.models.flower_pot import Stem
|
|
8
8
|
from dkist_processing_common.parsers.l0_fits_access import L0FitsAccess
|
|
9
9
|
from dkist_processing_common.parsers.task import passthrough_header_ip_task
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class UniqueBud(
|
|
12
|
+
class UniqueBud(SetStem):
|
|
13
13
|
"""
|
|
14
|
-
Pre-made
|
|
14
|
+
Pre-made `SetStem` that reads a single header key from all files and raises a ValueError if it is not unique.
|
|
15
15
|
|
|
16
16
|
Parameters
|
|
17
17
|
----------
|
|
@@ -46,24 +46,17 @@ class UniqueBud(Stem):
|
|
|
46
46
|
"""
|
|
47
47
|
return getattr(fits_obj, self.metadata_key)
|
|
48
48
|
|
|
49
|
-
def getter(self
|
|
49
|
+
def getter(self):
|
|
50
50
|
"""
|
|
51
51
|
Get the value for this key and raise an error if it is not unique.
|
|
52
52
|
|
|
53
|
-
Parameters
|
|
54
|
-
----------
|
|
55
|
-
key
|
|
56
|
-
The input key
|
|
57
53
|
Returns
|
|
58
54
|
-------
|
|
59
55
|
The value associated with this input key
|
|
60
56
|
"""
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
f"Multiple {self.stem_name} values found for key {key}. Values: {value_set}"
|
|
65
|
-
)
|
|
66
|
-
return value_set.pop()
|
|
57
|
+
if len(self.value_set) > 1:
|
|
58
|
+
raise ValueError(f"Multiple {self.stem_name} values found! Values: {self.value_set}")
|
|
59
|
+
return self.value_set.pop()
|
|
67
60
|
|
|
68
61
|
|
|
69
62
|
class TaskUniqueBud(UniqueBud):
|
|
@@ -397,6 +397,8 @@ class ParseDataBase(WorkflowTaskBase, ABC):
|
|
|
397
397
|
return self.read(
|
|
398
398
|
tags=self.tags_for_input_frames,
|
|
399
399
|
decoder=fits_access_decoder,
|
|
400
|
+
checksum=False,
|
|
401
|
+
disable_image_compression=True,
|
|
400
402
|
fits_access_class=self.fits_parsing_class,
|
|
401
403
|
)
|
|
402
404
|
|
|
@@ -419,8 +421,8 @@ class ParseDataBase(WorkflowTaskBase, ABC):
|
|
|
419
421
|
"""
|
|
420
422
|
for stem in constant_pot:
|
|
421
423
|
with self.telemetry_span(f"Setting value of constant {stem.stem_name}"):
|
|
422
|
-
if
|
|
423
|
-
#
|
|
424
|
+
if not stem.can_be_picked:
|
|
425
|
+
# Nothing to do
|
|
424
426
|
continue
|
|
425
427
|
if stem.bud.value is Thorn:
|
|
426
428
|
# Must've been a picky bud that passed. We don't want to pick it because it has no value
|
|
@@ -78,6 +78,7 @@ class TransferL0Data(WorkflowTaskBase, GlobusMixin):
|
|
|
78
78
|
self.tag(obj_path, tags=obj.tag)
|
|
79
79
|
else:
|
|
80
80
|
self.tag(obj_path, tags=[Tag.input(), Tag.frame()])
|
|
81
|
+
logger.info(f"Tagged {len(input_dataset_objects)} input dataset objects in scratch")
|
|
81
82
|
|
|
82
83
|
def run(self) -> None:
|
|
83
84
|
"""Execute the data transfer."""
|
|
@@ -183,7 +183,7 @@ def primary_hdu_list(ndarray_object, fits_header) -> HDUList:
|
|
|
183
183
|
|
|
184
184
|
@pytest.fixture
|
|
185
185
|
def path_to_primary_fits_file(primary_hdu_list, tmp_file) -> Path:
|
|
186
|
-
primary_hdu_list.writeto(tmp_file)
|
|
186
|
+
primary_hdu_list.writeto(tmp_file, checksum=True)
|
|
187
187
|
return tmp_file
|
|
188
188
|
|
|
189
189
|
|
|
@@ -210,7 +210,7 @@ def compressed_hdu_list(ndarray_object, fits_header) -> HDUList:
|
|
|
210
210
|
|
|
211
211
|
@pytest.fixture
|
|
212
212
|
def path_to_compressed_fits_file(compressed_hdu_list, tmp_file) -> Path:
|
|
213
|
-
compressed_hdu_list.writeto(tmp_file)
|
|
213
|
+
compressed_hdu_list.writeto(tmp_file, checksum=True)
|
|
214
214
|
return tmp_file
|
|
215
215
|
|
|
216
216
|
|
|
@@ -472,16 +472,27 @@ def test_bytesio_decoder(bytesIO_object, path_to_bytesIO):
|
|
|
472
472
|
pytest.param("path_to_compressed_fits_file", id="compressed"),
|
|
473
473
|
],
|
|
474
474
|
)
|
|
475
|
-
|
|
475
|
+
@pytest.mark.parametrize(
|
|
476
|
+
"checksum", [pytest.param(True, id="checksum"), pytest.param(False, id="no_checksum")]
|
|
477
|
+
)
|
|
478
|
+
@pytest.mark.parametrize(
|
|
479
|
+
"decompress", [pytest.param(True, id="decompress"), pytest.param(False, id="no_decompress")]
|
|
480
|
+
)
|
|
481
|
+
def test_fits_hdu_decoder(
|
|
482
|
+
path_fixture_name, ndarray_object, fits_header, request, checksum, decompress
|
|
483
|
+
):
|
|
476
484
|
"""
|
|
477
485
|
Given: Path to a FITS file
|
|
478
486
|
When: Decoding the path with the fits_hdu_decoder
|
|
479
487
|
Then: The correct data are returned
|
|
480
488
|
"""
|
|
481
489
|
file_path = request.getfixturevalue(path_fixture_name)
|
|
482
|
-
hdu = fits_hdu_decoder(file_path)
|
|
490
|
+
hdu = fits_hdu_decoder(file_path, checksum=checksum, disable_image_compression=not decompress)
|
|
483
491
|
|
|
484
|
-
|
|
492
|
+
if "compressed" in path_fixture_name and not decompress:
|
|
493
|
+
assert not np.array_equal(hdu.data, ndarray_object)
|
|
494
|
+
else:
|
|
495
|
+
assert np.array_equal(hdu.data, ndarray_object)
|
|
485
496
|
assert hdu.header["foo"] == fits_header["foo"]
|
|
486
497
|
|
|
487
498
|
|
|
@@ -492,7 +503,15 @@ def test_fits_hdu_decoder(path_fixture_name, ndarray_object, fits_header, reques
|
|
|
492
503
|
pytest.param("path_to_compressed_fits_file", id="compressed"),
|
|
493
504
|
],
|
|
494
505
|
)
|
|
495
|
-
|
|
506
|
+
@pytest.mark.parametrize(
|
|
507
|
+
"checksum", [pytest.param(True, id="checksum"), pytest.param(False, id="no_checksum")]
|
|
508
|
+
)
|
|
509
|
+
@pytest.mark.parametrize(
|
|
510
|
+
"decompress", [pytest.param(True, id="decompress"), pytest.param(False, id="no_decompress")]
|
|
511
|
+
)
|
|
512
|
+
def test_fits_access_decoder(
|
|
513
|
+
path_fixture_name, ndarray_object, fits_header, request, checksum, decompress
|
|
514
|
+
):
|
|
496
515
|
"""
|
|
497
516
|
Given: Path to a FITS file
|
|
498
517
|
When: Decoding the path with the fits_access_decoder
|
|
@@ -500,20 +519,47 @@ def test_fits_access_decoder(path_fixture_name, ndarray_object, fits_header, req
|
|
|
500
519
|
"""
|
|
501
520
|
file_path = request.getfixturevalue(path_fixture_name)
|
|
502
521
|
|
|
503
|
-
fits_obj = fits_access_decoder(
|
|
522
|
+
fits_obj = fits_access_decoder(
|
|
523
|
+
file_path,
|
|
524
|
+
fits_access_class=DummyFitsAccess,
|
|
525
|
+
checksum=checksum,
|
|
526
|
+
disable_image_compression=not decompress,
|
|
527
|
+
)
|
|
504
528
|
assert fits_obj.name == str(file_path)
|
|
505
|
-
assert np.array_equal(fits_obj.data, ndarray_object)
|
|
506
529
|
assert fits_obj.foo == fits_header["foo"]
|
|
530
|
+
if "compressed" in path_fixture_name and not decompress:
|
|
531
|
+
assert not np.array_equal(fits_obj.data, ndarray_object)
|
|
532
|
+
else:
|
|
533
|
+
assert np.array_equal(fits_obj.data, ndarray_object)
|
|
507
534
|
|
|
508
535
|
|
|
509
|
-
|
|
536
|
+
@pytest.mark.parametrize(
|
|
537
|
+
"path_fixture_name",
|
|
538
|
+
[
|
|
539
|
+
pytest.param("path_to_primary_fits_file", id="uncompressed"),
|
|
540
|
+
pytest.param("path_to_compressed_fits_file", id="compressed"),
|
|
541
|
+
],
|
|
542
|
+
)
|
|
543
|
+
@pytest.mark.parametrize(
|
|
544
|
+
"checksum", [pytest.param(True, id="checksum"), pytest.param(False, id="no_checksum")]
|
|
545
|
+
)
|
|
546
|
+
@pytest.mark.parametrize(
|
|
547
|
+
"decompress", [pytest.param(True, id="decompress"), pytest.param(False, id="no_decompress")]
|
|
548
|
+
)
|
|
549
|
+
def test_fits_array_decoder(path_fixture_name, ndarray_object, request, checksum, decompress):
|
|
510
550
|
"""
|
|
511
551
|
Given: Path to a FITS file
|
|
512
552
|
When: Decoding the path the fits_array_decoder
|
|
513
553
|
Then: The correct data are returned
|
|
514
554
|
"""
|
|
515
|
-
|
|
516
|
-
|
|
555
|
+
file_path = request.getfixturevalue(path_fixture_name)
|
|
556
|
+
array = fits_array_decoder(
|
|
557
|
+
file_path, checksum=checksum, disable_image_compression=not decompress
|
|
558
|
+
)
|
|
559
|
+
if "compressed" in path_fixture_name and not decompress:
|
|
560
|
+
assert not np.array_equal(array, ndarray_object)
|
|
561
|
+
else:
|
|
562
|
+
assert np.array_equal(ndarray_object, array)
|
|
517
563
|
|
|
518
564
|
|
|
519
565
|
def test_fits_array_decoder_autosqueeze(
|
|
@@ -4,11 +4,13 @@ from typing import Hashable
|
|
|
4
4
|
import pytest
|
|
5
5
|
|
|
6
6
|
from dkist_processing_common.models.flower_pot import FlowerPot
|
|
7
|
+
from dkist_processing_common.models.flower_pot import ListStem
|
|
8
|
+
from dkist_processing_common.models.flower_pot import SetStem
|
|
7
9
|
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
8
10
|
from dkist_processing_common.models.flower_pot import Stem
|
|
9
11
|
|
|
10
12
|
|
|
11
|
-
@pytest.fixture
|
|
13
|
+
@pytest.fixture
|
|
12
14
|
def simple_flower():
|
|
13
15
|
class Flower(Stem):
|
|
14
16
|
def setter(self, value: Any) -> Any:
|
|
@@ -22,7 +24,7 @@ def simple_flower():
|
|
|
22
24
|
return Flower(stem_name="simple_flower")
|
|
23
25
|
|
|
24
26
|
|
|
25
|
-
@pytest.fixture
|
|
27
|
+
@pytest.fixture
|
|
26
28
|
def simple_flower_pot(simple_flower):
|
|
27
29
|
flower_pot = FlowerPot()
|
|
28
30
|
flower_pot.stems += [simple_flower]
|
|
@@ -30,16 +32,66 @@ def simple_flower_pot(simple_flower):
|
|
|
30
32
|
return flower_pot
|
|
31
33
|
|
|
32
34
|
|
|
33
|
-
@pytest.fixture
|
|
35
|
+
@pytest.fixture
|
|
34
36
|
def simple_key_values():
|
|
35
37
|
return {f"thing{i}": i for i in range(5)}
|
|
36
38
|
|
|
37
39
|
|
|
40
|
+
@pytest.fixture
|
|
41
|
+
def stem_bud():
|
|
42
|
+
class Bud(Stem):
|
|
43
|
+
def setter(self, value: int) -> int:
|
|
44
|
+
return value % 3
|
|
45
|
+
|
|
46
|
+
def getter(self, key: str) -> int:
|
|
47
|
+
return len(set(self.key_to_petal_dict.values()))
|
|
48
|
+
|
|
49
|
+
return Bud(stem_name="StemBud")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.fixture
|
|
53
|
+
def setstem_bud():
|
|
54
|
+
# Computes the same result as `stem_bud`
|
|
55
|
+
class SetBud(SetStem):
|
|
56
|
+
def setter(self, value: int) -> int:
|
|
57
|
+
return value % 3
|
|
58
|
+
|
|
59
|
+
def getter(self) -> int:
|
|
60
|
+
return len(self.value_set)
|
|
61
|
+
|
|
62
|
+
return SetBud(stem_name="SetStemBud")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@pytest.fixture
|
|
66
|
+
def liststem_bud():
|
|
67
|
+
# Highlights the difference between using a `set` and a `list` in these more efficient buds
|
|
68
|
+
class ListBud(ListStem):
|
|
69
|
+
def setter(self, value: int) -> int:
|
|
70
|
+
return value % 3
|
|
71
|
+
|
|
72
|
+
def getter(self) -> int:
|
|
73
|
+
return len(self.value_list)
|
|
74
|
+
|
|
75
|
+
return ListBud(stem_name="ListStemBud")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@pytest.fixture
|
|
79
|
+
def simple_bud_pot(stem_bud, setstem_bud, liststem_bud):
|
|
80
|
+
bud_pot = FlowerPot()
|
|
81
|
+
bud_pot.stems += [stem_bud, liststem_bud, setstem_bud]
|
|
82
|
+
return bud_pot
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@pytest.fixture
|
|
86
|
+
def bud_key_values():
|
|
87
|
+
return {f"DOESN'T_MATTER_THAT'S_THE_POINT_{i}": i for i in [0, 1, 3, 4]}
|
|
88
|
+
|
|
89
|
+
|
|
38
90
|
def test_simple_flower_pot(simple_flower_pot, simple_key_values):
|
|
39
91
|
"""
|
|
40
92
|
Given: A FlowerPot with a simple Flower
|
|
41
|
-
When: Updating
|
|
42
|
-
Then: The
|
|
93
|
+
When: Updating flower with key: value pairs
|
|
94
|
+
Then: The flower are correctly updated
|
|
43
95
|
"""
|
|
44
96
|
assert len(simple_flower_pot) == 1
|
|
45
97
|
|
|
@@ -51,6 +103,8 @@ def test_simple_flower_pot(simple_flower_pot, simple_key_values):
|
|
|
51
103
|
|
|
52
104
|
petals = sorted(list(flower.petals), key=lambda x: x.value)
|
|
53
105
|
assert len(petals) == 2
|
|
106
|
+
assert flower.bud.value == petals[0].value
|
|
107
|
+
assert flower.bud.keys == petals[0].keys
|
|
54
108
|
assert petals[0].value == 0
|
|
55
109
|
assert petals[0].keys == ["thing0", "thing2", "thing4"]
|
|
56
110
|
assert petals[1].value == 1
|
|
@@ -67,8 +121,12 @@ def test_cached_petal(simple_flower):
|
|
|
67
121
|
value1 = 4
|
|
68
122
|
simple_flower.update(key1, value1)
|
|
69
123
|
assert len(simple_flower.petals) == 1
|
|
124
|
+
|
|
125
|
+
# Assert twice to hit the cache
|
|
70
126
|
assert simple_flower.petals[0].value == value1 % 2 # % 2 because of simple_flower's `setter`
|
|
71
127
|
assert simple_flower.petals[0].keys == [key1]
|
|
128
|
+
assert simple_flower.petals[0].value == value1 % 2
|
|
129
|
+
assert simple_flower.petals[0].keys == [key1]
|
|
72
130
|
|
|
73
131
|
key2 = "thing2"
|
|
74
132
|
value2 = 3
|
|
@@ -79,6 +137,10 @@ def test_cached_petal(simple_flower):
|
|
|
79
137
|
assert sorted_petals[0].keys == [key1]
|
|
80
138
|
assert sorted_petals[1].value == value2 % 2
|
|
81
139
|
assert sorted_petals[1].keys == [key2]
|
|
140
|
+
assert sorted_petals[0].value == value1 % 2
|
|
141
|
+
assert sorted_petals[0].keys == [key1]
|
|
142
|
+
assert sorted_petals[1].value == value2 % 2
|
|
143
|
+
assert sorted_petals[1].keys == [key2]
|
|
82
144
|
|
|
83
145
|
|
|
84
146
|
def test_spilled_dirt_flower(simple_flower):
|
|
@@ -103,3 +165,83 @@ def test_unhashable_dirt(simple_flower_pot):
|
|
|
103
165
|
value = "never gonna get here"
|
|
104
166
|
with pytest.raises(TypeError):
|
|
105
167
|
simple_flower_pot.add_dirt(unhashable_key, value)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_buds(simple_bud_pot, bud_key_values):
|
|
171
|
+
"""
|
|
172
|
+
Given: A Flower pot with two Buds that compute the same thing; one a `Stem` and one a `SetStem`, and a `ListStem` bud
|
|
173
|
+
When: Updating the pot with key: value pairs
|
|
174
|
+
Then: The computed buds are correct and the `Stem` and `SetStem` buds match
|
|
175
|
+
"""
|
|
176
|
+
assert len(simple_bud_pot) == 3
|
|
177
|
+
|
|
178
|
+
assert simple_bud_pot[0].stem_name == "StemBud"
|
|
179
|
+
assert simple_bud_pot[1].stem_name == "ListStemBud"
|
|
180
|
+
assert simple_bud_pot[2].stem_name == "SetStemBud"
|
|
181
|
+
|
|
182
|
+
for k, m in bud_key_values.items():
|
|
183
|
+
simple_bud_pot.add_dirt(k, m)
|
|
184
|
+
|
|
185
|
+
assert simple_bud_pot[0].bud.value == 2
|
|
186
|
+
assert simple_bud_pot[1].bud.value == 4
|
|
187
|
+
assert simple_bud_pot[2].bud.value == simple_bud_pot[0].bud.value
|
|
188
|
+
|
|
189
|
+
assert len(simple_bud_pot[0].petals) == 1
|
|
190
|
+
assert simple_bud_pot[0].petals[0].value == 2
|
|
191
|
+
assert simple_bud_pot[0].petals[0].keys == [
|
|
192
|
+
f"DOESN'T_MATTER_THAT'S_THE_POINT_{i}" for i in [0, 1, 3, 4]
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
with pytest.raises(
|
|
196
|
+
AttributeError,
|
|
197
|
+
match="ListBud subclasses ListStem and therefore does not define the `petals` property",
|
|
198
|
+
):
|
|
199
|
+
_ = simple_bud_pot[1].petals
|
|
200
|
+
|
|
201
|
+
with pytest.raises(
|
|
202
|
+
AttributeError,
|
|
203
|
+
match="SetBud subclasses SetStem and therefore does not define the `petals` property",
|
|
204
|
+
):
|
|
205
|
+
_ = simple_bud_pot[2].petals
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_liststem_cached_bud(liststem_bud):
|
|
209
|
+
"""
|
|
210
|
+
Given: A `ListStem` instance and some different input values
|
|
211
|
+
When: Computing the `bud` property after each value is ingested
|
|
212
|
+
Then: The `bud` value correctly updates based on the state of the `ListStem` object
|
|
213
|
+
"""
|
|
214
|
+
key = "Who cares"
|
|
215
|
+
value1 = 3
|
|
216
|
+
liststem_bud.update(key, value1)
|
|
217
|
+
|
|
218
|
+
# Assert twice so we hit the cache
|
|
219
|
+
assert liststem_bud.bud.value == 1
|
|
220
|
+
assert liststem_bud.bud.value == 1
|
|
221
|
+
|
|
222
|
+
value2 = 1
|
|
223
|
+
liststem_bud.update(key, value2)
|
|
224
|
+
|
|
225
|
+
assert liststem_bud.bud.value == 2
|
|
226
|
+
assert liststem_bud.bud.value == 2
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def test_setstem_cached_bud(setstem_bud):
|
|
230
|
+
"""
|
|
231
|
+
Given: A `SetStem` instance and some different input values
|
|
232
|
+
When: Computing the `bud` property after each value is ingested
|
|
233
|
+
Then: The `bud` value correctly updates based on the state of the `SetStem` object
|
|
234
|
+
"""
|
|
235
|
+
key = "Who cares"
|
|
236
|
+
value1 = 3
|
|
237
|
+
setstem_bud.update(key, value1)
|
|
238
|
+
|
|
239
|
+
# Assert twice so we hit the cache
|
|
240
|
+
assert setstem_bud.bud.value == 1
|
|
241
|
+
assert setstem_bud.bud.value == 1
|
|
242
|
+
|
|
243
|
+
value2 = 1
|
|
244
|
+
setstem_bud.update(key, value2)
|
|
245
|
+
|
|
246
|
+
assert setstem_bud.bud.value == 2
|
|
247
|
+
assert setstem_bud.bud.value == 2
|
|
@@ -12,6 +12,8 @@ from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
|
12
12
|
from dkist_processing_common.codecs.fits import fits_hdulist_encoder
|
|
13
13
|
from dkist_processing_common.models.constants import BudName
|
|
14
14
|
from dkist_processing_common.models.fits_access import FitsAccessBase
|
|
15
|
+
from dkist_processing_common.models.flower_pot import ListStem
|
|
16
|
+
from dkist_processing_common.models.flower_pot import SetStem
|
|
15
17
|
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
16
18
|
from dkist_processing_common.models.flower_pot import Stem
|
|
17
19
|
from dkist_processing_common.models.flower_pot import Thorn
|
|
@@ -156,7 +158,27 @@ def empty_buds():
|
|
|
156
158
|
def getter(self, key):
|
|
157
159
|
pass # We'll never get here because we spilled the dirt
|
|
158
160
|
|
|
159
|
-
|
|
161
|
+
class EmptyListBud(ListStem):
|
|
162
|
+
def __init__(self):
|
|
163
|
+
super().__init__(stem_name="EMPTY_LIST_BUD")
|
|
164
|
+
|
|
165
|
+
def setter(self, value):
|
|
166
|
+
return SpilledDirt
|
|
167
|
+
|
|
168
|
+
def getter(self):
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
class EmptySetBud(SetStem):
|
|
172
|
+
def __init__(self):
|
|
173
|
+
super().__init__(stem_name="EMPTY_SET_BUD")
|
|
174
|
+
|
|
175
|
+
def setter(self, value):
|
|
176
|
+
return SpilledDirt
|
|
177
|
+
|
|
178
|
+
def getter(self):
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
return [EmptyBud(), EmptyListBud(), EmptySetBud()]
|
|
160
182
|
|
|
161
183
|
|
|
162
184
|
@pytest.fixture()
|
|
@@ -266,13 +288,15 @@ def test_make_flowerpots(parse_inputs_task):
|
|
|
266
288
|
tag_pot, constant_pot = parse_inputs_task.make_flower_pots()
|
|
267
289
|
|
|
268
290
|
assert len(tag_pot.stems) == 2
|
|
269
|
-
assert len(constant_pot.stems) ==
|
|
291
|
+
assert len(constant_pot.stems) == 6
|
|
270
292
|
assert tag_pot.stems[0].stem_name == StemName.modstate
|
|
271
293
|
assert tag_pot.stems[1].stem_name == "EMPTY_FLOWER"
|
|
272
294
|
assert constant_pot.stems[0].stem_name == BudName.num_modstates
|
|
273
295
|
assert constant_pot.stems[1].stem_name == "LOOKUP_BUD"
|
|
274
296
|
assert constant_pot.stems[2].stem_name == "EMPTY_BUD"
|
|
275
|
-
assert constant_pot.stems[3].stem_name == "
|
|
297
|
+
assert constant_pot.stems[3].stem_name == "EMPTY_LIST_BUD"
|
|
298
|
+
assert constant_pot.stems[4].stem_name == "EMPTY_SET_BUD"
|
|
299
|
+
assert constant_pot.stems[5].stem_name == "PICKY_BUD"
|
|
276
300
|
|
|
277
301
|
|
|
278
302
|
def test_subclass_flowers(visp_parse_inputs_task, max_cs_step_time_sec):
|
|
@@ -336,7 +360,7 @@ def test_constants_correct(parse_inputs_task):
|
|
|
336
360
|
parse_inputs_task.update_constants(constant_pot)
|
|
337
361
|
assert dict(parse_inputs_task.constants._db_dict) == {
|
|
338
362
|
BudName.num_modstates.value: parse_inputs_task._num_mod,
|
|
339
|
-
"LOOKUP_BUD":
|
|
363
|
+
"LOOKUP_BUD": {str(parse_inputs_task._num_mod): [0, 1]},
|
|
340
364
|
}
|
|
341
365
|
|
|
342
366
|
|