lsst-ctrl-bps 29.2025.2900__tar.gz → 29.2025.3500__tar.gz
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.
- {lsst_ctrl_bps-29.2025.2900/python/lsst_ctrl_bps.egg-info → lsst_ctrl_bps-29.2025.3500}/PKG-INFO +1 -1
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/doc/lsst.ctrl.bps/quickstart.rst +53 -16
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/construct.py +108 -5
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/transform.py +1 -7
- lsst_ctrl_bps-29.2025.3500/python/lsst/ctrl/bps/version.py +2 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500/python/lsst_ctrl_bps.egg-info}/PKG-INFO +1 -1
- lsst_ctrl_bps-29.2025.3500/tests/test_construct.py +371 -0
- lsst_ctrl_bps-29.2025.2900/python/lsst/ctrl/bps/version.py +0 -2
- lsst_ctrl_bps-29.2025.2900/tests/test_construct.py +0 -122
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/COPYRIGHT +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/LICENSE +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/MANIFEST.in +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/README.md +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/bsd_license.txt +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/doc/lsst.ctrl.bps/CHANGES.rst +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/doc/lsst.ctrl.bps/index.rst +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/gpl-v3.0.txt +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/pyproject.toml +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/_exceptions.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/bps_config.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/bps_draw.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/bps_reports.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/bps_utils.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/cancel.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/cli/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/cli/bps.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/cli/cmd/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/cli/cmd/commands.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/cli/opt/__init__.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/cli/opt/arguments.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/cli/opt/option_groups.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/cli/opt/options.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/clustered_quantum_graph.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/constants.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/drivers.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/etc/bps_defaults.yaml +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/generic_workflow.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/initialize.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/ping.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/pre_transform.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/prepare.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/quantum_clustering_funcs.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/report.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/restart.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/status.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/submit.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/tests/config_test_utils.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/tests/gw_test_utils.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/wms_service.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst_ctrl_bps.egg-info/SOURCES.txt +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst_ctrl_bps.egg-info/dependency_links.txt +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst_ctrl_bps.egg-info/entry_points.txt +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst_ctrl_bps.egg-info/requires.txt +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst_ctrl_bps.egg-info/top_level.txt +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst_ctrl_bps.egg-info/zip-safe +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/setup.cfg +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/tests/test_bps_utils.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/tests/test_bpsconfig.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/tests/test_cli_commands.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/tests/test_clustered_quantum_graph.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/tests/test_drivers.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/tests/test_generic_workflow.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/tests/test_initialize.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/tests/test_ping.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/tests/test_pre_transform.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/tests/test_quantum_clustering_funcs.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/tests/test_report.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/tests/test_status.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/tests/test_transform.py +0 -0
- {lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/tests/test_wms_service.py +0 -0
{lsst_ctrl_bps-29.2025.2900/python/lsst_ctrl_bps.egg-info → lsst_ctrl_bps-29.2025.3500}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lsst-ctrl-bps
|
|
3
|
-
Version: 29.2025.
|
|
3
|
+
Version: 29.2025.3500
|
|
4
4
|
Summary: Pluggable execution of workflow graphs from Rubin pipelines.
|
|
5
5
|
Author-email: Rubin Observatory Data Management <dm-admin@lists.lsst.org>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -265,7 +265,7 @@ arguments, e.g.:
|
|
|
265
265
|
.. code-block:: yaml
|
|
266
266
|
|
|
267
267
|
customJob:
|
|
268
|
-
executable: "${HOME}/scripts/
|
|
268
|
+
executable: "${HOME}/scripts/do_stuff.sh"
|
|
269
269
|
arguments: "2"
|
|
270
270
|
|
|
271
271
|
# Uncomment settings below to disable automatic memory scaling and retries
|
|
@@ -276,16 +276,62 @@ arguments, e.g.:
|
|
|
276
276
|
|
|
277
277
|
where ``executable`` specifies the path to the executable to run and
|
|
278
278
|
``arguments`` is a list of arguments to be supplied to the executable as part
|
|
279
|
-
of the command line.
|
|
279
|
+
of the command line. If your executable does not take any command line
|
|
280
|
+
arguments set ``arguments`` to an empty string.
|
|
280
281
|
|
|
281
282
|
.. note::
|
|
282
283
|
|
|
283
|
-
|
|
284
|
-
|
|
284
|
+
The script specified by ``customJob.executable`` is copied to the run's
|
|
285
|
+
submit directory and this copy (not the original script) is being submitted
|
|
286
|
+
for execution. As a result, making any changes to the original script after
|
|
287
|
+
the run has been submitted will have no effect even if the run is still
|
|
288
|
+
in the WMS work queue waiting for execution.
|
|
289
|
+
|
|
290
|
+
If the script requires any input files that should be transferred to the
|
|
291
|
+
execution site as well and/or produces output files that should be brought back
|
|
292
|
+
specify them as follows:
|
|
293
|
+
|
|
294
|
+
.. code-block:: yaml
|
|
295
|
+
|
|
296
|
+
customJob:
|
|
297
|
+
executable: "${HOME}/scripts/do_stuff.sh"
|
|
298
|
+
arguments: "-o {outfile} {infile}"
|
|
299
|
+
inputs:
|
|
300
|
+
infile: path/to/input/file
|
|
301
|
+
outputs:
|
|
302
|
+
outfile: path/to/output/file
|
|
285
303
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
304
|
+
# Uncomment settings below to disable automatic memory scaling and retries
|
|
305
|
+
# which BPS enables by default.
|
|
306
|
+
#
|
|
307
|
+
# memoryMultiplier: 1
|
|
308
|
+
# numberOfRetries: 1
|
|
309
|
+
|
|
310
|
+
The paths in ``inputs`` specify files as they are accessed on the submit site.
|
|
311
|
+
They can be absolute or relative to the current working directory as the run is
|
|
312
|
+
submitted. The input files will be copied to the run's submit directory.
|
|
313
|
+
These copies (not the original files) will be submitted along with the script's
|
|
314
|
+
copy for execution. During the execution BPS will transfer these copies into a
|
|
315
|
+
single flat directory -- job's scratch directory on the execute machine.
|
|
316
|
+
|
|
317
|
+
The paths in ``outputs`` specifies the paths on the submit site the output
|
|
318
|
+
files will be copied to after job's completion. As input paths they can be
|
|
319
|
+
either absolute or relative. However, on the execution site, the script is
|
|
320
|
+
expected to write all its output files directly to job's scratch directory
|
|
321
|
+
(assumed to be the current working directory when the job starts unless stated
|
|
322
|
+
otherwise in the WMS-specific documentation).
|
|
323
|
+
|
|
324
|
+
As a result, both input and output files base names *must* be unique.
|
|
325
|
+
|
|
326
|
+
.. note::
|
|
327
|
+
|
|
328
|
+
Currently, BPS itself doesn't verify if the file declared in ``outputs`` was
|
|
329
|
+
produced by the script. Whether a missing output file will be considered an
|
|
330
|
+
error depends entirely on WMS in use.
|
|
331
|
+
|
|
332
|
+
The config files shown above will instruct BPS to create a special single-job
|
|
333
|
+
*workflow* to run your script. That workflow will be submitted for execution
|
|
334
|
+
as any other workflow.
|
|
289
335
|
|
|
290
336
|
As a result, the submission process for a custom script looks quite similar
|
|
291
337
|
to the submission process of regular payload jobs (i.e. jobs running
|
|
@@ -305,15 +351,6 @@ There are few things you need to keep in mind though:
|
|
|
305
351
|
instructions exist in the submit YAML. If you need the quantum graph, use
|
|
306
352
|
``bps submit``.
|
|
307
353
|
|
|
308
|
-
#. At the moment, the mechanism does not support transferring files other than
|
|
309
|
-
executable.
|
|
310
|
-
|
|
311
|
-
#. The script specified by ``customJob.executable`` is copied to the run's
|
|
312
|
-
submit directory and this copy (not the original script) is being submitted
|
|
313
|
-
for execution. As a result, making any changes to the original script after
|
|
314
|
-
the run has been submitted will have no effect even if the run is still in
|
|
315
|
-
the WMS work queue waiting for execution.
|
|
316
|
-
|
|
317
354
|
#. Some BPS plugins may require inclusion of plugin-specific settings for this
|
|
318
355
|
mechanism to work. Consult the documentation of the plugin you use for
|
|
319
356
|
details.
|
|
@@ -31,9 +31,16 @@ __all__ = ["construct"]
|
|
|
31
31
|
|
|
32
32
|
import logging
|
|
33
33
|
import shutil
|
|
34
|
+
from collections.abc import Callable
|
|
34
35
|
from pathlib import Path
|
|
35
36
|
|
|
36
|
-
from lsst.ctrl.bps import
|
|
37
|
+
from lsst.ctrl.bps import (
|
|
38
|
+
BpsConfig,
|
|
39
|
+
GenericWorkflow,
|
|
40
|
+
GenericWorkflowExec,
|
|
41
|
+
GenericWorkflowFile,
|
|
42
|
+
GenericWorkflowJob,
|
|
43
|
+
)
|
|
37
44
|
from lsst.ctrl.bps.transform import _get_job_values
|
|
38
45
|
|
|
39
46
|
_LOG = logging.getLogger(__name__)
|
|
@@ -73,7 +80,7 @@ def create_custom_workflow(config: BpsConfig) -> tuple[GenericWorkflow, BpsConfi
|
|
|
73
80
|
generic_workflow_config : `lsst.ctrl.BpsConfig`
|
|
74
81
|
Configuration to accompany created generic workflow.
|
|
75
82
|
"""
|
|
76
|
-
gwjob = create_custom_job(config)
|
|
83
|
+
gwjob, inputs, outputs = create_custom_job(config)
|
|
77
84
|
|
|
78
85
|
_, name = config.search("uniqProcName", opt={"required": True})
|
|
79
86
|
generic_workflow = GenericWorkflow(name)
|
|
@@ -90,6 +97,10 @@ def create_custom_workflow(config: BpsConfig) -> tuple[GenericWorkflow, BpsConfi
|
|
|
90
97
|
"bps_runsite": config["computeSite"],
|
|
91
98
|
}
|
|
92
99
|
)
|
|
100
|
+
if inputs:
|
|
101
|
+
generic_workflow.add_job_inputs(gwjob.name, inputs)
|
|
102
|
+
if outputs:
|
|
103
|
+
generic_workflow.add_job_outputs(gwjob.name, outputs)
|
|
93
104
|
|
|
94
105
|
generic_workflow_config = BpsConfig(config)
|
|
95
106
|
generic_workflow_config["workflowName"] = config["uniqProcName"]
|
|
@@ -98,7 +109,9 @@ def create_custom_workflow(config: BpsConfig) -> tuple[GenericWorkflow, BpsConfi
|
|
|
98
109
|
return generic_workflow, generic_workflow_config
|
|
99
110
|
|
|
100
111
|
|
|
101
|
-
def create_custom_job(
|
|
112
|
+
def create_custom_job(
|
|
113
|
+
config: BpsConfig,
|
|
114
|
+
) -> tuple[GenericWorkflowJob, list[GenericWorkflowFile], list[GenericWorkflowFile]]:
|
|
102
115
|
"""Create a job that will run a custom command or script.
|
|
103
116
|
|
|
104
117
|
Parameters
|
|
@@ -110,6 +123,10 @@ def create_custom_job(config: BpsConfig) -> GenericWorkflowJob:
|
|
|
110
123
|
-------
|
|
111
124
|
job : `lsst.ctrl.bps.GenericWorkflowJob`
|
|
112
125
|
A custom job responsible for running the command.
|
|
126
|
+
inputs : `list` [`lsst.ctrl.bps.GenericWorkflowFile`]
|
|
127
|
+
List of job's input files, empty if the job has no input files.
|
|
128
|
+
outputs : `list` [`lsst.ctrl.bps.GenericWorkflowFile`]
|
|
129
|
+
List of job's output files, empty if the job has no output files.
|
|
113
130
|
"""
|
|
114
131
|
prefix = Path(config["submitPath"])
|
|
115
132
|
job_label = "customJob"
|
|
@@ -135,6 +152,92 @@ def create_custom_job(config: BpsConfig) -> GenericWorkflowJob:
|
|
|
135
152
|
job.executable = GenericWorkflowExec(
|
|
136
153
|
name=script_name, src_uri=str(prefix / script_name), transfer_executable=True
|
|
137
154
|
)
|
|
138
|
-
job.arguments = config
|
|
155
|
+
_, job.arguments = config.search("arguments", opt=search_opts | {"replaceVars": False})
|
|
139
156
|
|
|
140
|
-
|
|
157
|
+
inputs = []
|
|
158
|
+
found, mapping = config.search("inputs", opt=search_opts)
|
|
159
|
+
if found:
|
|
160
|
+
inputs = create_job_files(mapping, prefix, path_creator=create_input_path)
|
|
161
|
+
|
|
162
|
+
outputs = []
|
|
163
|
+
found, mapping = config.search("outputs", opt=search_opts)
|
|
164
|
+
if found:
|
|
165
|
+
outputs = create_job_files(mapping, prefix, path_creator=create_output_path)
|
|
166
|
+
|
|
167
|
+
for gwfile in inputs + outputs:
|
|
168
|
+
job.arguments = job.arguments.replace(f"{{{gwfile.name}}}", f"<FILE:{gwfile.name}>")
|
|
169
|
+
|
|
170
|
+
return job, inputs, outputs
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def create_job_files(
|
|
174
|
+
file_specs: BpsConfig, prefix: str | Path, path_creator: Callable[[Path, Path], Path]
|
|
175
|
+
) -> list[GenericWorkflowFile]:
|
|
176
|
+
"""Create files for a job.
|
|
177
|
+
|
|
178
|
+
Parameters
|
|
179
|
+
----------
|
|
180
|
+
file_specs : `lsst.ctrl.bps.BpsConfig`
|
|
181
|
+
The mapping between file keys and file paths.
|
|
182
|
+
prefix : `str` | `pathlib.Path`
|
|
183
|
+
The root directory to which the files will be written.
|
|
184
|
+
path_creator : `Callable` [[`Path`, `Path`], `Path`]
|
|
185
|
+
File category that determines actions that need to be taken during
|
|
186
|
+
file creation.
|
|
187
|
+
|
|
188
|
+
Returns
|
|
189
|
+
-------
|
|
190
|
+
gwfiles : `list` [`lsst.ctrl.bps.GenericWorkflowFile`]
|
|
191
|
+
List of files created for the job.
|
|
192
|
+
"""
|
|
193
|
+
prefix = Path(prefix)
|
|
194
|
+
|
|
195
|
+
gwfiles = []
|
|
196
|
+
for key, path in file_specs.items():
|
|
197
|
+
src = Path(path)
|
|
198
|
+
dest = path_creator(src, prefix)
|
|
199
|
+
gwfiles.append(GenericWorkflowFile(name=key, src_uri=str(dest), wms_transfer=True))
|
|
200
|
+
return gwfiles
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def create_input_path(path: Path, prefix: Path) -> Path:
|
|
204
|
+
"""Process an input path.
|
|
205
|
+
|
|
206
|
+
Parameters
|
|
207
|
+
----------
|
|
208
|
+
path : `pathlib.Path`
|
|
209
|
+
The input path.
|
|
210
|
+
prefix : `pathlib.Path`
|
|
211
|
+
The root directory to which the file will be written.
|
|
212
|
+
|
|
213
|
+
Raises
|
|
214
|
+
------
|
|
215
|
+
ValueError
|
|
216
|
+
Raised if the input path does not exist or is a directory.
|
|
217
|
+
"""
|
|
218
|
+
if path.exists():
|
|
219
|
+
if path.is_dir():
|
|
220
|
+
raise ValueError(f"input path '{path} is a directory, must be file")
|
|
221
|
+
else:
|
|
222
|
+
raise ValueError(f"input path '{path}' does not exist")
|
|
223
|
+
dest = prefix / path.name
|
|
224
|
+
shutil.copy2(path, dest)
|
|
225
|
+
return dest
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def create_output_path(path: Path, prefix: Path) -> Path:
|
|
229
|
+
"""Process an output path.
|
|
230
|
+
|
|
231
|
+
Parameters
|
|
232
|
+
----------
|
|
233
|
+
path : `pathlib.Path`
|
|
234
|
+
The output path.
|
|
235
|
+
prefix : `pathlib.Path`
|
|
236
|
+
The root directory to which the file will be written.
|
|
237
|
+
"""
|
|
238
|
+
if path.is_absolute():
|
|
239
|
+
dest = path
|
|
240
|
+
else:
|
|
241
|
+
dest = prefix / path
|
|
242
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
243
|
+
return dest
|
|
@@ -316,13 +316,7 @@ def _fill_arguments(use_shared, generic_workflow, arguments, cmdvals):
|
|
|
316
316
|
# Have shared filesystems and jobs can share file.
|
|
317
317
|
uri = gwfile.src_uri
|
|
318
318
|
else:
|
|
319
|
-
|
|
320
|
-
# Temporary fix until have job wrapper that pulls files
|
|
321
|
-
# within job.
|
|
322
|
-
if gwfile.name == "butlerConfig" and os.path.splitext(gwfile.src_uri)[1] != ".yaml":
|
|
323
|
-
uri = "butler.yaml"
|
|
324
|
-
else:
|
|
325
|
-
uri = os.path.basename(gwfile.src_uri)
|
|
319
|
+
uri = os.path.basename(gwfile.src_uri)
|
|
326
320
|
else: # Using push transfer
|
|
327
321
|
uri = os.path.basename(gwfile.src_uri)
|
|
328
322
|
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500/python/lsst_ctrl_bps.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lsst-ctrl-bps
|
|
3
|
-
Version: 29.2025.
|
|
3
|
+
Version: 29.2025.3500
|
|
4
4
|
Summary: Pluggable execution of workflow graphs from Rubin pipelines.
|
|
5
5
|
Author-email: Rubin Observatory Data Management <dm-admin@lists.lsst.org>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# This file is part of ctrl_bps.
|
|
2
|
+
#
|
|
3
|
+
# Developed for the LSST Data Management System.
|
|
4
|
+
# This product includes software developed by the LSST Project
|
|
5
|
+
# (https://www.lsst.org).
|
|
6
|
+
# See the COPYRIGHT file at the top-level directory of this distribution
|
|
7
|
+
# for details of code ownership.
|
|
8
|
+
#
|
|
9
|
+
# This software is dual licensed under the GNU General Public License and also
|
|
10
|
+
# under a 3-clause BSD license. Recipients may choose which of these licenses
|
|
11
|
+
# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
|
|
12
|
+
# respectively. If you choose the GPL option then the following text applies
|
|
13
|
+
# (but note that there is still no warranty even if you opt for BSD instead):
|
|
14
|
+
#
|
|
15
|
+
# This program is free software: you can redistribute it and/or modify
|
|
16
|
+
# it under the terms of the GNU General Public License as published by
|
|
17
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
18
|
+
# (at your option) any later version.
|
|
19
|
+
#
|
|
20
|
+
# This program is distributed in the hope that it will be useful,
|
|
21
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
22
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
23
|
+
# GNU General Public License for more details.
|
|
24
|
+
#
|
|
25
|
+
# You should have received a copy of the GNU General Public License
|
|
26
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
27
|
+
|
|
28
|
+
"""Unit tests for the methods in construct.py."""
|
|
29
|
+
|
|
30
|
+
import os
|
|
31
|
+
import tempfile
|
|
32
|
+
import unittest
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from unittest.mock import patch
|
|
35
|
+
|
|
36
|
+
from lsst.ctrl.bps import BpsConfig, GenericWorkflowFile, GenericWorkflowJob
|
|
37
|
+
from lsst.ctrl.bps.construct import (
|
|
38
|
+
construct,
|
|
39
|
+
create_custom_job,
|
|
40
|
+
create_custom_workflow,
|
|
41
|
+
create_input_path,
|
|
42
|
+
create_job_files,
|
|
43
|
+
create_output_path,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ConstructTestCase(unittest.TestCase):
|
|
48
|
+
"""Tests for the main construct function."""
|
|
49
|
+
|
|
50
|
+
def setUp(self):
|
|
51
|
+
self.script = tempfile.NamedTemporaryFile(prefix="foo", suffix=".sh")
|
|
52
|
+
self.submit_dir = tempfile.TemporaryDirectory()
|
|
53
|
+
self.config = BpsConfig(
|
|
54
|
+
{
|
|
55
|
+
"submitPath": self.submit_dir.name,
|
|
56
|
+
"uniqProcName": "test_workflow",
|
|
57
|
+
"project": "test_project",
|
|
58
|
+
"campaign": "test_campaign",
|
|
59
|
+
"operator": "test_operator",
|
|
60
|
+
"payloadName": "test_payload",
|
|
61
|
+
"computeSite": "test_site",
|
|
62
|
+
"customJob": {
|
|
63
|
+
"executable": self.script.name,
|
|
64
|
+
"arguments": "test_arg",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
defaults={},
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def tearDown(self):
|
|
71
|
+
self.script.close()
|
|
72
|
+
self.submit_dir.cleanup()
|
|
73
|
+
|
|
74
|
+
def testConstructSuccess(self):
|
|
75
|
+
"""Test that construct returns a workflow and config."""
|
|
76
|
+
workflow, config = construct(self.config)
|
|
77
|
+
|
|
78
|
+
self.assertIsNotNone(workflow)
|
|
79
|
+
self.assertIsNotNone(config)
|
|
80
|
+
self.assertEqual(workflow.name, "test_workflow")
|
|
81
|
+
self.assertEqual(config["workflowName"], "test_workflow")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class CreateCustomWorkflowTestCase(unittest.TestCase):
|
|
85
|
+
"""Tests for creating a custom workflow."""
|
|
86
|
+
|
|
87
|
+
def setUp(self):
|
|
88
|
+
self.script = tempfile.NamedTemporaryFile(prefix="bar", suffix=".sh")
|
|
89
|
+
self.submit_dir = tempfile.TemporaryDirectory()
|
|
90
|
+
self.config = BpsConfig(
|
|
91
|
+
{
|
|
92
|
+
"submitPath": self.submit_dir.name,
|
|
93
|
+
"customJob": {
|
|
94
|
+
"executable": self.script.name,
|
|
95
|
+
"arguments": "arg",
|
|
96
|
+
},
|
|
97
|
+
"project": "dev",
|
|
98
|
+
"campaign": "test",
|
|
99
|
+
"operator": "tester",
|
|
100
|
+
"payloadName": "custom/workflow",
|
|
101
|
+
"computeCloud": "test_cloud",
|
|
102
|
+
"computeSite": "test_site",
|
|
103
|
+
},
|
|
104
|
+
defaults={},
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def tearDown(self):
|
|
108
|
+
self.script.close()
|
|
109
|
+
self.submit_dir.cleanup()
|
|
110
|
+
|
|
111
|
+
def testSuccess(self):
|
|
112
|
+
self.config["uniqProcName"] = self.config["submitPath"]
|
|
113
|
+
|
|
114
|
+
workflow, config = create_custom_workflow(self.config)
|
|
115
|
+
|
|
116
|
+
self.assertEqual(workflow.name, self.config["uniqProcName"])
|
|
117
|
+
self.assertEqual(workflow.job_counts, {"customJob": 1})
|
|
118
|
+
self.assertTrue(workflow.run_attrs["bps_isjob"])
|
|
119
|
+
self.assertTrue(workflow.run_attrs["bps_iscustom"])
|
|
120
|
+
self.assertEqual(workflow.run_attrs["bps_project"], "dev")
|
|
121
|
+
self.assertEqual(workflow.run_attrs["bps_campaign"], "test")
|
|
122
|
+
self.assertEqual(workflow.run_attrs["bps_operator"], "tester")
|
|
123
|
+
self.assertEqual(workflow.run_attrs["bps_payload"], "custom/workflow")
|
|
124
|
+
self.assertEqual(workflow.run_attrs["bps_run"], workflow.name)
|
|
125
|
+
self.assertEqual(workflow.run_attrs["bps_runsite"], "test_site")
|
|
126
|
+
self.assertEqual(config["workflowName"], self.config["uniqProcName"])
|
|
127
|
+
self.assertEqual(config["workflowPath"], self.config["submitPath"])
|
|
128
|
+
|
|
129
|
+
def testEmptyInputs(self):
|
|
130
|
+
"""Test workflow creation when job has no inputs files."""
|
|
131
|
+
with patch("lsst.ctrl.bps.construct.create_custom_job") as mock_create:
|
|
132
|
+
self.config["uniqProcName"] = "test_custom"
|
|
133
|
+
|
|
134
|
+
job = GenericWorkflowJob(name="test_job", label="test_job")
|
|
135
|
+
gwfile = GenericWorkflowFile(name="test_output", src_uri="test_output")
|
|
136
|
+
mock_create.return_value = (job, [], [gwfile])
|
|
137
|
+
|
|
138
|
+
workflow, config = create_custom_workflow(self.config)
|
|
139
|
+
|
|
140
|
+
self.assertEqual(len(workflow.get_job_inputs(job.name)), 0)
|
|
141
|
+
self.assertGreater(len(workflow.get_job_outputs(job.name)), 0)
|
|
142
|
+
|
|
143
|
+
def testEmptyOutputs(self):
|
|
144
|
+
"""Test workflow creation when job has no output files."""
|
|
145
|
+
with patch("lsst.ctrl.bps.construct.create_custom_job") as mock_create:
|
|
146
|
+
self.config["uniqProcName"] = "test_custom"
|
|
147
|
+
|
|
148
|
+
job = GenericWorkflowJob(name="test_job", label="test_job")
|
|
149
|
+
gwfile = GenericWorkflowFile(name="test_input", src_uri="test_input")
|
|
150
|
+
mock_create.return_value = (job, [gwfile], [])
|
|
151
|
+
|
|
152
|
+
workflow, config = create_custom_workflow(self.config)
|
|
153
|
+
|
|
154
|
+
self.assertGreater(len(workflow.get_job_inputs(job.name)), 0)
|
|
155
|
+
self.assertEqual(len(workflow.get_job_outputs(job.name)), 0)
|
|
156
|
+
|
|
157
|
+
def testEmptyInputsAndOutputs(self):
|
|
158
|
+
"""Test workflow creation when job has no input nor output files."""
|
|
159
|
+
with patch("lsst.ctrl.bps.construct.create_custom_job") as mock_create:
|
|
160
|
+
self.config["uniqProcName"] = "test_custom"
|
|
161
|
+
|
|
162
|
+
job = GenericWorkflowJob(name="test_job", label="test_job")
|
|
163
|
+
mock_create.return_value = (job, [], [])
|
|
164
|
+
|
|
165
|
+
workflow, config = create_custom_workflow(self.config)
|
|
166
|
+
|
|
167
|
+
self.assertEqual(len(workflow.get_job_inputs(job.name)), 0)
|
|
168
|
+
self.assertEqual(len(workflow.get_job_outputs(job.name)), 0)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class CreateCustomJobTestCase(unittest.TestCase):
|
|
172
|
+
"""Tests for creating a custom job."""
|
|
173
|
+
|
|
174
|
+
def setUp(self):
|
|
175
|
+
self.script = tempfile.NamedTemporaryFile(prefix="foo", suffix=".sh")
|
|
176
|
+
self.submit_dir = tempfile.TemporaryDirectory()
|
|
177
|
+
self.config = BpsConfig(
|
|
178
|
+
{
|
|
179
|
+
"submitPath": self.submit_dir.name,
|
|
180
|
+
"computeCloud": "test_cloud",
|
|
181
|
+
"computeSite": "test_site",
|
|
182
|
+
"customJob": {
|
|
183
|
+
"executable": self.script.name,
|
|
184
|
+
"arguments": "arg",
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
defaults={},
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
def tearDown(self):
|
|
191
|
+
self.script.close()
|
|
192
|
+
self.submit_dir.cleanup()
|
|
193
|
+
|
|
194
|
+
def testJobCreationNoFilesSuccess(self):
|
|
195
|
+
"""Test successful creation of a custom job."""
|
|
196
|
+
script_file = self.script.name
|
|
197
|
+
script_name = Path(script_file).name
|
|
198
|
+
|
|
199
|
+
job, inputs, outputs = create_custom_job(self.config)
|
|
200
|
+
|
|
201
|
+
self.assertEqual(job.name, script_name)
|
|
202
|
+
self.assertEqual(job.label, "customJob")
|
|
203
|
+
self.assertEqual(job.compute_cloud, "test_cloud")
|
|
204
|
+
self.assertEqual(job.compute_site, "test_site")
|
|
205
|
+
self.assertEqual(job.executable.name, script_name)
|
|
206
|
+
self.assertEqual(job.executable.src_uri, f"{self.submit_dir.name}/{script_name}")
|
|
207
|
+
self.assertTrue(job.executable.transfer_executable)
|
|
208
|
+
self.assertTrue(Path(f"{self.submit_dir.name}/{script_name}").exists())
|
|
209
|
+
self.assertEqual(job.arguments, "arg")
|
|
210
|
+
self.assertEqual(inputs, [])
|
|
211
|
+
self.assertEqual(outputs, [])
|
|
212
|
+
|
|
213
|
+
def testJobCreationWithFilesSuccess(self):
|
|
214
|
+
"""Test custom job creation with input and output files."""
|
|
215
|
+
# Create a temporary input file
|
|
216
|
+
input_file = tempfile.NamedTemporaryFile(prefix="input", suffix=".txt", delete=False)
|
|
217
|
+
input_file.write(b"test input data")
|
|
218
|
+
input_file.close()
|
|
219
|
+
|
|
220
|
+
self.config[".customJob.arguments"] = "--input {input1} --output {output1}"
|
|
221
|
+
self.config[".customJob.inputs.input1"] = input_file.name
|
|
222
|
+
self.config[".customJob.outputs.output1"] = "output.txt"
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
job, inputs, outputs = create_custom_job(self.config)
|
|
226
|
+
|
|
227
|
+
self.assertEqual(len(inputs), 1)
|
|
228
|
+
self.assertEqual(len(outputs), 1)
|
|
229
|
+
self.assertEqual(inputs[0].name, "input1")
|
|
230
|
+
self.assertEqual(outputs[0].name, "output1")
|
|
231
|
+
self.assertIn("<FILE:input1>", job.arguments)
|
|
232
|
+
self.assertIn("<FILE:output1>", job.arguments)
|
|
233
|
+
finally:
|
|
234
|
+
os.unlink(input_file.name)
|
|
235
|
+
|
|
236
|
+
def testJobCreationMissingExecutable(self):
|
|
237
|
+
"""Test custom job creation fails with missing executable."""
|
|
238
|
+
self.config[".customJob.executable"] = "/nonexistent/script.sh"
|
|
239
|
+
|
|
240
|
+
with self.assertRaises(FileNotFoundError):
|
|
241
|
+
create_custom_job(self.config)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class CreateJobFilesTestCase(unittest.TestCase):
|
|
245
|
+
"""Tests for create_job_files function."""
|
|
246
|
+
|
|
247
|
+
def setUp(self):
|
|
248
|
+
self.temp_dir = tempfile.TemporaryDirectory()
|
|
249
|
+
self.prefix = Path(self.temp_dir.name)
|
|
250
|
+
|
|
251
|
+
def tearDown(self):
|
|
252
|
+
self.temp_dir.cleanup()
|
|
253
|
+
|
|
254
|
+
def testJobFileCreationNoFiles(self):
|
|
255
|
+
"""Test create_job_files with empty file specs."""
|
|
256
|
+
config = BpsConfig({"inputs": {}})
|
|
257
|
+
_, filespecs = config.search("inputs")
|
|
258
|
+
files = create_job_files(filespecs, self.prefix, lambda path, prefix: prefix / path.name)
|
|
259
|
+
|
|
260
|
+
self.assertEqual(files, [])
|
|
261
|
+
|
|
262
|
+
def testJobFileCreationWithFiles(self):
|
|
263
|
+
"""Test create_job_files with file specifications."""
|
|
264
|
+
config = BpsConfig(
|
|
265
|
+
{
|
|
266
|
+
"inputs": {
|
|
267
|
+
"file1": "/path/to/file1.txt",
|
|
268
|
+
"file2": "/path/to/file2.txt",
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
)
|
|
272
|
+
_, filespecs = config.search("inputs")
|
|
273
|
+
files = create_job_files(filespecs, self.prefix, lambda path, prefix: prefix / path.name)
|
|
274
|
+
|
|
275
|
+
self.assertEqual(len(files), 2)
|
|
276
|
+
self.assertEqual(files[0].name, "file1")
|
|
277
|
+
self.assertEqual(files[1].name, "file2")
|
|
278
|
+
self.assertTrue(files[0].wms_transfer)
|
|
279
|
+
self.assertTrue(files[1].wms_transfer)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class CreateInputPathTestCase(unittest.TestCase):
|
|
283
|
+
"""Tests for create_input_path function."""
|
|
284
|
+
|
|
285
|
+
def setUp(self):
|
|
286
|
+
self.temp_dir = tempfile.TemporaryDirectory()
|
|
287
|
+
self.prefix = Path(self.temp_dir.name)
|
|
288
|
+
|
|
289
|
+
# Create a test input file
|
|
290
|
+
self.input_file = tempfile.NamedTemporaryFile(prefix="input_", suffix=".txt", delete=False)
|
|
291
|
+
self.input_file.write(b"test content")
|
|
292
|
+
self.input_file.close()
|
|
293
|
+
|
|
294
|
+
def tearDown(self):
|
|
295
|
+
self.temp_dir.cleanup()
|
|
296
|
+
|
|
297
|
+
def testInputPathCreationSuccess(self):
|
|
298
|
+
"""Test successful input path creation."""
|
|
299
|
+
input_path = Path(self.input_file.name)
|
|
300
|
+
result_path = create_input_path(input_path, self.prefix)
|
|
301
|
+
|
|
302
|
+
expected_path = self.prefix / input_path.name
|
|
303
|
+
self.assertEqual(result_path, expected_path)
|
|
304
|
+
self.assertTrue(result_path.exists())
|
|
305
|
+
|
|
306
|
+
# Verify file content was copied
|
|
307
|
+
with open(result_path) as f:
|
|
308
|
+
content = f.read()
|
|
309
|
+
self.assertEqual(content, "test content")
|
|
310
|
+
|
|
311
|
+
def testInputPathCreationFileIsMissing(self):
|
|
312
|
+
"""Test input path creation fails if file does not exist."""
|
|
313
|
+
nonexistent_path = Path("/nonexistent/file.txt")
|
|
314
|
+
|
|
315
|
+
with self.assertRaisesRegex(ValueError, "does not exist"):
|
|
316
|
+
create_input_path(nonexistent_path, self.prefix)
|
|
317
|
+
|
|
318
|
+
def testInputPathCreationFileIsDirectory(self):
|
|
319
|
+
"""Test input path creation fails if path is a directory."""
|
|
320
|
+
dir_path = Path(self.temp_dir.name)
|
|
321
|
+
|
|
322
|
+
with self.assertRaisesRegex(ValueError, "is a directory"):
|
|
323
|
+
create_input_path(dir_path, self.prefix)
|
|
324
|
+
|
|
325
|
+
def testInputPathCreationPermissionError(self):
|
|
326
|
+
"""Test input path creation with permission denied."""
|
|
327
|
+
with patch("shutil.copy2", side_effect=PermissionError("Permission denied")):
|
|
328
|
+
with self.assertRaises(PermissionError):
|
|
329
|
+
create_input_path(Path(self.input_file.name), self.prefix)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class CreateOutputPathTestCase(unittest.TestCase):
|
|
333
|
+
"""Tests for create_output_path function."""
|
|
334
|
+
|
|
335
|
+
def setUp(self):
|
|
336
|
+
self.temp_dir = tempfile.TemporaryDirectory()
|
|
337
|
+
self.prefix = Path(self.temp_dir.name)
|
|
338
|
+
|
|
339
|
+
def tearDown(self):
|
|
340
|
+
self.temp_dir.cleanup()
|
|
341
|
+
|
|
342
|
+
def testOutputPathCreationRelativePathNew(self):
|
|
343
|
+
"""Test output path creation."""
|
|
344
|
+
output_path = Path("foo/bar.txt")
|
|
345
|
+
result_path = create_output_path(output_path, self.prefix)
|
|
346
|
+
expected_path = self.prefix / "foo/bar.txt"
|
|
347
|
+
|
|
348
|
+
self.assertEqual(result_path, expected_path)
|
|
349
|
+
self.assertTrue(result_path.parent.exists())
|
|
350
|
+
|
|
351
|
+
def testOutputPathCreationRelativePathParentExits(self):
|
|
352
|
+
"""Test output path creation when directory already exists."""
|
|
353
|
+
# Create the directory first
|
|
354
|
+
subdir = self.prefix / "foo"
|
|
355
|
+
subdir.mkdir()
|
|
356
|
+
|
|
357
|
+
output_path = Path("foo/bar.txt")
|
|
358
|
+
result_path = create_output_path(output_path, self.prefix)
|
|
359
|
+
expected_path = self.prefix / "foo/bar.txt"
|
|
360
|
+
|
|
361
|
+
self.assertEqual(result_path, expected_path)
|
|
362
|
+
self.assertTrue(result_path.parent.exists())
|
|
363
|
+
|
|
364
|
+
def testOutputPathCreationAbsolutePath(self):
|
|
365
|
+
"""Test that absolute output paths are handled properly."""
|
|
366
|
+
output_path = self.prefix / "foo/bar.txt"
|
|
367
|
+
expected_path = output_path
|
|
368
|
+
result_path = create_output_path(output_path, self.prefix)
|
|
369
|
+
|
|
370
|
+
self.assertEqual(result_path, expected_path)
|
|
371
|
+
self.assertTrue(result_path.parent.exists())
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
# This file is part of ctrl_bps.
|
|
2
|
-
#
|
|
3
|
-
# Developed for the LSST Data Management System.
|
|
4
|
-
# This product includes software developed by the LSST Project
|
|
5
|
-
# (https://www.lsst.org).
|
|
6
|
-
# See the COPYRIGHT file at the top-level directory of this distribution
|
|
7
|
-
# for details of code ownership.
|
|
8
|
-
#
|
|
9
|
-
# This software is dual licensed under the GNU General Public License and also
|
|
10
|
-
# under a 3-clause BSD license. Recipients may choose which of these licenses
|
|
11
|
-
# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
|
|
12
|
-
# respectively. If you choose the GPL option then the following text applies
|
|
13
|
-
# (but note that there is still no warranty even if you opt for BSD instead):
|
|
14
|
-
#
|
|
15
|
-
# This program is free software: you can redistribute it and/or modify
|
|
16
|
-
# it under the terms of the GNU General Public License as published by
|
|
17
|
-
# the Free Software Foundation, either version 3 of the License, or
|
|
18
|
-
# (at your option) any later version.
|
|
19
|
-
#
|
|
20
|
-
# This program is distributed in the hope that it will be useful,
|
|
21
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
22
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
23
|
-
# GNU General Public License for more details.
|
|
24
|
-
#
|
|
25
|
-
# You should have received a copy of the GNU General Public License
|
|
26
|
-
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
27
|
-
|
|
28
|
-
"""Unit tests for the methods in construct.py."""
|
|
29
|
-
|
|
30
|
-
import tempfile
|
|
31
|
-
import unittest
|
|
32
|
-
from pathlib import Path
|
|
33
|
-
|
|
34
|
-
from lsst.ctrl.bps import BpsConfig
|
|
35
|
-
from lsst.ctrl.bps.construct import create_custom_job, create_custom_workflow
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class CreateCustomJobTestCase(unittest.TestCase):
|
|
39
|
-
"""Tests for creating a custom job."""
|
|
40
|
-
|
|
41
|
-
def setUp(self):
|
|
42
|
-
self.script = tempfile.NamedTemporaryFile(prefix="foo", suffix=".sh")
|
|
43
|
-
self.submit_dir = tempfile.TemporaryDirectory()
|
|
44
|
-
self.config = BpsConfig(
|
|
45
|
-
{
|
|
46
|
-
"submitPath": self.submit_dir.name,
|
|
47
|
-
"computeCloud": "testcloud",
|
|
48
|
-
"computeSite": "testsite",
|
|
49
|
-
"customJob": {
|
|
50
|
-
"executable": self.script.name,
|
|
51
|
-
"arguments": "arg",
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
defaults={},
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
def tearDown(self):
|
|
58
|
-
self.script.close()
|
|
59
|
-
self.submit_dir.cleanup()
|
|
60
|
-
|
|
61
|
-
def testSuccess(self):
|
|
62
|
-
script_file = self.script.name
|
|
63
|
-
script_name = Path(script_file).name
|
|
64
|
-
|
|
65
|
-
job = create_custom_job(self.config)
|
|
66
|
-
|
|
67
|
-
self.assertEqual(job.name, script_name)
|
|
68
|
-
self.assertEqual(job.label, "customJob")
|
|
69
|
-
self.assertEqual(job.compute_cloud, "testcloud")
|
|
70
|
-
self.assertEqual(job.compute_site, "testsite")
|
|
71
|
-
self.assertEqual(job.executable.name, script_name)
|
|
72
|
-
self.assertEqual(job.executable.src_uri, f"{self.submit_dir.name}/{script_name}")
|
|
73
|
-
self.assertTrue(job.executable.transfer_executable)
|
|
74
|
-
self.assertTrue(Path(f"{self.submit_dir.name}/{script_name}").exists())
|
|
75
|
-
self.assertEqual(job.arguments, "arg")
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class CreateCustomWorkflowTestSuite(unittest.TestCase):
|
|
79
|
-
"""Tests for creating a custom workflow."""
|
|
80
|
-
|
|
81
|
-
def setUp(self):
|
|
82
|
-
self.script = tempfile.NamedTemporaryFile(prefix="bar", suffix=".sh")
|
|
83
|
-
self.submit_dir = tempfile.TemporaryDirectory()
|
|
84
|
-
self.config = BpsConfig(
|
|
85
|
-
{
|
|
86
|
-
"submitPath": self.submit_dir.name,
|
|
87
|
-
"customJob": {
|
|
88
|
-
"executable": self.script.name,
|
|
89
|
-
"arguments": "arg",
|
|
90
|
-
},
|
|
91
|
-
"project": "dev",
|
|
92
|
-
"campaign": "test",
|
|
93
|
-
"operator": "tester",
|
|
94
|
-
"payloadName": "custom/workflow",
|
|
95
|
-
"computeCloud": "testcloud",
|
|
96
|
-
"computeSite": "testsite",
|
|
97
|
-
},
|
|
98
|
-
defaults={},
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
def tearDown(self):
|
|
102
|
-
self.script.close()
|
|
103
|
-
self.submit_dir.cleanup()
|
|
104
|
-
|
|
105
|
-
def testSuccess(self):
|
|
106
|
-
self.config["uniqProcName"] = self.config["submitPath"]
|
|
107
|
-
|
|
108
|
-
workflow, config = create_custom_workflow(self.config)
|
|
109
|
-
|
|
110
|
-
self.assertEqual(workflow.name, self.config["uniqProcName"])
|
|
111
|
-
self.assertEqual(workflow.job_counts, {"customJob": 1})
|
|
112
|
-
self.assertTrue(workflow.run_attrs["bps_isjob"])
|
|
113
|
-
self.assertTrue(workflow.run_attrs["bps_iscustom"])
|
|
114
|
-
self.assertEqual(workflow.run_attrs["bps_project"], "dev")
|
|
115
|
-
self.assertEqual(workflow.run_attrs["bps_campaign"], "test")
|
|
116
|
-
self.assertEqual(workflow.run_attrs["bps_operator"], "tester")
|
|
117
|
-
self.assertEqual(workflow.run_attrs["bps_payload"], "custom/workflow")
|
|
118
|
-
self.assertEqual(workflow.run_attrs["bps_run"], workflow.name)
|
|
119
|
-
self.assertEqual(workflow.run_attrs["bps_runsite"], "testsite")
|
|
120
|
-
|
|
121
|
-
self.assertEqual(config["workflowName"], self.config["uniqProcName"])
|
|
122
|
-
self.assertEqual(config["workflowPath"], self.config["submitPath"])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/_exceptions.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/bps_config.py
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/bps_reports.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/cli/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/cli/cmd/__init__.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/cli/cmd/commands.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/cli/opt/__init__.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/cli/opt/arguments.py
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/cli/opt/options.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/etc/bps_defaults.yaml
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/generic_workflow.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/initialize.py
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/pre_transform.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst/ctrl/bps/wms_service.py
RENAMED
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst_ctrl_bps.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst_ctrl_bps.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/python/lsst_ctrl_bps.egg-info/zip-safe
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/tests/test_clustered_quantum_graph.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lsst_ctrl_bps-29.2025.2900 → lsst_ctrl_bps-29.2025.3500}/tests/test_quantum_clustering_funcs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|