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.
@@ -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(Stem):
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, key: Hashable) -> tuple[float, ...]:
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(list(self.key_to_petal_dict.values())))
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, key) -> np.float64:
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(key)))
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, key) -> np.float64:
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(key)))
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, key) -> np.float64:
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(key)))
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, key) -> np.float64:
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(key)))
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, key) -> str:
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
- min_time = super().getter(key)[0]
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(Stem):
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, key: Hashable) -> tuple[float, ...]:
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(set(self.key_to_petal_dict.values())))
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(Stem):
12
+ class UniqueBud(SetStem):
13
13
  """
14
- Pre-made flower that reads a single header key from all files and raises a ValueError if it is not unique.
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, key):
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
- value_set = set(self.key_to_petal_dict.values())
62
- if len(value_set) > 1:
63
- raise ValueError(
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 len(stem.petals) == 0:
423
- # There are no petals so nothing to do
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
- def test_fits_hdu_decoder(path_fixture_name, ndarray_object, fits_header, request):
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
- assert np.array_equal(hdu.data, ndarray_object)
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
- def test_fits_access_decoder(path_fixture_name, ndarray_object, fits_header, request):
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(file_path, fits_access_class=DummyFitsAccess)
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
- def test_fits_array_decoder(path_to_primary_fits_file, ndarray_object):
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
- array = fits_array_decoder(path_to_primary_fits_file)
516
- assert np.array_equal(ndarray_object, array)
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 rock and flower with key: value pairs
42
- Then: The rock and flower are correctly updated
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
- return [EmptyBud()]
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) == 4
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 == "PICKY_BUD"
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": [[parse_inputs_task._num_mod, [0, 1]]],
363
+ "LOOKUP_BUD": {str(parse_inputs_task._num_mod): [0, 1]},
340
364
  }
341
365
 
342
366