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
|
@@ -7,7 +7,6 @@ from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
|
7
7
|
from dkist_processing_common.models.tags import Tag
|
|
8
8
|
from dkist_processing_common.tasks import SubmitDatasetMetadata
|
|
9
9
|
from dkist_processing_common.tasks.mixin import metadata_store
|
|
10
|
-
from dkist_processing_common.tests.conftest import FakeGQLClient
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
@pytest.fixture()
|
|
@@ -86,6 +85,7 @@ def submit_dataset_metadata_task(
|
|
|
86
85
|
def test_submit_dataset_metadata(
|
|
87
86
|
submit_dataset_metadata_task,
|
|
88
87
|
mocker,
|
|
88
|
+
fake_gql_client,
|
|
89
89
|
):
|
|
90
90
|
"""
|
|
91
91
|
:Given: An instance of SubmitDatasetMetadata with tagged processed data
|
|
@@ -93,15 +93,12 @@ def test_submit_dataset_metadata(
|
|
|
93
93
|
:Then: Metadata files for the dataset are saved to the remote database
|
|
94
94
|
"""
|
|
95
95
|
mocker.patch(
|
|
96
|
-
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=
|
|
96
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
|
|
97
97
|
)
|
|
98
|
-
# intercept
|
|
98
|
+
# intercept this GraphQLClient call so it can be confirmed
|
|
99
99
|
mocked_metadata_store_add_dataset_receipt_account = mocker.patch.object(
|
|
100
100
|
metadata_store.MetadataStoreMixin, "metadata_store_add_dataset_receipt_account"
|
|
101
101
|
)
|
|
102
|
-
mocked_metadata_store_add_quality_data = mocker.patch.object(
|
|
103
|
-
metadata_store.MetadataStoreMixin, "metadata_store_add_quality_data"
|
|
104
|
-
)
|
|
105
102
|
task = submit_dataset_metadata_task
|
|
106
103
|
|
|
107
104
|
# When
|
|
@@ -109,4 +106,3 @@ def test_submit_dataset_metadata(
|
|
|
109
106
|
|
|
110
107
|
# Then
|
|
111
108
|
mocked_metadata_store_add_dataset_receipt_account.assert_called_once()
|
|
112
|
-
mocked_metadata_store_add_quality_data.assert_called_once()
|
|
@@ -19,7 +19,7 @@ def test_task_name_tags():
|
|
|
19
19
|
assert Tag.task_geometric() == f"TASK_{TaskName.geometric.value}"
|
|
20
20
|
assert Tag.task_geometric_angle() == f"TASK_{TaskName.geometric_angle.value}"
|
|
21
21
|
assert (
|
|
22
|
-
Tag.
|
|
22
|
+
Tag.task_geometric_spectral_shifts() == f"TASK_{TaskName.geometric_spectral_shifts.value}"
|
|
23
23
|
)
|
|
24
24
|
assert Tag.task_geometric_offset() == f"TASK_{TaskName.geometric_offsets.value}"
|
|
25
25
|
assert Tag.task_polcal() == f"TASK_{TaskName.polcal.value}"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
1
3
|
import pytest
|
|
2
4
|
from astropy.io import fits
|
|
3
5
|
|
|
@@ -9,6 +11,15 @@ from dkist_processing_common.parsers.task import parse_polcal_task_type
|
|
|
9
11
|
from dkist_processing_common.parsers.task import passthrough_header_ip_task
|
|
10
12
|
|
|
11
13
|
|
|
14
|
+
class DummyMetadataKey(StrEnum):
|
|
15
|
+
ip_task_type = "IPTASK"
|
|
16
|
+
gos_level3_status = "GOSLVL3"
|
|
17
|
+
gos_level3_lamp_status = "GOSLAMP"
|
|
18
|
+
gos_level0_status = "GOSLVL0"
|
|
19
|
+
gos_retarder_status = "GOSRET"
|
|
20
|
+
gos_polarizer_status = "GOSPOL"
|
|
21
|
+
|
|
22
|
+
|
|
12
23
|
class DummyFitsAccess(FitsAccessBase):
|
|
13
24
|
def __init__(
|
|
14
25
|
self,
|
|
@@ -17,13 +28,12 @@ class DummyFitsAccess(FitsAccessBase):
|
|
|
17
28
|
auto_squeeze: bool = False, # Because L1 data should always have the right form, right?
|
|
18
29
|
):
|
|
19
30
|
super().__init__(hdu=hdu, name=name, auto_squeeze=auto_squeeze)
|
|
20
|
-
|
|
21
|
-
self.
|
|
22
|
-
self.
|
|
23
|
-
self.
|
|
24
|
-
self.
|
|
25
|
-
self.
|
|
26
|
-
self.gos_polarizer_status: str = self.header["GOSPOL"]
|
|
31
|
+
self.ip_task_type = self.header[DummyMetadataKey.ip_task_type]
|
|
32
|
+
self.gos_level3_status = self.header[DummyMetadataKey.gos_level3_status]
|
|
33
|
+
self.gos_level3_lamp_status = self.header[DummyMetadataKey.gos_level3_lamp_status]
|
|
34
|
+
self.gos_level0_status = self.header[DummyMetadataKey.gos_level0_status]
|
|
35
|
+
self.gos_retarder_status = self.header[DummyMetadataKey.gos_retarder_status]
|
|
36
|
+
self.gos_polarizer_status = self.header[DummyMetadataKey.gos_polarizer_status]
|
|
27
37
|
|
|
28
38
|
|
|
29
39
|
@pytest.fixture
|
|
@@ -5,10 +5,11 @@ import pytest
|
|
|
5
5
|
|
|
6
6
|
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
7
7
|
from dkist_processing_common.codecs.str import str_encoder
|
|
8
|
-
from dkist_processing_common.models.graphql import RecipeRunResponse
|
|
9
8
|
from dkist_processing_common.models.tags import Tag
|
|
10
9
|
from dkist_processing_common.tasks.teardown import Teardown
|
|
11
|
-
from dkist_processing_common.tests.
|
|
10
|
+
from dkist_processing_common.tests.mock_metadata_store import RecipeRunResponseMapping
|
|
11
|
+
from dkist_processing_common.tests.mock_metadata_store import fake_gql_client_factory
|
|
12
|
+
from dkist_processing_common.tests.mock_metadata_store import make_default_recipe_run_response
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class TeardownTest(Teardown):
|
|
@@ -18,33 +19,36 @@ class TeardownTest(Teardown):
|
|
|
18
19
|
|
|
19
20
|
@pytest.fixture()
|
|
20
21
|
def make_mock_GQL_with_configuration():
|
|
21
|
-
def class_generator(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
def class_generator(teardown_option: bool | None):
|
|
23
|
+
recipe_run_response = make_default_recipe_run_response()
|
|
24
|
+
config = recipe_run_response.configuration
|
|
25
|
+
if isinstance(teardown_option, bool):
|
|
26
|
+
config.teardown_enabled = teardown_option
|
|
27
|
+
else:
|
|
28
|
+
config_dict = config.model_dump(exclude="teardown_enabled")
|
|
29
|
+
config = json.dumps(config_dict)
|
|
30
|
+
response_mapping_override = RecipeRunResponseMapping(response=recipe_run_response)
|
|
31
|
+
TeardownFakeGQLClient = fake_gql_client_factory(
|
|
32
|
+
response_mapping_override=response_mapping_override
|
|
33
|
+
)
|
|
30
34
|
return TeardownFakeGQLClient
|
|
31
35
|
|
|
32
36
|
return class_generator
|
|
33
37
|
|
|
34
38
|
|
|
35
39
|
@pytest.fixture(scope="session")
|
|
36
|
-
def
|
|
37
|
-
return
|
|
40
|
+
def teardown_enabled() -> bool:
|
|
41
|
+
return True
|
|
38
42
|
|
|
39
43
|
|
|
40
44
|
@pytest.fixture(scope="session")
|
|
41
|
-
def
|
|
42
|
-
return
|
|
45
|
+
def teardown_disabled() -> bool:
|
|
46
|
+
return False
|
|
43
47
|
|
|
44
48
|
|
|
45
49
|
@pytest.fixture(scope="session")
|
|
46
|
-
def
|
|
47
|
-
return
|
|
50
|
+
def teardown_default() -> None:
|
|
51
|
+
return None
|
|
48
52
|
|
|
49
53
|
|
|
50
54
|
@pytest.fixture(scope="function")
|
|
@@ -75,14 +79,14 @@ def teardown_task_factory(tmp_path, recipe_run_id):
|
|
|
75
79
|
|
|
76
80
|
|
|
77
81
|
def test_purge_data(
|
|
78
|
-
teardown_task_factory, make_mock_GQL_with_configuration,
|
|
82
|
+
teardown_task_factory, make_mock_GQL_with_configuration, teardown_enabled, mocker
|
|
79
83
|
):
|
|
80
84
|
"""
|
|
81
85
|
:Given: A Teardown task with files and tags linked to it and teardown enabled
|
|
82
86
|
:When: Running the task
|
|
83
87
|
:Then: All the files are deleted and the tags are removed
|
|
84
88
|
"""
|
|
85
|
-
FakeGQLClass = make_mock_GQL_with_configuration(
|
|
89
|
+
FakeGQLClass = make_mock_GQL_with_configuration(teardown_enabled)
|
|
86
90
|
mocker.patch(
|
|
87
91
|
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClass
|
|
88
92
|
)
|
|
@@ -102,14 +106,14 @@ def test_purge_data(
|
|
|
102
106
|
|
|
103
107
|
|
|
104
108
|
def test_purge_data_disabled(
|
|
105
|
-
teardown_task_factory, make_mock_GQL_with_configuration,
|
|
109
|
+
teardown_task_factory, make_mock_GQL_with_configuration, teardown_disabled, mocker
|
|
106
110
|
):
|
|
107
111
|
"""
|
|
108
112
|
:Given: A Teardown task with files and tags linked to it and teardown disabled
|
|
109
113
|
:When: Running the task
|
|
110
114
|
:Then: All the files are not deleted and the tags remain
|
|
111
115
|
"""
|
|
112
|
-
FakeGQLClass = make_mock_GQL_with_configuration(
|
|
116
|
+
FakeGQLClass = make_mock_GQL_with_configuration(teardown_disabled)
|
|
113
117
|
mocker.patch(
|
|
114
118
|
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClass
|
|
115
119
|
)
|
|
@@ -129,14 +133,14 @@ def test_purge_data_disabled(
|
|
|
129
133
|
|
|
130
134
|
|
|
131
135
|
def test_purge_data_no_config(
|
|
132
|
-
teardown_task_factory, make_mock_GQL_with_configuration,
|
|
136
|
+
teardown_task_factory, make_mock_GQL_with_configuration, teardown_default, mocker
|
|
133
137
|
):
|
|
134
138
|
"""
|
|
135
|
-
:Given: A Teardown task with files and tags linked and teardown
|
|
139
|
+
:Given: A Teardown task with files and tags linked and default teardown configuration
|
|
136
140
|
:When: Running the task
|
|
137
141
|
:Then: All the files are deleted and the tags are removed
|
|
138
142
|
"""
|
|
139
|
-
FakeGQLClass = make_mock_GQL_with_configuration(
|
|
143
|
+
FakeGQLClass = make_mock_GQL_with_configuration(teardown_default)
|
|
140
144
|
mocker.patch(
|
|
141
145
|
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClass
|
|
142
146
|
)
|
|
@@ -1,167 +1,312 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
|
-
from itertools import chain
|
|
4
3
|
from pathlib import Path
|
|
5
|
-
from uuid import uuid4
|
|
6
4
|
|
|
7
5
|
import pytest
|
|
8
6
|
|
|
9
7
|
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
8
|
+
from dkist_processing_common.codecs.basemodel import basemodel_decoder
|
|
9
|
+
from dkist_processing_common.models.input_dataset import InputDatasetPartDocumentList
|
|
10
10
|
from dkist_processing_common.models.tags import Tag
|
|
11
|
+
from dkist_processing_common.tasks import WorkflowTaskBase
|
|
11
12
|
from dkist_processing_common.tasks.transfer_input_data import TransferL0Data
|
|
12
|
-
from dkist_processing_common.tests.
|
|
13
|
+
from dkist_processing_common.tests.mock_metadata_store import InputDatasetRecipeRunResponseMapping
|
|
14
|
+
from dkist_processing_common.tests.mock_metadata_store import default_calibration_frames_doc
|
|
15
|
+
from dkist_processing_common.tests.mock_metadata_store import default_observe_frames_doc
|
|
16
|
+
from dkist_processing_common.tests.mock_metadata_store import default_parameters_doc
|
|
17
|
+
from dkist_processing_common.tests.mock_metadata_store import fake_gql_client_factory
|
|
18
|
+
from dkist_processing_common.tests.mock_metadata_store import (
|
|
19
|
+
make_default_input_dataset_recipe_run_response,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_parameter_files(
|
|
24
|
+
task: WorkflowTaskBase, parameters_doc: list[dict] = default_parameters_doc
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Create the parameter files specified in the parameters document returned by the metadata store.
|
|
28
|
+
|
|
29
|
+
This fixture assumes that the JSON parameters document has already been loaded into a python
|
|
30
|
+
structure (a list of dicts), but the parameter values themselves are still JSON.
|
|
31
|
+
"""
|
|
32
|
+
for parameter in parameters_doc:
|
|
33
|
+
for value in parameter["parameterValues"]:
|
|
34
|
+
if "__file__" not in value["parameterValue"]:
|
|
35
|
+
continue
|
|
36
|
+
parameter_value = json.loads(value["parameterValue"])
|
|
37
|
+
param_path = parameter_value["__file__"]["objectKey"]
|
|
38
|
+
file_path = task.scratch.workflow_base_path / Path(param_path)
|
|
39
|
+
if not file_path.parent.exists():
|
|
40
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
file_path.write_text(data="")
|
|
42
|
+
task.tag(path=file_path, tags=Tag.parameter(param_path))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def create_input_frames(
|
|
46
|
+
task: WorkflowTaskBase,
|
|
47
|
+
input_frame_docs: list[dict] = default_observe_frames_doc + default_calibration_frames_doc,
|
|
48
|
+
):
|
|
49
|
+
"""
|
|
50
|
+
Create the observe and calibration frame files specified in the input dataset documents
|
|
51
|
+
returned by the metadata store.
|
|
52
|
+
"""
|
|
53
|
+
for frame in input_frame_docs:
|
|
54
|
+
for object_key in frame["object_keys"]:
|
|
55
|
+
file_path = task.scratch.workflow_base_path / Path(object_key)
|
|
56
|
+
if not file_path.parent.exists():
|
|
57
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
file_path.write_text(data="")
|
|
59
|
+
task.tag(path=file_path, tags=[Tag.frame(), Tag.input()])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class TransferL0DataTask(TransferL0Data):
|
|
63
|
+
def run(self) -> None: ...
|
|
13
64
|
|
|
14
65
|
|
|
15
66
|
@pytest.fixture
|
|
16
|
-
def
|
|
17
|
-
|
|
67
|
+
def fake_gql_client_class_missing_calibration_part():
|
|
68
|
+
input_dataset_recipe_run_response = make_default_input_dataset_recipe_run_response()
|
|
69
|
+
dataset_parts = (
|
|
70
|
+
input_dataset_recipe_run_response.recipeInstance.inputDataset.inputDatasetInputDatasetParts
|
|
71
|
+
)
|
|
72
|
+
for index, part in enumerate(dataset_parts):
|
|
73
|
+
if (
|
|
74
|
+
part.inputDatasetPart.inputDatasetPartType.inputDatasetPartTypeName
|
|
75
|
+
== "calibration_frames"
|
|
76
|
+
):
|
|
77
|
+
del dataset_parts[index]
|
|
78
|
+
new_response_mapping = InputDatasetRecipeRunResponseMapping(
|
|
79
|
+
response=input_dataset_recipe_run_response
|
|
80
|
+
)
|
|
81
|
+
FakeGQLClientMissingInputDatasetCalibrationPart = fake_gql_client_factory(
|
|
82
|
+
response_mapping_override=new_response_mapping
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return FakeGQLClientMissingInputDatasetCalibrationPart
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _transfer_l0_data_task_with_client(recipe_run_id, tmp_path, mocker, client_cls):
|
|
89
|
+
mocker.patch(
|
|
90
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient",
|
|
91
|
+
new=client_cls,
|
|
92
|
+
)
|
|
93
|
+
with TransferL0DataTask(
|
|
18
94
|
recipe_run_id=recipe_run_id,
|
|
19
95
|
workflow_name="workflow_name",
|
|
20
96
|
workflow_version="workflow_version",
|
|
97
|
+
) as task:
|
|
98
|
+
task.scratch = WorkflowFileSystem(
|
|
99
|
+
recipe_run_id=recipe_run_id,
|
|
100
|
+
scratch_base_path=tmp_path,
|
|
101
|
+
)
|
|
102
|
+
yield task
|
|
103
|
+
task._purge()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@pytest.fixture
|
|
107
|
+
def transfer_l0_data_task(recipe_run_id, tmp_path, mocker, fake_gql_client):
|
|
108
|
+
yield from _transfer_l0_data_task_with_client(recipe_run_id, tmp_path, mocker, fake_gql_client)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@pytest.fixture
|
|
112
|
+
def transfer_l0_data_task_missing_calibration_part(
|
|
113
|
+
recipe_run_id, tmp_path, mocker, fake_gql_client_class_missing_calibration_part
|
|
114
|
+
):
|
|
115
|
+
yield from _transfer_l0_data_task_with_client(
|
|
116
|
+
recipe_run_id, tmp_path, mocker, fake_gql_client_class_missing_calibration_part
|
|
21
117
|
)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"__file__": {
|
|
46
|
-
"bucket": "data",
|
|
47
|
-
"objectKey": f"parameters/param_name/{uuid4().hex}.dat",
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
),
|
|
51
|
-
"parameterValueStartDate": "2000-01-01",
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
"parameterValueId": 3,
|
|
55
|
-
"parameterValue": json.dumps(
|
|
56
|
-
{
|
|
57
|
-
"__file__": {
|
|
58
|
-
"bucket": "data",
|
|
59
|
-
"objectKey": f"parameters/param_name/{uuid4().hex}.dat",
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
),
|
|
63
|
-
"parameterValueStartDate": "2000-01-02",
|
|
64
|
-
},
|
|
65
|
-
],
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
"parameterName": "param_name_4",
|
|
69
|
-
"parameterValues": [
|
|
70
|
-
{
|
|
71
|
-
"parameterValueId": 4,
|
|
72
|
-
"parameterValue": json.dumps(
|
|
73
|
-
{"a": 1, "b": 3.14159, "c": "foo", "d": [1, 2, 3]}
|
|
74
|
-
),
|
|
75
|
-
"parameterValueStartDate": "2000-01-01",
|
|
76
|
-
}
|
|
77
|
-
],
|
|
78
|
-
},
|
|
79
|
-
]
|
|
80
|
-
input_dataset_observe_frames_part = [
|
|
81
|
-
{
|
|
82
|
-
"bucket": uuid4().hex[:6],
|
|
83
|
-
"object_keys": [Path(uuid4().hex[:6]).as_posix() for _ in range(3)],
|
|
84
|
-
}
|
|
85
|
-
]
|
|
86
|
-
input_dataset_calibration_frames_part = [
|
|
87
|
-
{
|
|
88
|
-
"bucket": uuid4().hex[:6],
|
|
89
|
-
"object_keys": [Path(uuid4().hex[:6]).as_posix() for _ in range(3)],
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
"bucket": uuid4().hex[:6],
|
|
93
|
-
"object_keys": [Path(uuid4().hex[:6]).as_posix() for _ in range(3)],
|
|
94
|
-
},
|
|
95
|
-
]
|
|
96
|
-
# load parameters file
|
|
97
|
-
file_path = task.scratch.workflow_base_path / Path(f"{uuid4().hex[:6]}.ext")
|
|
98
|
-
file_path.write_text(data=json.dumps(input_dataset_parameters_part))
|
|
99
|
-
task.tag(path=file_path, tags=Tag.input_dataset_parameters())
|
|
100
|
-
# create parameter files
|
|
101
|
-
expected_parameters = dict()
|
|
102
|
-
for item in input_dataset_parameters_part:
|
|
103
|
-
expected_parameters[item["parameterName"]] = item["parameterValues"]
|
|
104
|
-
create_parameter_files(task, expected_parameters)
|
|
105
|
-
# load observe frames file
|
|
106
|
-
file_path = task.scratch.workflow_base_path / Path(f"{uuid4().hex[:6]}.ext")
|
|
107
|
-
file_path.write_text(data=json.dumps(input_dataset_observe_frames_part))
|
|
108
|
-
task.tag(path=file_path, tags=Tag.input_dataset_observe_frames())
|
|
109
|
-
# load calibration frames file
|
|
110
|
-
file_path = task.scratch.workflow_base_path / Path(f"{uuid4().hex[:6]}.ext")
|
|
111
|
-
file_path.write_text(data=json.dumps(input_dataset_calibration_frames_part))
|
|
112
|
-
task.tag(path=file_path, tags=Tag.input_dataset_calibration_frames())
|
|
113
|
-
|
|
114
|
-
yield {
|
|
115
|
-
"task": task,
|
|
116
|
-
"parameters": input_dataset_parameters_part,
|
|
117
|
-
"observe": input_dataset_observe_frames_part,
|
|
118
|
-
"calibration": input_dataset_calibration_frames_part,
|
|
119
|
-
}
|
|
120
|
-
task._purge()
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def test_format_frame_transfer_items(transfer_l0_data):
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@pytest.mark.parametrize(
|
|
121
|
+
"expected_doc, tag",
|
|
122
|
+
[
|
|
123
|
+
pytest.param(
|
|
124
|
+
default_observe_frames_doc,
|
|
125
|
+
Tag.input_dataset_observe_frames(),
|
|
126
|
+
id="observe_frames",
|
|
127
|
+
),
|
|
128
|
+
pytest.param(
|
|
129
|
+
default_calibration_frames_doc,
|
|
130
|
+
Tag.input_dataset_calibration_frames(),
|
|
131
|
+
id="calibration_frames",
|
|
132
|
+
),
|
|
133
|
+
pytest.param(
|
|
134
|
+
default_parameters_doc,
|
|
135
|
+
Tag.input_dataset_parameters(),
|
|
136
|
+
id="parameters",
|
|
137
|
+
),
|
|
138
|
+
],
|
|
139
|
+
)
|
|
140
|
+
def test_download_dataset(transfer_l0_data_task, expected_doc, tag):
|
|
124
141
|
"""
|
|
125
142
|
:Given: a TransferL0Data task with a valid input dataset
|
|
126
|
-
:When:
|
|
127
|
-
:Then: the
|
|
143
|
+
:When: downloading the dataset documents from the metadata store
|
|
144
|
+
:Then: the correct documents are written to disk, along with tags for file parameters
|
|
145
|
+
"""
|
|
146
|
+
# Given
|
|
147
|
+
task = transfer_l0_data_task
|
|
148
|
+
# When
|
|
149
|
+
task.download_input_dataset()
|
|
150
|
+
# Then
|
|
151
|
+
doc_from_file = next(
|
|
152
|
+
task.read(tags=tag, decoder=basemodel_decoder, model=InputDatasetPartDocumentList)
|
|
153
|
+
)
|
|
154
|
+
doc_list_from_file = doc_from_file.model_dump()["doc_list"]
|
|
155
|
+
if (
|
|
156
|
+
tag == Tag.input_dataset_parameters()
|
|
157
|
+
): # parameter doc gets written with tags for file objects
|
|
158
|
+
for item in expected_doc:
|
|
159
|
+
for val in item["parameterValues"]:
|
|
160
|
+
if "__file__" in val["parameterValue"]:
|
|
161
|
+
file_dict = json.loads(val["parameterValue"])["__file__"]
|
|
162
|
+
file_dict["tag"] = Tag.parameter(Path(file_dict["objectKey"]).name)
|
|
163
|
+
val["parameterValue"] = json.dumps({"__file__": file_dict})
|
|
164
|
+
assert doc_list_from_file == expected_doc
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_download_dataset_missing_part(transfer_l0_data_task_missing_calibration_part):
|
|
128
168
|
"""
|
|
129
|
-
task
|
|
169
|
+
:Given: a TransferL0Data task with a valid input dataset without calibration frames
|
|
170
|
+
:When: downloading the dataset documents from the metadata store
|
|
171
|
+
:Then: the correct number of documents are written to disk
|
|
172
|
+
"""
|
|
173
|
+
# Given
|
|
174
|
+
task = transfer_l0_data_task_missing_calibration_part
|
|
175
|
+
# When
|
|
176
|
+
task.download_input_dataset()
|
|
177
|
+
# Then
|
|
178
|
+
observe_doc_from_file = next(
|
|
179
|
+
task.read(
|
|
180
|
+
tags=Tag.input_dataset_observe_frames(),
|
|
181
|
+
decoder=basemodel_decoder,
|
|
182
|
+
model=InputDatasetPartDocumentList,
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
parameters_doc_from_file = next(
|
|
186
|
+
task.read(
|
|
187
|
+
tags=Tag.input_dataset_parameters(),
|
|
188
|
+
decoder=basemodel_decoder,
|
|
189
|
+
model=InputDatasetPartDocumentList,
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
with pytest.raises(StopIteration):
|
|
193
|
+
calibration_doc_from_file = next(
|
|
194
|
+
task.read(
|
|
195
|
+
tags=Tag.input_dataset_calibration_frames(),
|
|
196
|
+
decoder=basemodel_decoder,
|
|
197
|
+
model=InputDatasetPartDocumentList,
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@pytest.mark.parametrize(
|
|
203
|
+
"task_name",
|
|
204
|
+
[
|
|
205
|
+
pytest.param(
|
|
206
|
+
"transfer_l0_data_task",
|
|
207
|
+
id="observe_and_calibration_frames",
|
|
208
|
+
),
|
|
209
|
+
pytest.param(
|
|
210
|
+
"transfer_l0_data_task_missing_calibration_part",
|
|
211
|
+
id="calibration_frames_missing",
|
|
212
|
+
),
|
|
213
|
+
],
|
|
214
|
+
)
|
|
215
|
+
def test_build_frame_transfer_list_formatted(request, task_name):
|
|
216
|
+
"""
|
|
217
|
+
:Given: a TransferL0Data task with downloaded input dataset docs
|
|
218
|
+
:When: building a list of frames in the input dataset formatted for transfer
|
|
219
|
+
:Then: the correct items are correctly loaded into GlobusTransferItem objects
|
|
220
|
+
"""
|
|
221
|
+
# Given
|
|
222
|
+
task = request.getfixturevalue(task_name)
|
|
223
|
+
task.download_input_dataset()
|
|
224
|
+
# When
|
|
225
|
+
observe_transfer_objects = task.build_transfer_list(doc_tag=Tag.input_dataset_observe_frames())
|
|
226
|
+
calibration_transfer_objects = task.build_transfer_list(
|
|
227
|
+
doc_tag=Tag.input_dataset_calibration_frames()
|
|
228
|
+
)
|
|
229
|
+
transfer_objects = observe_transfer_objects + calibration_transfer_objects
|
|
230
|
+
formatted_transfer_items = task.format_transfer_items(input_dataset_objects=transfer_objects)
|
|
231
|
+
# Then
|
|
130
232
|
source_filenames = []
|
|
131
233
|
destination_filenames = []
|
|
132
|
-
|
|
234
|
+
expected_frames = list(default_observe_frames_doc)
|
|
235
|
+
if "missing_calibration_part" not in task_name:
|
|
236
|
+
expected_frames += default_calibration_frames_doc
|
|
237
|
+
for frame_set in expected_frames:
|
|
133
238
|
for key in frame_set["object_keys"]:
|
|
134
239
|
source_filenames.append(os.path.join("/", frame_set["bucket"], key))
|
|
135
240
|
destination_filenames.append(Path(key).name)
|
|
136
|
-
assert len(
|
|
137
|
-
for item in
|
|
241
|
+
assert len(formatted_transfer_items) == len(source_filenames)
|
|
242
|
+
for item in formatted_transfer_items:
|
|
138
243
|
assert item.source_path.as_posix() in source_filenames
|
|
139
244
|
assert item.destination_path.name in destination_filenames
|
|
140
245
|
assert not item.recursive
|
|
141
246
|
|
|
142
247
|
|
|
143
|
-
def
|
|
248
|
+
def test_build_parameter_file_transfer_items(transfer_l0_data_task):
|
|
144
249
|
"""
|
|
145
|
-
:Given: a TransferL0Data task with
|
|
146
|
-
:When:
|
|
147
|
-
:Then: the items are correctly loaded into GlobusTransferItem objects
|
|
250
|
+
:Given: a TransferL0Data task with downloaded input dataset docs
|
|
251
|
+
:When: building a list of parameter files formatted for transfer
|
|
252
|
+
:Then: the correct items are correctly loaded into GlobusTransferItem objects
|
|
148
253
|
"""
|
|
149
|
-
|
|
254
|
+
# Given
|
|
255
|
+
task = transfer_l0_data_task
|
|
256
|
+
task.download_input_dataset()
|
|
257
|
+
# When
|
|
258
|
+
transfer_objects = task.build_transfer_list(doc_tag=Tag.input_dataset_parameters())
|
|
259
|
+
formatted_transfer_items = task.format_transfer_items(input_dataset_objects=transfer_objects)
|
|
260
|
+
# Then
|
|
150
261
|
source_filenames = []
|
|
151
262
|
destination_filenames = []
|
|
152
|
-
|
|
263
|
+
parameters = default_parameters_doc
|
|
264
|
+
for param in parameters:
|
|
153
265
|
for value in param["parameterValues"]:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
value_dict = json.loads(param_value)
|
|
266
|
+
if "__file__" in value["parameterValue"]:
|
|
267
|
+
value_dict = json.loads(value["parameterValue"])
|
|
157
268
|
bucket = value_dict["__file__"]["bucket"]
|
|
158
269
|
object_key = value_dict["__file__"]["objectKey"]
|
|
159
270
|
source_filenames.append(os.path.join("/", bucket, object_key))
|
|
160
271
|
destination_filenames.append(Path(object_key).name)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
for transfer_item in transfer_items:
|
|
272
|
+
assert len(formatted_transfer_items) == len(source_filenames)
|
|
273
|
+
for transfer_item in formatted_transfer_items:
|
|
164
274
|
assert transfer_item.source_path.as_posix() in source_filenames
|
|
165
275
|
assert transfer_item.destination_path.name in destination_filenames
|
|
166
276
|
assert str(transfer_item.destination_path).startswith(str(task.scratch.workflow_base_path))
|
|
167
277
|
assert not transfer_item.recursive
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def test_tag_transfer_items(transfer_l0_data_task):
|
|
281
|
+
"""
|
|
282
|
+
:Given: a TransferL0Data task with downloaded input dataset frames and parameter files
|
|
283
|
+
:When: tagging the downloaded files
|
|
284
|
+
:Then: the downloaded items are correctly tagged
|
|
285
|
+
"""
|
|
286
|
+
# Given
|
|
287
|
+
task = transfer_l0_data_task
|
|
288
|
+
task.download_input_dataset()
|
|
289
|
+
observe_transfer_objects = task.build_transfer_list(doc_tag=Tag.input_dataset_observe_frames())
|
|
290
|
+
calibration_transfer_objects = task.build_transfer_list(
|
|
291
|
+
doc_tag=Tag.input_dataset_calibration_frames()
|
|
292
|
+
)
|
|
293
|
+
frame_transfer_objects = observe_transfer_objects + calibration_transfer_objects
|
|
294
|
+
create_input_frames(task)
|
|
295
|
+
parameter_transfer_objects = task.build_transfer_list(doc_tag=Tag.input_dataset_parameters())
|
|
296
|
+
create_parameter_files(task)
|
|
297
|
+
# When
|
|
298
|
+
transfer_objects = frame_transfer_objects + parameter_transfer_objects
|
|
299
|
+
task.tag_transfer_objects(input_dataset_objects=transfer_objects)
|
|
300
|
+
# Then
|
|
301
|
+
input_tags = [Tag.input(), Tag.frame()]
|
|
302
|
+
input_frames_on_disk = list(task.scratch.find_all(tags=input_tags))
|
|
303
|
+
for obj in frame_transfer_objects:
|
|
304
|
+
destination_path = task.scratch.absolute_path(obj.object_key)
|
|
305
|
+
assert destination_path in input_frames_on_disk
|
|
306
|
+
assert len(input_frames_on_disk) == len(frame_transfer_objects)
|
|
307
|
+
for obj in parameter_transfer_objects:
|
|
308
|
+
destination_path = task.scratch.absolute_path(obj.object_key)
|
|
309
|
+
param_tag = Tag.parameter(Path(obj.object_key))
|
|
310
|
+
param_file_on_disk = list(task.scratch.find_all(tags=param_tag))
|
|
311
|
+
assert destination_path in param_file_on_disk
|
|
312
|
+
assert len(param_file_on_disk) == 1
|