dkist-processing-trend 0.1.0rc1__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 (66) hide show
  1. changelog/.gitempty +0 -0
  2. changelog/1.feature.rst +1 -0
  3. dkist_processing_trend/__init__.py +10 -0
  4. dkist_processing_trend/config.py +11 -0
  5. dkist_processing_trend/models/__init__.py +1 -0
  6. dkist_processing_trend/models/constants.py +143 -0
  7. dkist_processing_trend/models/fit_options.py +15 -0
  8. dkist_processing_trend/models/fits_access.py +65 -0
  9. dkist_processing_trend/models/instrument.py +11 -0
  10. dkist_processing_trend/models/instrument_options.py +35 -0
  11. dkist_processing_trend/models/parameters.py +134 -0
  12. dkist_processing_trend/models/tags.py +117 -0
  13. dkist_processing_trend/models/task_name.py +20 -0
  14. dkist_processing_trend/parsers/__init__.py +1 -0
  15. dkist_processing_trend/parsers/arm_id.py +103 -0
  16. dkist_processing_trend/parsers/instrument_unique_bud.py +40 -0
  17. dkist_processing_trend/parsers/time.py +27 -0
  18. dkist_processing_trend/parsers/trend_l0_fits_access.py +123 -0
  19. dkist_processing_trend/tasks/__init__.py +11 -0
  20. dkist_processing_trend/tasks/dark.py +56 -0
  21. dkist_processing_trend/tasks/gain.py +74 -0
  22. dkist_processing_trend/tasks/parse.py +169 -0
  23. dkist_processing_trend/tasks/prepare_fit_data_base.py +257 -0
  24. dkist_processing_trend/tasks/run_pac_fitter.py +392 -0
  25. dkist_processing_trend/tasks/trend_base.py +97 -0
  26. dkist_processing_trend/tasks/trend_output_data.py +167 -0
  27. dkist_processing_trend/tasks/visp/__init__.py +6 -0
  28. dkist_processing_trend/tasks/visp/visp_dmpd.py +405 -0
  29. dkist_processing_trend/tasks/visp/visp_extract_beam.py +14 -0
  30. dkist_processing_trend/tasks/visp/visp_geometric.py +260 -0
  31. dkist_processing_trend/tasks/visp/visp_prep_fit_data.py +162 -0
  32. dkist_processing_trend/tasks/visp/visp_process_demod.py +236 -0
  33. dkist_processing_trend/tasks/write_trend.py +663 -0
  34. dkist_processing_trend/tests/__init__.py +1 -0
  35. dkist_processing_trend/tests/conftest.py +718 -0
  36. dkist_processing_trend/tests/local_trial_workflows/__init__.py +0 -0
  37. dkist_processing_trend/tests/local_trial_workflows/l0_to_trend_visp_polcal.py +294 -0
  38. dkist_processing_trend/tests/local_trial_workflows/local_trial_helpers.py +488 -0
  39. dkist_processing_trend/tests/test_arm_class_factory.py +39 -0
  40. dkist_processing_trend/tests/test_base_tasks.py +86 -0
  41. dkist_processing_trend/tests/test_constants.py +120 -0
  42. dkist_processing_trend/tests/test_dark.py +97 -0
  43. dkist_processing_trend/tests/test_gain.py +135 -0
  44. dkist_processing_trend/tests/test_parameters.py +149 -0
  45. dkist_processing_trend/tests/test_parse.py +276 -0
  46. dkist_processing_trend/tests/test_prep_fit_data_base.py +233 -0
  47. dkist_processing_trend/tests/test_publish_catalog_messages.py +45 -0
  48. dkist_processing_trend/tests/test_run_pac_fitter.py +371 -0
  49. dkist_processing_trend/tests/test_stems.py +75 -0
  50. dkist_processing_trend/tests/test_transfer_output_data.py +76 -0
  51. dkist_processing_trend/tests/test_trend_fits_access.py +173 -0
  52. dkist_processing_trend/tests/test_visp.py +870 -0
  53. dkist_processing_trend/tests/test_workflows.py +10 -0
  54. dkist_processing_trend/tests/test_write_trend.py +460 -0
  55. dkist_processing_trend/workflows/__init__.py +3 -0
  56. dkist_processing_trend/workflows/arm_class_factory.py +30 -0
  57. dkist_processing_trend/workflows/visp.py +59 -0
  58. dkist_processing_trend-0.1.0rc1.dist-info/METADATA +549 -0
  59. dkist_processing_trend-0.1.0rc1.dist-info/RECORD +66 -0
  60. dkist_processing_trend-0.1.0rc1.dist-info/WHEEL +5 -0
  61. dkist_processing_trend-0.1.0rc1.dist-info/top_level.txt +3 -0
  62. docs/conf.py +57 -0
  63. docs/index.rst +10 -0
  64. docs/l0_to_trend_visp_polcal.rst +4 -0
  65. docs/landing_page.rst +11 -0
  66. docs/requirements_table.rst +8 -0
@@ -0,0 +1,488 @@
1
+ import json
2
+ import os
3
+ import shutil
4
+ from dataclasses import asdict
5
+ from pathlib import Path
6
+ from random import randint
7
+
8
+ import asdf
9
+ from astropy.io import fits
10
+ from dkist_header_validator import spec122_validator
11
+ from dkist_processing_common.codecs.basemodel import basemodel_encoder
12
+ from dkist_processing_common.models.input_dataset import InputDatasetPartDocumentList
13
+ from dkist_processing_common.models.task_name import TaskName
14
+ from dkist_processing_common.tasks.base import WorkflowTaskBase
15
+ from dkist_service_configuration.logging import logger
16
+
17
+ from dkist_processing_trend.models.constants import TrendBudName
18
+ from dkist_processing_trend.models.tags import TrendTag
19
+ from dkist_processing_trend.models.task_name import TrendTaskName
20
+ from dkist_processing_trend.tests.conftest import TrendInputDatasetParameterValues
21
+
22
+
23
+ def translate_task(instrument: str, suffix: str):
24
+ class Translate122To214L0(WorkflowTaskBase):
25
+ def run(self) -> None:
26
+ raw_dir = (
27
+ Path(self.scratch.scratch_base_path)
28
+ / f"{instrument.upper()}{self.recipe_run_id:03n}"
29
+ )
30
+ if not self.scratch.workflow_base_path.exists():
31
+ self.scratch.workflow_base_path.mkdir(parents=True, exist_ok=False)
32
+
33
+ if not raw_dir.exists():
34
+ raise FileNotFoundError(
35
+ f"Expected to find a raw {instrument.upper()}{self.recipe_run_id:03n} folder in {self.scratch.scratch_base_path}"
36
+ )
37
+
38
+ for file in raw_dir.glob(f"*.{suffix}"):
39
+ translated_file_name = self.scratch.workflow_base_path / os.path.basename(file)
40
+ logger.info(f"Translating {file} -> {translated_file_name}")
41
+ with fits.open(file, memmap=False) as hdl:
42
+ # Handle both compressed and uncompressed files...
43
+ hdu = hdl[1] if len(hdl) > 1 else hdl[0]
44
+ header = spec122_validator.validate_and_translate_to_214_l0(
45
+ hdu.header, return_type=fits.HDUList
46
+ )[0].header
47
+
48
+ comp_hdu = fits.CompImageHDU(header=header, data=hdu.data)
49
+ comp_hdl = fits.HDUList([fits.PrimaryHDU(), comp_hdu])
50
+ comp_hdl.writeto(translated_file_name, overwrite=True)
51
+
52
+ comp_hdl.close()
53
+ del comp_hdl
54
+
55
+ return Translate122To214L0
56
+
57
+
58
+ def tag_inputs_task(suffix: str):
59
+ class TagInputs(WorkflowTaskBase):
60
+ def run(self) -> None:
61
+ logger.info(f"Looking in {self.scratch.workflow_base_path.absolute()}")
62
+ input_file_list = list(self.scratch.workflow_base_path.glob(f"*.{suffix}"))
63
+ if len(input_file_list) == 0:
64
+ raise FileNotFoundError(
65
+ f"Did not find any files matching '*.{suffix}' in {self.scratch.workflow_base_path}"
66
+ )
67
+ for file in input_file_list:
68
+ logger.info(f"Found {file}")
69
+ self.tag(path=file, tags=[TrendTag.input(), TrendTag.frame()])
70
+
71
+ return TagInputs
72
+
73
+
74
+ def input_dataset_parameter_task(parameter_data: TrendInputDatasetParameterValues):
75
+
76
+ class CreateInputDatasetParameterDocument(WorkflowTaskBase):
77
+ def run(self) -> None:
78
+ relative_path = "input_dataset_parameters.json"
79
+ self.write(
80
+ data=InputDatasetPartDocumentList(
81
+ doc_list=self.input_dataset_document_simple_parameters_part
82
+ ),
83
+ relative_path=relative_path,
84
+ tags=TrendTag.input_dataset_parameters(),
85
+ encoder=basemodel_encoder,
86
+ overwrite=True,
87
+ )
88
+ logger.info(f"Wrote input dataset parameter doc to {relative_path}")
89
+
90
+ @property
91
+ def input_dataset_document_simple_parameters_part(self):
92
+ parameters_list = []
93
+ value_id = randint(1000, 2000)
94
+ for pn, pv in asdict(parameter_data).items():
95
+ values = [
96
+ {
97
+ "parameterValueId": value_id,
98
+ "parameterValue": json.dumps(pv),
99
+ "parameterValueStartDate": "1946-11-20",
100
+ }
101
+ ]
102
+ parameter = {"parameterName": pn, "parameterValues": values}
103
+ parameters_list.append(parameter)
104
+
105
+ return parameters_list
106
+
107
+ return CreateInputDatasetParameterDocument
108
+
109
+
110
+ def save_parsing_task(
111
+ tag_list: list[str], save_file: str, save_file_tags: bool = True, save_constants: bool = True
112
+ ):
113
+ class SaveParsing(WorkflowTaskBase):
114
+ """Save the result of parsing (constants and tags) to an asdf file."""
115
+
116
+ @property
117
+ def relative_save_file(self) -> str:
118
+ return save_file
119
+
120
+ def run(self):
121
+ if save_file_tags:
122
+ file_tag_dict = self.get_input_tags()
123
+ else:
124
+ logger.info("Skipping saving of file tags")
125
+ file_tag_dict = dict()
126
+ if save_constants:
127
+ constant_dict = self.get_constants()
128
+ else:
129
+ logger.info("Skipping saving of constants")
130
+ constant_dict = dict()
131
+
132
+ full_save_file = self.scratch.workflow_base_path / self.relative_save_file
133
+ tree = {"file_tag_dict": file_tag_dict, "constants_dict": constant_dict}
134
+ af = asdf.AsdfFile(tree)
135
+ af.write_to(full_save_file)
136
+ logger.info(f"Saved input tags to {full_save_file}")
137
+
138
+ def get_input_tags(self) -> dict[str, list[str]]:
139
+ file_tag_dict = dict()
140
+ path_list = self.read(tags=tag_list)
141
+ for p in path_list:
142
+ tags = self.tags(p)
143
+ file_tag_dict[str(p)] = tags
144
+
145
+ return file_tag_dict
146
+
147
+ def get_constants(self) -> dict[str, str | float | list]:
148
+ constants_dict = dict()
149
+ for c in self.constants._db_dict.keys():
150
+ constants_dict[c] = self.constants._db_dict[c]
151
+
152
+ return constants_dict
153
+
154
+ return SaveParsing
155
+
156
+
157
+ def load_parsing_task(save_file: str):
158
+ class LoadParsing(WorkflowTaskBase):
159
+ """Load tags and constants into the database."""
160
+
161
+ @property
162
+ def relative_save_file(self) -> str:
163
+ return save_file
164
+
165
+ def run(self):
166
+ full_save_file = self.scratch.workflow_base_path / self.relative_save_file
167
+ with asdf.open(full_save_file) as af:
168
+ file_tag_dict = af.tree["file_tag_dict"]
169
+ self.tag_input_files(file_tag_dict)
170
+
171
+ constants_dict = af.tree["constants_dict"]
172
+ self.populate_constants(constants_dict)
173
+
174
+ logger.info(f"Loaded tags and constants from {full_save_file}")
175
+
176
+ def tag_input_files(self, file_tag_dict: dict[str, list[str]]):
177
+ for f, t in file_tag_dict.items():
178
+ if not os.path.exists(f):
179
+ pass
180
+ # raise FileNotFoundError(f"Expected to find {f}, but it doesn't exist.")
181
+ else:
182
+ self.tag(path=f, tags=t)
183
+
184
+ def populate_constants(self, constants_dict: dict[str, str | int | float]) -> None:
185
+ # First we purge all constants because a previous load might have polluted the DB
186
+ self.constants._purge()
187
+ for c, v in constants_dict.items():
188
+ logger.info(f"Setting value of {c} to {v}")
189
+ self.constants._update({c: v})
190
+
191
+ return LoadParsing
192
+
193
+
194
+ def get_arm_id_list(workflow_base_path: Path, parsing_file_name: str | Path) -> list[str | int]:
195
+ with asdf.open(workflow_base_path / parsing_file_name) as af:
196
+ constants = af.tree["constants_dict"]
197
+ arm_id_list = constants[TrendBudName.arm_id_list]
198
+
199
+ return arm_id_list
200
+
201
+
202
+ class SaveTaskTags(WorkflowTaskBase):
203
+ """Base task for saving all INTERMEDIATE files with the same 'TASK'"""
204
+
205
+ @property
206
+ def task_str(self) -> str:
207
+ return "TASK"
208
+
209
+ @property
210
+ def arm_id(self) -> str | int | None:
211
+ return None
212
+
213
+ @property
214
+ def relative_save_file(self) -> str:
215
+ return "default_sav.asdf"
216
+
217
+ @property
218
+ def tag_lists_to_save(self) -> list[list[str]] | list[str]:
219
+ intermediate_tags = [TrendTag.task(self.task_str), TrendTag.intermediate()]
220
+ if self.arm_id is not None:
221
+ intermediate_tags.append(TrendTag.arm_id(self.arm_id))
222
+ return [intermediate_tags]
223
+
224
+ def run(self):
225
+ file_tag_dict = dict()
226
+ tag_list_list = self.tag_lists_to_save
227
+ if isinstance(tag_list_list[0], str):
228
+ tag_list_list = [tag_list_list]
229
+
230
+ for tags_to_save in tag_list_list:
231
+ path_list = self.read(tags=tags_to_save)
232
+ save_dir = self.scratch.workflow_base_path / Path(self.relative_save_file).stem
233
+ save_dir.mkdir(exist_ok=True)
234
+ for p in path_list:
235
+ copied_path = shutil.copy(str(p), save_dir)
236
+ tags = self.tags(p)
237
+ file_tag_dict[copied_path] = tags
238
+
239
+ full_save_file = self.scratch.workflow_base_path / self.relative_save_file
240
+ tree = {"file_tag_dict": file_tag_dict}
241
+ af = asdf.AsdfFile(tree)
242
+ af.write_to(full_save_file)
243
+ logger.info(f"Saved {self.task_str} to {full_save_file}")
244
+
245
+
246
+ class LoadTaskTags(WorkflowTaskBase):
247
+ """Base task for loading file/tag associations from a previously saved set."""
248
+
249
+ @property
250
+ def relative_save_file(self) -> str:
251
+ return "default_sav.asdf"
252
+
253
+ def run(self):
254
+ full_save_file = self.scratch.workflow_base_path / self.relative_save_file
255
+ with asdf.open(full_save_file) as af:
256
+ for f, t in af.tree["file_tag_dict"].items():
257
+ self.tag(path=f, tags=t)
258
+ logger.info(f"Loaded database entries from {full_save_file}")
259
+
260
+
261
+ def save_dark_cal_task(arm_id: str | int):
262
+ class SaveDarkCal(SaveTaskTags):
263
+ @property
264
+ def task_str(self) -> str:
265
+ return TaskName.dark.value
266
+
267
+ @property
268
+ def arm_id(self):
269
+ return arm_id
270
+
271
+ @property
272
+ def relative_save_file(self) -> str:
273
+ return f"dark_cal_{arm_id}.asdf"
274
+
275
+ return SaveDarkCal
276
+
277
+
278
+ def load_dark_cal_task(arm_id: str | int):
279
+ class LoadDarkCal(LoadTaskTags):
280
+ @property
281
+ def relative_save_file(self) -> str:
282
+ return f"dark_cal_{arm_id}.asdf"
283
+
284
+ return LoadDarkCal
285
+
286
+
287
+ def save_gain_cal_task(arm_id: str | int):
288
+ class SaveGainCal(SaveTaskTags):
289
+ @property
290
+ def task_str(self) -> str:
291
+ return TaskName.gain.value
292
+
293
+ @property
294
+ def arm_id(self):
295
+ return arm_id
296
+
297
+ @property
298
+ def relative_save_file(self) -> str:
299
+ return f"gain_cal_{arm_id}.asdf"
300
+
301
+ return SaveGainCal
302
+
303
+
304
+ def load_gain_cal_task(arm_id: str | int):
305
+ class LoadGainCal(LoadTaskTags):
306
+ @property
307
+ def relative_save_file(self) -> str:
308
+ return f"gain_cal_{arm_id}.asdf"
309
+
310
+ return LoadGainCal
311
+
312
+
313
+ def save_prepped_cal_task(arm_id: str | int):
314
+ class SavePreppedData(SaveTaskTags):
315
+ @property
316
+ def tag_lists_to_save(self):
317
+ return [
318
+ [
319
+ TrendTag.intermediate(),
320
+ TrendTag.arm_id(self.arm_id),
321
+ TrendTag.task_local_pac_input(),
322
+ ],
323
+ [
324
+ TrendTag.intermediate(),
325
+ TrendTag.arm_id(self.arm_id),
326
+ TrendTag.task_global_pac_input(),
327
+ ],
328
+ ]
329
+
330
+ @property
331
+ def arm_id(self):
332
+ return arm_id
333
+
334
+ @property
335
+ def relative_save_file(self) -> str:
336
+ return f"prepped_cal_{arm_id}.asdf"
337
+
338
+ return SavePreppedData
339
+
340
+
341
+ def load_prepped_cal_task(arm_id: str | int):
342
+ class LoadPreppedData(LoadTaskTags):
343
+ @property
344
+ def relative_save_file(self) -> str:
345
+ return f"prepped_cal_{arm_id}.asdf"
346
+
347
+ return LoadPreppedData
348
+
349
+
350
+ def save_pac_fit_task(arm_id: str | int):
351
+ class SavePacFit(SaveTaskTags):
352
+ @property
353
+ def tag_lists_to_save(self):
354
+ return [
355
+ [
356
+ TrendTag.intermediate(),
357
+ TrendTag.arm_id(self.arm_id),
358
+ TrendTag.task_calibration_sequence(),
359
+ ],
360
+ [
361
+ TrendTag.intermediate(),
362
+ TrendTag.arm_id(self.arm_id),
363
+ TrendTag.task_best_fit_parameters(),
364
+ ],
365
+ [
366
+ TrendTag.intermediate(),
367
+ TrendTag.arm_id(self.arm_id),
368
+ TrendTag.task_best_fit_demodulation_matrices(),
369
+ ],
370
+ [
371
+ TrendTag.intermediate(),
372
+ TrendTag.arm_id(self.arm_id),
373
+ TrendTag.task_best_fit_flux(),
374
+ ],
375
+ [
376
+ TrendTag.intermediate(),
377
+ TrendTag.arm_id(self.arm_id),
378
+ TrendTag.task_fit_residuals(),
379
+ ],
380
+ ]
381
+
382
+ @property
383
+ def arm_id(self):
384
+ return arm_id
385
+
386
+ @property
387
+ def relative_save_file(self) -> str:
388
+ return f"pac_fit_{arm_id}.asdf"
389
+
390
+ return SavePacFit
391
+
392
+
393
+ def load_pac_fit_task(arm_id: str | int):
394
+ class LoadPacFit(LoadTaskTags):
395
+ @property
396
+ def relative_save_file(self) -> str:
397
+ return f"pac_fit_{arm_id}.asdf"
398
+
399
+ return LoadPacFit
400
+
401
+
402
+ def save_processed_demod_cal_task(arm_id: str | int):
403
+ class SaveProcessedDemodCal(SaveTaskTags):
404
+ @property
405
+ def task_str(self) -> str:
406
+ return TrendTaskName.processed_demodulation_matrices.value
407
+
408
+ @property
409
+ def arm_id(self):
410
+ return arm_id
411
+
412
+ @property
413
+ def relative_save_file(self) -> str:
414
+ return f"processed_demod_cal_{arm_id}.asdf"
415
+
416
+ return SaveProcessedDemodCal
417
+
418
+
419
+ def load_processed_demod_cal_task(arm_id: str | int):
420
+ class LoadGainCal(LoadTaskTags):
421
+ @property
422
+ def relative_save_file(self) -> str:
423
+ return f"processed_demod_cal_{arm_id}.asdf"
424
+
425
+ return LoadGainCal
426
+
427
+
428
+ def save_dmpd_task(arm_id: str | int):
429
+ class SaveDmpd(SaveTaskTags):
430
+ @property
431
+ def tag_lists_to_save(self):
432
+ return [
433
+ [
434
+ TrendTag.intermediate(),
435
+ TrendTag.arm_id(self.arm_id),
436
+ TrendTag.task_single_beam_demodulated_polcal_data(),
437
+ ],
438
+ [
439
+ TrendTag.intermediate(),
440
+ TrendTag.arm_id(self.arm_id),
441
+ TrendTag.task_demodulated_polcal_data(),
442
+ ],
443
+ ]
444
+
445
+ @property
446
+ def arm_id(self):
447
+ return arm_id
448
+
449
+ @property
450
+ def relative_save_file(self) -> str:
451
+ return f"dmpd_{arm_id}.asdf"
452
+
453
+ return SaveDmpd
454
+
455
+
456
+ def load_dmpd_task(arm_id: str | int):
457
+ class LoadDmpd(LoadTaskTags):
458
+ @property
459
+ def relative_save_file(self) -> str:
460
+ return f"dmpd_{arm_id}.asdf"
461
+
462
+ return LoadDmpd
463
+
464
+
465
+ def save_visp_geo_cal_task(arm_id: str | int):
466
+ class SaveVispGeoCal(SaveTaskTags):
467
+ @property
468
+ def task_str(self) -> str:
469
+ return TrendTaskName.visp_geometric_calibration.value
470
+
471
+ @property
472
+ def arm_id(self):
473
+ return arm_id
474
+
475
+ @property
476
+ def relative_save_file(self) -> str:
477
+ return f"visp_geo_cal_{arm_id}.asdf"
478
+
479
+ return SaveVispGeoCal
480
+
481
+
482
+ def load_visp_geo_cal_task(arm_id: str | int):
483
+ class LoadVispGeoCal(LoadTaskTags):
484
+ @property
485
+ def relative_save_file(self) -> str:
486
+ return f"visp_geo_cal_{arm_id}.asdf"
487
+
488
+ return LoadVispGeoCal
@@ -0,0 +1,39 @@
1
+ import pytest
2
+ from dkist_processing_core import TaskBase
3
+
4
+ from dkist_processing_trend.tasks.trend_base import TrendArmTaskBase
5
+ from dkist_processing_trend.tests.conftest import TrendTestingConstantsDatabase
6
+ from dkist_processing_trend.workflows.arm_class_factory import arm_class_factory
7
+
8
+
9
+ class DummyArmClass(TrendArmTaskBase):
10
+ """A real cool class"""
11
+
12
+ def run(self):
13
+ return 5
14
+
15
+
16
+ @pytest.mark.parametrize("arm_id", [pytest.param("foo", id="str"), pytest.param(3, id="int")])
17
+ def test_arm_class_factory(arm_id, recipe_run_id, link_constants_db):
18
+ """
19
+ Given: A Task class that subclasses `TrendArmTaskBase`
20
+ When: Creating a new version of that task with a pre-defined arm_id
21
+ Then: The new class is a `TaskBase`, has the correct arm_id, and retains the docstring of its parent
22
+ """
23
+ link_constants_db(
24
+ recipe_run_id,
25
+ TrendTestingConstantsDatabase(),
26
+ )
27
+ arm_class = arm_class_factory(DummyArmClass, arm_id=arm_id)
28
+ assert issubclass(arm_class, DummyArmClass)
29
+ assert issubclass(arm_class, TaskBase)
30
+ assert arm_class.__doc__ == "A real cool class"
31
+ assert arm_class.__name__ == f"DummyArmClass{str(arm_id).capitalize()}"
32
+ assert arm_class.__qualname__ == f"DummyArmClass{str(arm_id).capitalize()}"
33
+ assert arm_class.__module__ == DummyArmClass.__module__
34
+
35
+ with arm_class(
36
+ recipe_run_id=recipe_run_id, workflow_version="version", workflow_name="name"
37
+ ) as task:
38
+ assert task.arm_id == arm_id
39
+ assert task.run() == 5
@@ -0,0 +1,86 @@
1
+ import random
2
+
3
+ import pytest
4
+ from dkist_processing_common.codecs.str import str_decoder
5
+ from dkist_processing_common.codecs.str import str_encoder
6
+
7
+ from dkist_processing_trend.models.constants import TrendConstants
8
+ from dkist_processing_trend.models.tags import TrendTag
9
+ from dkist_processing_trend.tasks.trend_base import TrendArmTaskBase
10
+ from dkist_processing_trend.tasks.trend_base import TrendTaskBase
11
+ from dkist_processing_trend.tests.conftest import TrendTestingConstantsDatabase
12
+
13
+
14
+ @pytest.fixture
15
+ def trend_science_task(recipe_run_id, link_constants_db):
16
+ class ScienceTask(TrendTaskBase):
17
+ def run(self): ...
18
+
19
+ link_constants_db(recipe_run_id, TrendTestingConstantsDatabase())
20
+ with ScienceTask(
21
+ recipe_run_id=recipe_run_id, workflow_name="name", workflow_version="version"
22
+ ) as task:
23
+ yield task
24
+ task._purge
25
+
26
+
27
+ class ArmScienceTask(TrendArmTaskBase):
28
+ def run(self):
29
+ self.write(data="Ran .run!", tags=[TrendTag.task("ALL_DONE")], encoder=str_encoder)
30
+
31
+
32
+ @pytest.fixture
33
+ def make_trend_arm_science_task(recipe_run_id, link_constants_db):
34
+
35
+ def make_task(arm_id):
36
+ link_constants_db(recipe_run_id, TrendTestingConstantsDatabase())
37
+ with ArmScienceTask(
38
+ arm_id=arm_id,
39
+ recipe_run_id=recipe_run_id,
40
+ workflow_name="name",
41
+ workflow_version="version",
42
+ ) as task:
43
+ yield task
44
+ task._purge
45
+
46
+ return make_task
47
+
48
+
49
+ def test_trend_base_task(trend_science_task):
50
+ """
51
+ Given: A task class that is a subclass of `TrendTaskBase`
52
+ When: Instantiating the class
53
+ Then: The class' `.constants` property is the correct type
54
+ """
55
+ task = trend_science_task
56
+ assert isinstance(task.constants, TrendConstants)
57
+ assert task.constants.arm_id_list == [1, 2, 3]
58
+
59
+
60
+ @pytest.mark.parametrize(
61
+ "dataset_has_arm_data",
62
+ [pytest.param(False, id="arm_w_no_data"), pytest.param(True, id="arm_w_data")],
63
+ )
64
+ def test_trend_arm_base_task(make_trend_arm_science_task, dataset_has_arm_data):
65
+ """
66
+ Given: A task class that is a subclass of `TrendArmTaskBase`
67
+ When: Instantiating that class
68
+ Then: The `arm_id` argument is correctly set as the `.arm_id` property
69
+ """
70
+ constants_arm_id_list = TrendTestingConstantsDatabase().ARM_ID_LIST
71
+ if dataset_has_arm_data:
72
+ arm_id = random.choice(constants_arm_id_list)
73
+ else:
74
+ arm_id = "LUHILHDLLVUHFSHDUIAHDL"
75
+ assert arm_id not in constants_arm_id_list
76
+
77
+ task = next(make_trend_arm_science_task(arm_id=arm_id))
78
+ assert task.arm_id == arm_id
79
+
80
+ task()
81
+
82
+ if dataset_has_arm_data:
83
+ assert task.count(tags=[TrendTag.task("ALL_DONE")]) == 1
84
+ assert next(task.read(tags=[TrendTag.task("ALL_DONE")], decoder=str_decoder)) == "Ran .run!"
85
+ else:
86
+ assert task.count(tags=[TrendTag.task("ALL_DONE")]) == 0