dkist-processing-common 10.5.4__py3-none-any.whl → 12.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.
- changelog/280.misc.rst +1 -0
- changelog/282.feature.2.rst +2 -0
- changelog/282.feature.rst +2 -0
- changelog/284.feature.rst +1 -0
- changelog/285.feature.rst +2 -0
- changelog/285.misc.rst +2 -0
- changelog/286.feature.rst +2 -0
- changelog/287.misc.rst +1 -0
- dkist_processing_common/__init__.py +1 -0
- dkist_processing_common/_util/constants.py +1 -0
- dkist_processing_common/_util/graphql.py +1 -0
- dkist_processing_common/_util/scratch.py +9 -9
- dkist_processing_common/_util/tags.py +1 -0
- dkist_processing_common/codecs/array.py +20 -0
- dkist_processing_common/codecs/asdf.py +9 -3
- dkist_processing_common/codecs/basemodel.py +22 -0
- dkist_processing_common/codecs/bytes.py +1 -0
- dkist_processing_common/codecs/fits.py +37 -9
- dkist_processing_common/codecs/iobase.py +1 -0
- dkist_processing_common/codecs/json.py +1 -0
- dkist_processing_common/codecs/path.py +1 -0
- dkist_processing_common/codecs/quality.py +1 -1
- dkist_processing_common/codecs/str.py +1 -0
- dkist_processing_common/config.py +64 -25
- dkist_processing_common/manual.py +6 -8
- dkist_processing_common/models/constants.py +373 -37
- dkist_processing_common/models/dkist_location.py +27 -0
- dkist_processing_common/models/fits_access.py +48 -0
- dkist_processing_common/models/flower_pot.py +231 -9
- dkist_processing_common/models/fried_parameter.py +41 -0
- dkist_processing_common/models/graphql.py +66 -75
- dkist_processing_common/models/input_dataset.py +117 -0
- dkist_processing_common/models/message.py +1 -1
- dkist_processing_common/models/message_queue_binding.py +1 -1
- dkist_processing_common/models/metric_code.py +2 -0
- dkist_processing_common/models/parameters.py +65 -28
- dkist_processing_common/models/quality.py +50 -5
- dkist_processing_common/models/tags.py +23 -21
- dkist_processing_common/models/task_name.py +3 -2
- dkist_processing_common/models/telemetry.py +28 -0
- dkist_processing_common/models/wavelength.py +3 -1
- dkist_processing_common/parsers/average_bud.py +46 -0
- dkist_processing_common/parsers/cs_step.py +13 -12
- dkist_processing_common/parsers/dsps_repeat.py +6 -4
- dkist_processing_common/parsers/experiment_id_bud.py +12 -4
- dkist_processing_common/parsers/id_bud.py +42 -27
- dkist_processing_common/parsers/l0_fits_access.py +5 -3
- dkist_processing_common/parsers/l1_fits_access.py +51 -23
- dkist_processing_common/parsers/lookup_bud.py +125 -0
- dkist_processing_common/parsers/near_bud.py +21 -20
- dkist_processing_common/parsers/observing_program_id_bud.py +24 -0
- dkist_processing_common/parsers/proposal_id_bud.py +13 -5
- dkist_processing_common/parsers/quality.py +2 -0
- dkist_processing_common/parsers/retarder.py +32 -0
- dkist_processing_common/parsers/single_value_single_key_flower.py +6 -1
- dkist_processing_common/parsers/task.py +8 -6
- dkist_processing_common/parsers/time.py +178 -72
- dkist_processing_common/parsers/unique_bud.py +21 -22
- dkist_processing_common/parsers/wavelength.py +5 -3
- dkist_processing_common/tasks/__init__.py +3 -2
- dkist_processing_common/tasks/assemble_movie.py +4 -3
- dkist_processing_common/tasks/base.py +59 -60
- dkist_processing_common/tasks/l1_output_data.py +54 -53
- dkist_processing_common/tasks/mixin/globus.py +24 -27
- dkist_processing_common/tasks/mixin/interservice_bus.py +1 -0
- dkist_processing_common/tasks/mixin/metadata_store.py +108 -243
- dkist_processing_common/tasks/mixin/object_store.py +22 -0
- dkist_processing_common/tasks/mixin/quality/__init__.py +1 -0
- dkist_processing_common/tasks/mixin/quality/_base.py +8 -1
- dkist_processing_common/tasks/mixin/quality/_metrics.py +166 -14
- dkist_processing_common/tasks/output_data_base.py +4 -3
- dkist_processing_common/tasks/parse_l0_input_data.py +277 -15
- dkist_processing_common/tasks/quality_metrics.py +9 -9
- dkist_processing_common/tasks/teardown.py +7 -7
- dkist_processing_common/tasks/transfer_input_data.py +67 -69
- dkist_processing_common/tasks/trial_catalog.py +77 -17
- dkist_processing_common/tasks/trial_output_data.py +16 -17
- dkist_processing_common/tasks/write_l1.py +102 -72
- dkist_processing_common/tests/conftest.py +32 -173
- dkist_processing_common/tests/mock_metadata_store.py +271 -0
- dkist_processing_common/tests/test_assemble_movie.py +4 -4
- dkist_processing_common/tests/test_assemble_quality.py +32 -4
- dkist_processing_common/tests/test_base.py +5 -19
- dkist_processing_common/tests/test_codecs.py +103 -12
- dkist_processing_common/tests/test_constants.py +15 -0
- dkist_processing_common/tests/test_dkist_location.py +15 -0
- dkist_processing_common/tests/test_fits_access.py +56 -19
- dkist_processing_common/tests/test_flower_pot.py +147 -5
- dkist_processing_common/tests/test_fried_parameter.py +27 -0
- dkist_processing_common/tests/test_input_dataset.py +78 -361
- dkist_processing_common/tests/test_interservice_bus.py +1 -0
- dkist_processing_common/tests/test_interservice_bus_mixin.py +1 -1
- dkist_processing_common/tests/test_manual_processing.py +33 -0
- dkist_processing_common/tests/test_output_data_base.py +5 -7
- dkist_processing_common/tests/test_parameters.py +71 -22
- dkist_processing_common/tests/test_parse_l0_input_data.py +115 -32
- dkist_processing_common/tests/test_publish_catalog_messages.py +2 -24
- dkist_processing_common/tests/test_quality.py +1 -0
- dkist_processing_common/tests/test_quality_mixin.py +255 -23
- dkist_processing_common/tests/test_scratch.py +2 -1
- dkist_processing_common/tests/test_stems.py +511 -168
- dkist_processing_common/tests/test_submit_dataset_metadata.py +3 -7
- dkist_processing_common/tests/test_tags.py +1 -0
- dkist_processing_common/tests/test_task_name.py +1 -1
- dkist_processing_common/tests/test_task_parsing.py +17 -7
- dkist_processing_common/tests/test_teardown.py +28 -24
- dkist_processing_common/tests/test_transfer_input_data.py +270 -125
- dkist_processing_common/tests/test_transfer_l1_output_data.py +2 -3
- dkist_processing_common/tests/test_trial_catalog.py +83 -8
- dkist_processing_common/tests/test_trial_output_data.py +46 -73
- dkist_processing_common/tests/test_workflow_task_base.py +8 -10
- dkist_processing_common/tests/test_write_l1.py +298 -76
- dkist_processing_common-12.1.0rc1.dist-info/METADATA +265 -0
- dkist_processing_common-12.1.0rc1.dist-info/RECORD +134 -0
- {dkist_processing_common-10.5.4.dist-info → dkist_processing_common-12.1.0rc1.dist-info}/WHEEL +1 -1
- docs/conf.py +1 -0
- docs/index.rst +1 -1
- docs/landing_page.rst +13 -0
- dkist_processing_common/tasks/mixin/input_dataset.py +0 -166
- dkist_processing_common-10.5.4.dist-info/METADATA +0 -175
- dkist_processing_common-10.5.4.dist-info/RECORD +0 -112
- {dkist_processing_common-10.5.4.dist-info → dkist_processing_common-12.1.0rc1.dist-info}/top_level.txt +0 -0
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
"""Mixin for a WorkflowDataTaskBase subclass which implements Metadata Store data access functionality."""
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import logging
|
|
4
4
|
from functools import cached_property
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import validate_call
|
|
5
8
|
|
|
6
9
|
from dkist_processing_common._util.graphql import GraphQLClient
|
|
7
|
-
from dkist_processing_common.codecs.quality import QualityDataEncoder
|
|
8
10
|
from dkist_processing_common.config import common_configurations
|
|
9
11
|
from dkist_processing_common.models.graphql import DatasetCatalogReceiptAccountMutation
|
|
10
12
|
from dkist_processing_common.models.graphql import DatasetCatalogReceiptAccountResponse
|
|
11
13
|
from dkist_processing_common.models.graphql import InputDatasetPartResponse
|
|
12
14
|
from dkist_processing_common.models.graphql import InputDatasetRecipeRunResponse
|
|
13
|
-
from dkist_processing_common.models.graphql import QualitiesRequest
|
|
14
|
-
from dkist_processing_common.models.graphql import QualityCreation
|
|
15
|
-
from dkist_processing_common.models.graphql import QualityResponse
|
|
16
15
|
from dkist_processing_common.models.graphql import RecipeRunMutation
|
|
17
16
|
from dkist_processing_common.models.graphql import RecipeRunMutationResponse
|
|
18
17
|
from dkist_processing_common.models.graphql import RecipeRunProvenanceMutation
|
|
@@ -23,11 +22,8 @@ from dkist_processing_common.models.graphql import RecipeRunStatusMutation
|
|
|
23
22
|
from dkist_processing_common.models.graphql import RecipeRunStatusQuery
|
|
24
23
|
from dkist_processing_common.models.graphql import RecipeRunStatusResponse
|
|
25
24
|
|
|
26
|
-
|
|
27
25
|
logger = logging.getLogger(__name__)
|
|
28
26
|
|
|
29
|
-
input_dataset_part_document_type_hint = list | dict | str | int | float | None
|
|
30
|
-
|
|
31
27
|
|
|
32
28
|
class MetadataStoreMixin:
|
|
33
29
|
"""Mixin for a WorkflowDataTaskBase which implements Metadata Store access functionality."""
|
|
@@ -37,6 +33,8 @@ class MetadataStoreMixin:
|
|
|
37
33
|
"""Get the graphql client."""
|
|
38
34
|
return GraphQLClient(common_configurations.metadata_store_api_base)
|
|
39
35
|
|
|
36
|
+
# RECIPE RUN STATUS
|
|
37
|
+
|
|
40
38
|
def metadata_store_change_recipe_run_to_inprogress(self):
|
|
41
39
|
"""Set the recipe run status to "INPROGRESS"."""
|
|
42
40
|
self._metadata_store_change_status(status="INPROGRESS", is_complete=False)
|
|
@@ -49,237 +47,6 @@ class MetadataStoreMixin:
|
|
|
49
47
|
"""Set the recipe run status to "TRIALSUCCESS"."""
|
|
50
48
|
self._metadata_store_change_status(status="TRIALSUCCESS", is_complete=False)
|
|
51
49
|
|
|
52
|
-
def metadata_store_add_dataset_receipt_account(
|
|
53
|
-
self, dataset_id: str, expected_object_count: int
|
|
54
|
-
):
|
|
55
|
-
"""Set the number of expected objects."""
|
|
56
|
-
params = DatasetCatalogReceiptAccountMutation(
|
|
57
|
-
datasetId=dataset_id, expectedObjectCount=expected_object_count
|
|
58
|
-
)
|
|
59
|
-
self.metadata_store_client.execute_gql_mutation(
|
|
60
|
-
mutation_base="createDatasetCatalogReceiptAccount",
|
|
61
|
-
mutation_parameters=params,
|
|
62
|
-
mutation_response_cls=DatasetCatalogReceiptAccountResponse,
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
def metadata_store_record_provenance(self, is_task_manual: bool, library_versions: str):
|
|
66
|
-
"""Record the provenance record in the metadata store."""
|
|
67
|
-
params = RecipeRunProvenanceMutation(
|
|
68
|
-
inputDatasetId=self.metadata_store_input_dataset_id,
|
|
69
|
-
isTaskManual=is_task_manual,
|
|
70
|
-
recipeRunId=self.recipe_run_id,
|
|
71
|
-
taskName=self.task_name,
|
|
72
|
-
libraryVersions=library_versions,
|
|
73
|
-
workflowVersion=self.workflow_version,
|
|
74
|
-
)
|
|
75
|
-
self.metadata_store_client.execute_gql_mutation(
|
|
76
|
-
mutation_base="createRecipeRunProvenance",
|
|
77
|
-
mutation_parameters=params,
|
|
78
|
-
mutation_response_cls=RecipeRunProvenanceResponse,
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
def metadata_store_add_quality_data(self, dataset_id: str, quality_data: list[dict]):
|
|
82
|
-
"""Add the quality data to the metadata-store."""
|
|
83
|
-
if self.metadata_store_quality_data_exists(dataset_id):
|
|
84
|
-
raise RuntimeError(f"Quality data already persisted for dataset {dataset_id!r}")
|
|
85
|
-
for metric in quality_data:
|
|
86
|
-
if (metric_code := metric.get("metric_code")) is None:
|
|
87
|
-
name = metric.get("name")
|
|
88
|
-
raise ValueError(f"No metric_code for {name!r} in dataset {dataset_id!r}")
|
|
89
|
-
params = QualityCreation(
|
|
90
|
-
datasetId=dataset_id,
|
|
91
|
-
metricCode=metric_code,
|
|
92
|
-
facet=metric.get("facet"),
|
|
93
|
-
name=metric.get("name"),
|
|
94
|
-
description=metric.get("description"),
|
|
95
|
-
statement=metric.get("statement"),
|
|
96
|
-
# JSON array
|
|
97
|
-
warnings=json.dumps(metric.get("warnings")),
|
|
98
|
-
# JSON objects
|
|
99
|
-
plotData=json.dumps(metric.get("plot_data"), cls=QualityDataEncoder),
|
|
100
|
-
tableData=json.dumps(metric.get("table_data"), cls=QualityDataEncoder),
|
|
101
|
-
histogramData=json.dumps(metric.get("histogram_data"), cls=QualityDataEncoder),
|
|
102
|
-
modmatData=json.dumps(metric.get("modmat_data"), cls=QualityDataEncoder),
|
|
103
|
-
raincloudData=json.dumps(metric.get("raincloud_data"), cls=QualityDataEncoder),
|
|
104
|
-
efficiencyData=json.dumps(metric.get("efficiency_data"), cls=QualityDataEncoder),
|
|
105
|
-
)
|
|
106
|
-
self.metadata_store_client.execute_gql_mutation(
|
|
107
|
-
mutation_base="createQuality",
|
|
108
|
-
mutation_parameters=params,
|
|
109
|
-
mutation_response_cls=QualityResponse,
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
def metadata_store_quality_data_exists(self, dataset_id: str) -> bool:
|
|
113
|
-
"""Return True if quality data exists in the metadata-store for the given dataset id."""
|
|
114
|
-
params = QualitiesRequest(datasetId=dataset_id)
|
|
115
|
-
response = self.metadata_store_client.execute_gql_query(
|
|
116
|
-
query_base="qualities",
|
|
117
|
-
query_response_cls=QualityResponse,
|
|
118
|
-
query_parameters=params,
|
|
119
|
-
)
|
|
120
|
-
return bool(response)
|
|
121
|
-
|
|
122
|
-
def metadata_store_recipe_run_configuration(self) -> dict:
|
|
123
|
-
"""Get the recipe run configuration from the metadata store."""
|
|
124
|
-
configuration_json = self._metadata_store_recipe_run().configuration
|
|
125
|
-
if configuration_json is None:
|
|
126
|
-
return {}
|
|
127
|
-
try:
|
|
128
|
-
configuration = json.loads(configuration_json)
|
|
129
|
-
if not isinstance(configuration, dict):
|
|
130
|
-
raise ValueError(
|
|
131
|
-
f"Invalid recipe run configuration format. "
|
|
132
|
-
f"Expected json encoded dictionary, received json encoded {type(configuration)}"
|
|
133
|
-
)
|
|
134
|
-
return configuration
|
|
135
|
-
except (json.JSONDecodeError, ValueError, TypeError, UnicodeDecodeError) as e:
|
|
136
|
-
logger.error(f"Invalid recipe run configuration")
|
|
137
|
-
raise e
|
|
138
|
-
|
|
139
|
-
@cached_property
|
|
140
|
-
def metadata_store_input_dataset_parts(self) -> list[InputDatasetPartResponse]:
|
|
141
|
-
"""Get the input dataset parts from the metadata store."""
|
|
142
|
-
params = RecipeRunQuery(recipeRunId=self.recipe_run_id)
|
|
143
|
-
response = self.metadata_store_client.execute_gql_query(
|
|
144
|
-
query_base="recipeRuns",
|
|
145
|
-
query_response_cls=InputDatasetRecipeRunResponse,
|
|
146
|
-
query_parameters=params,
|
|
147
|
-
) # queried independently of other recipe run metadata for performance
|
|
148
|
-
recipe_run = response[0]
|
|
149
|
-
return [
|
|
150
|
-
part_link.inputDatasetPart
|
|
151
|
-
for part_link in recipe_run.recipeInstance.inputDataset.inputDatasetInputDatasetParts
|
|
152
|
-
]
|
|
153
|
-
|
|
154
|
-
def _metadata_store_filter_input_dataset_parts(
|
|
155
|
-
self, input_dataset_part_type_name: str
|
|
156
|
-
) -> InputDatasetPartResponse | list[InputDatasetPartResponse] | None:
|
|
157
|
-
"""Filter the input dataset parts based on the input dataset part type name."""
|
|
158
|
-
target_parts = [
|
|
159
|
-
part
|
|
160
|
-
for part in self.metadata_store_input_dataset_parts
|
|
161
|
-
if part.inputDatasetPartType.inputDatasetPartTypeName == input_dataset_part_type_name
|
|
162
|
-
]
|
|
163
|
-
if not target_parts:
|
|
164
|
-
return
|
|
165
|
-
if len(target_parts) == 1:
|
|
166
|
-
return target_parts[0]
|
|
167
|
-
raise ValueError(
|
|
168
|
-
f"Multiple ({len(target_parts)}) input dataset parts found for "
|
|
169
|
-
f"{input_dataset_part_type_name=}."
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
@property
|
|
173
|
-
def _metadata_store_input_dataset_observe_frames_part(
|
|
174
|
-
self,
|
|
175
|
-
) -> InputDatasetPartResponse | None:
|
|
176
|
-
"""Get the input dataset part for observe frames."""
|
|
177
|
-
return self._metadata_store_filter_input_dataset_parts(
|
|
178
|
-
input_dataset_part_type_name="observe_frames",
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
@property
|
|
182
|
-
def metadata_store_input_dataset_observe_frames_part_id(self) -> int | None:
|
|
183
|
-
"""Get the input dataset part id for observe frames."""
|
|
184
|
-
if part := self._metadata_store_input_dataset_observe_frames_part:
|
|
185
|
-
return part.inputDatasetPartId
|
|
186
|
-
|
|
187
|
-
@property
|
|
188
|
-
def metadata_store_input_dataset_observe_frames_part_document(
|
|
189
|
-
self,
|
|
190
|
-
) -> input_dataset_part_document_type_hint:
|
|
191
|
-
"""Get the input dataset part document for observe frames."""
|
|
192
|
-
if part := self._metadata_store_input_dataset_observe_frames_part:
|
|
193
|
-
return part.inputDatasetPartDocument
|
|
194
|
-
|
|
195
|
-
@property
|
|
196
|
-
def _metadata_store_input_dataset_calibration_frames_part(
|
|
197
|
-
self,
|
|
198
|
-
) -> InputDatasetPartResponse | None:
|
|
199
|
-
"""Get the input dataset part for calibration frames."""
|
|
200
|
-
return self._metadata_store_filter_input_dataset_parts(
|
|
201
|
-
input_dataset_part_type_name="calibration_frames"
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
@property
|
|
205
|
-
def metadata_store_input_dataset_calibration_frames_part_id(self) -> int | None:
|
|
206
|
-
"""Get the input dataset part id for calibration frames."""
|
|
207
|
-
if part := self._metadata_store_input_dataset_calibration_frames_part:
|
|
208
|
-
return part.inputDatasetPartId
|
|
209
|
-
|
|
210
|
-
@property
|
|
211
|
-
def metadata_store_input_dataset_calibration_frames_part_document(
|
|
212
|
-
self,
|
|
213
|
-
) -> input_dataset_part_document_type_hint:
|
|
214
|
-
"""Get the input dataset part document for calibration frames."""
|
|
215
|
-
if part := self._metadata_store_input_dataset_calibration_frames_part:
|
|
216
|
-
return part.inputDatasetPartDocument
|
|
217
|
-
|
|
218
|
-
@property
|
|
219
|
-
def _metadata_store_input_dataset_parameters_part(
|
|
220
|
-
self,
|
|
221
|
-
) -> InputDatasetPartResponse | None:
|
|
222
|
-
"""Get the input dataset part for parameters."""
|
|
223
|
-
return self._metadata_store_filter_input_dataset_parts(
|
|
224
|
-
input_dataset_part_type_name="parameters"
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
@property
|
|
228
|
-
def metadata_store_input_dataset_parameters_part_id(self) -> int | None:
|
|
229
|
-
"""Get the input dataset part id for parameters."""
|
|
230
|
-
if part := self._metadata_store_input_dataset_parameters_part:
|
|
231
|
-
return part.inputDatasetPartId
|
|
232
|
-
|
|
233
|
-
@property
|
|
234
|
-
def metadata_store_input_dataset_parameters_part_document(
|
|
235
|
-
self,
|
|
236
|
-
) -> input_dataset_part_document_type_hint:
|
|
237
|
-
"""Get the input dataset part document for parameters."""
|
|
238
|
-
if part := self._metadata_store_input_dataset_parameters_part:
|
|
239
|
-
return part.inputDatasetPartDocument
|
|
240
|
-
|
|
241
|
-
@property
|
|
242
|
-
def metadata_store_input_dataset_id(self) -> int:
|
|
243
|
-
"""Get the input dataset id from the metadata store."""
|
|
244
|
-
return self._metadata_store_recipe_run().recipeInstance.inputDatasetId
|
|
245
|
-
|
|
246
|
-
@property
|
|
247
|
-
def metadata_store_recipe_instance_id(self) -> int:
|
|
248
|
-
"""Get the recipe instance id from the metadata store."""
|
|
249
|
-
return self._metadata_store_recipe_run().recipeInstanceId
|
|
250
|
-
|
|
251
|
-
@property
|
|
252
|
-
def metadata_store_recipe_id(self) -> int:
|
|
253
|
-
"""Get the recipe id from the metadata store."""
|
|
254
|
-
return self._metadata_store_recipe_run().recipeInstance.recipeId
|
|
255
|
-
|
|
256
|
-
@property
|
|
257
|
-
def metadata_store_recipe_run_provenance(self) -> list[RecipeRunProvenanceResponse]:
|
|
258
|
-
"""Get all the provenance records for the recipe run."""
|
|
259
|
-
return self._metadata_store_recipe_run().recipeRunProvenances
|
|
260
|
-
|
|
261
|
-
def _metadata_store_recipe_run(self, allow_cache: bool = True) -> RecipeRunResponse:
|
|
262
|
-
is_cached = bool(getattr(self, "_recipe_run_cache", False))
|
|
263
|
-
if is_cached and allow_cache:
|
|
264
|
-
return self._recipe_run_cache
|
|
265
|
-
params = RecipeRunQuery(recipeRunId=self.recipe_run_id)
|
|
266
|
-
response = self.metadata_store_client.execute_gql_query(
|
|
267
|
-
query_base="recipeRuns",
|
|
268
|
-
query_response_cls=RecipeRunResponse,
|
|
269
|
-
query_parameters=params,
|
|
270
|
-
)
|
|
271
|
-
self._recipe_run_cache = response[0]
|
|
272
|
-
return self._recipe_run_cache
|
|
273
|
-
|
|
274
|
-
def _metadata_store_change_status(self, status: str, is_complete: bool):
|
|
275
|
-
"""Change the recipe run status of a recipe run to the given status."""
|
|
276
|
-
recipe_run_status_id = self._metadata_store_recipe_run_status_id(status=status)
|
|
277
|
-
if not recipe_run_status_id:
|
|
278
|
-
recipe_run_status_id = self._metadata_store_create_recipe_run_status(
|
|
279
|
-
status=status, is_complete=is_complete
|
|
280
|
-
)
|
|
281
|
-
self._metadata_store_update_status(recipe_run_status_id=recipe_run_status_id)
|
|
282
|
-
|
|
283
50
|
def _metadata_store_recipe_run_status_id(self, status: str) -> None | int:
|
|
284
51
|
"""Find the id of a recipe run status."""
|
|
285
52
|
params = RecipeRunStatusQuery(recipeRunStatusName=status)
|
|
@@ -291,6 +58,7 @@ class MetadataStoreMixin:
|
|
|
291
58
|
if len(response) > 0:
|
|
292
59
|
return response[0].recipeRunStatusId
|
|
293
60
|
|
|
61
|
+
@validate_call
|
|
294
62
|
def _metadata_store_create_recipe_run_status(self, status: str, is_complete: bool) -> int:
|
|
295
63
|
"""
|
|
296
64
|
Add a new recipe run status to the db.
|
|
@@ -305,10 +73,6 @@ class MetadataStoreMixin:
|
|
|
305
73
|
"marked complete.",
|
|
306
74
|
}
|
|
307
75
|
|
|
308
|
-
if not isinstance(status, str):
|
|
309
|
-
raise TypeError(f"status must be of type str: {status}")
|
|
310
|
-
if not isinstance(is_complete, bool):
|
|
311
|
-
raise TypeError(f"is_complete must be of type bool: {is_complete}")
|
|
312
76
|
params = RecipeRunStatusMutation(
|
|
313
77
|
recipeRunStatusName=status,
|
|
314
78
|
isComplete=is_complete,
|
|
@@ -321,6 +85,15 @@ class MetadataStoreMixin:
|
|
|
321
85
|
)
|
|
322
86
|
return recipe_run_status_response.recipeRunStatus.recipeRunStatusId
|
|
323
87
|
|
|
88
|
+
def _metadata_store_change_status(self, status: str, is_complete: bool):
|
|
89
|
+
"""Change the recipe run status of a recipe run to the given status."""
|
|
90
|
+
recipe_run_status_id = self._metadata_store_recipe_run_status_id(status=status)
|
|
91
|
+
if not recipe_run_status_id:
|
|
92
|
+
recipe_run_status_id = self._metadata_store_create_recipe_run_status(
|
|
93
|
+
status=status, is_complete=is_complete
|
|
94
|
+
)
|
|
95
|
+
self._metadata_store_update_status(recipe_run_status_id=recipe_run_status_id)
|
|
96
|
+
|
|
324
97
|
def _metadata_store_update_status(
|
|
325
98
|
self,
|
|
326
99
|
recipe_run_status_id: int,
|
|
@@ -338,3 +111,95 @@ class MetadataStoreMixin:
|
|
|
338
111
|
mutation_parameters=params,
|
|
339
112
|
mutation_response_cls=RecipeRunMutationResponse,
|
|
340
113
|
)
|
|
114
|
+
|
|
115
|
+
# RECEIPT
|
|
116
|
+
|
|
117
|
+
def metadata_store_add_dataset_receipt_account(
|
|
118
|
+
self, dataset_id: str, expected_object_count: int
|
|
119
|
+
):
|
|
120
|
+
"""Set the number of expected objects."""
|
|
121
|
+
params = DatasetCatalogReceiptAccountMutation(
|
|
122
|
+
datasetId=dataset_id, expectedObjectCount=expected_object_count
|
|
123
|
+
)
|
|
124
|
+
self.metadata_store_client.execute_gql_mutation(
|
|
125
|
+
mutation_base="createDatasetCatalogReceiptAccount",
|
|
126
|
+
mutation_parameters=params,
|
|
127
|
+
mutation_response_cls=DatasetCatalogReceiptAccountResponse,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# PROVENANCE
|
|
131
|
+
|
|
132
|
+
def metadata_store_record_provenance(self, is_task_manual: bool, library_versions: str):
|
|
133
|
+
"""Record the provenance record in the metadata store."""
|
|
134
|
+
params = RecipeRunProvenanceMutation(
|
|
135
|
+
inputDatasetId=self.metadata_store_recipe_run.recipeInstance.inputDatasetId,
|
|
136
|
+
isTaskManual=is_task_manual,
|
|
137
|
+
recipeRunId=self.recipe_run_id,
|
|
138
|
+
taskName=self.task_name,
|
|
139
|
+
libraryVersions=library_versions,
|
|
140
|
+
workflowVersion=self.workflow_version,
|
|
141
|
+
)
|
|
142
|
+
self.metadata_store_client.execute_gql_mutation(
|
|
143
|
+
mutation_base="createRecipeRunProvenance",
|
|
144
|
+
mutation_parameters=params,
|
|
145
|
+
mutation_response_cls=RecipeRunProvenanceResponse,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# INPUT DATASET RECIPE RUN
|
|
149
|
+
|
|
150
|
+
@cached_property
|
|
151
|
+
def metadata_store_input_dataset_recipe_run(self) -> InputDatasetRecipeRunResponse:
|
|
152
|
+
"""Get the input dataset recipe run response from the metadata store."""
|
|
153
|
+
params = RecipeRunQuery(recipeRunId=self.recipe_run_id)
|
|
154
|
+
response = self.metadata_store_client.execute_gql_query(
|
|
155
|
+
query_base="recipeRuns",
|
|
156
|
+
query_response_cls=InputDatasetRecipeRunResponse,
|
|
157
|
+
query_parameters=params,
|
|
158
|
+
)
|
|
159
|
+
return response[0]
|
|
160
|
+
|
|
161
|
+
def _metadata_store_input_dataset_part(
|
|
162
|
+
self, part_type: Literal["observe_frames", "calibration_frames", "parameters"]
|
|
163
|
+
) -> InputDatasetPartResponse:
|
|
164
|
+
"""Get the input dataset part by input dataset part type name."""
|
|
165
|
+
part_types_found = set()
|
|
166
|
+
input_dataset_part = None
|
|
167
|
+
parts = (
|
|
168
|
+
self.metadata_store_input_dataset_recipe_run.recipeInstance.inputDataset.inputDatasetInputDatasetParts
|
|
169
|
+
)
|
|
170
|
+
for part in parts:
|
|
171
|
+
part_type_name = part.inputDatasetPart.inputDatasetPartType.inputDatasetPartTypeName
|
|
172
|
+
if part_type_name in part_types_found:
|
|
173
|
+
raise ValueError(f"Multiple input dataset parts found for {part_type_name=}.")
|
|
174
|
+
part_types_found.add(part_type_name)
|
|
175
|
+
if part_type_name == part_type:
|
|
176
|
+
input_dataset_part = part.inputDatasetPart
|
|
177
|
+
return input_dataset_part
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def metadata_store_input_dataset_observe_frames(self) -> InputDatasetPartResponse:
|
|
181
|
+
"""Get the input dataset part for the observe frames."""
|
|
182
|
+
return self._metadata_store_input_dataset_part(part_type="observe_frames")
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def metadata_store_input_dataset_calibration_frames(self) -> InputDatasetPartResponse:
|
|
186
|
+
"""Get the input dataset part for the calibration frames."""
|
|
187
|
+
return self._metadata_store_input_dataset_part(part_type="calibration_frames")
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def metadata_store_input_dataset_parameters(self) -> InputDatasetPartResponse:
|
|
191
|
+
"""Get the input dataset part for the parameters."""
|
|
192
|
+
return self._metadata_store_input_dataset_part(part_type="parameters")
|
|
193
|
+
|
|
194
|
+
# RECIPE RUN
|
|
195
|
+
|
|
196
|
+
@cached_property
|
|
197
|
+
def metadata_store_recipe_run(self) -> RecipeRunResponse:
|
|
198
|
+
"""Get the recipe run response from the metadata store."""
|
|
199
|
+
params = RecipeRunQuery(recipeRunId=self.recipe_run_id)
|
|
200
|
+
response = self.metadata_store_client.execute_gql_query(
|
|
201
|
+
query_base="recipeRuns",
|
|
202
|
+
query_response_cls=RecipeRunResponse,
|
|
203
|
+
query_parameters=params,
|
|
204
|
+
)
|
|
205
|
+
return response[0]
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Mixin for a WorkflowDataTaskBase subclass which implements Object Store data access functionality."""
|
|
2
|
+
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
|
|
4
5
|
from object_clerk import ObjectClerk
|
|
@@ -54,6 +55,27 @@ class ObjectStoreMixin:
|
|
|
54
55
|
},
|
|
55
56
|
)
|
|
56
57
|
|
|
58
|
+
def object_store_upload_quality_data(
|
|
59
|
+
self,
|
|
60
|
+
quality_data: Path | bytes,
|
|
61
|
+
bucket: str,
|
|
62
|
+
object_key: str,
|
|
63
|
+
content_type: str = "application/json",
|
|
64
|
+
):
|
|
65
|
+
"""Upload quality data to the object store."""
|
|
66
|
+
self.object_store_client.upload_object(
|
|
67
|
+
object_data=quality_data,
|
|
68
|
+
bucket=bucket,
|
|
69
|
+
object_key=object_key,
|
|
70
|
+
verify_checksum=True,
|
|
71
|
+
content_type=content_type,
|
|
72
|
+
metadata={
|
|
73
|
+
"groupname": "DATASET",
|
|
74
|
+
"groupid": self.constants.dataset_id,
|
|
75
|
+
"objecttype": "QDATA",
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
|
|
57
79
|
def object_store_remove_folder_objects(self, bucket: str, path: Path | str) -> list[str]:
|
|
58
80
|
"""
|
|
59
81
|
Remove folder objects (end with /) in the specified bucket and path.
|
|
@@ -4,4 +4,5 @@ To improve readability the top-level mixin, `QualityMixin`, contains only base f
|
|
|
4
4
|
metrics are grouped into sub-mixins. To protect a user, this mixin-on-mixin stack is hidden in protected modules
|
|
5
5
|
and only the top-level mixin (`QualityMixin`) is exposed.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
from ._base import QualityMixin
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Base QualityMixin class that contains machinery common to all metric types."""
|
|
2
|
+
|
|
2
3
|
from typing import Iterable
|
|
3
4
|
|
|
4
5
|
import numpy as np
|
|
@@ -11,10 +12,15 @@ from dkist_processing_common.tasks.mixin.quality._metrics import _PolcalQualityM
|
|
|
11
12
|
from dkist_processing_common.tasks.mixin.quality._metrics import _SimplePlotQualityMixin
|
|
12
13
|
from dkist_processing_common.tasks.mixin.quality._metrics import _SimpleQualityMixin
|
|
13
14
|
from dkist_processing_common.tasks.mixin.quality._metrics import _TableQualityMixin
|
|
15
|
+
from dkist_processing_common.tasks.mixin.quality._metrics import _WavecalQualityMixin
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class QualityMixin(
|
|
17
|
-
_SimpleQualityMixin,
|
|
19
|
+
_SimpleQualityMixin,
|
|
20
|
+
_SimplePlotQualityMixin,
|
|
21
|
+
_TableQualityMixin,
|
|
22
|
+
_PolcalQualityMixin,
|
|
23
|
+
_WavecalQualityMixin,
|
|
18
24
|
):
|
|
19
25
|
"""Mixin class supporting the generation of the quality reports."""
|
|
20
26
|
|
|
@@ -81,6 +87,7 @@ class QualityMixin(
|
|
|
81
87
|
"HISTORICAL": self.quality_build_historical,
|
|
82
88
|
"AO_STATUS": self.quality_build_ao_status,
|
|
83
89
|
"RANGE": self.quality_build_range,
|
|
90
|
+
"WAVECAL_FIT": self.quality_build_wavecal_results,
|
|
84
91
|
}
|
|
85
92
|
|
|
86
93
|
@property
|